Compare commits

...

7 Commits

Author SHA1 Message Date
Pascal Gauthier 7b9c83ecd2
Merge 05d579a1cd into 4ebe1ef21a 2024-04-26 08:43:37 -04:00
dependabot[bot] 4ebe1ef21a
build(deps): bump github.com/miekg/dns from 1.1.58 to 1.1.59 (#1452)
Bumps [github.com/miekg/dns](https://github.com/miekg/dns) from 1.1.58 to 1.1.59.
- [Changelog](https://github.com/miekg/dns/blob/master/Makefile.release)
- [Commits](https://github.com/miekg/dns/compare/v1.1.58...v1.1.59)

---
updated-dependencies:
- dependency-name: github.com/miekg/dns
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 12:49:13 +02:00
dependabot[bot] 7f20d17d2e
build(deps): bump github.com/onsi/gomega from 1.32.0 to 1.33.0 (#1455)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.32.0 to 1.33.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.32.0...v1.33.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 12:48:40 +02:00
dependabot[bot] cbbe8d46f0
build(deps): bump github.com/avast/retry-go/v4 from 4.5.1 to 4.6.0 (#1456)
Bumps [github.com/avast/retry-go/v4](https://github.com/avast/retry-go) from 4.5.1 to 4.6.0.
- [Release notes](https://github.com/avast/retry-go/releases)
- [Commits](https://github.com/avast/retry-go/compare/4.5.1...4.6.0)

---
updated-dependencies:
- dependency-name: github.com/avast/retry-go/v4
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 12:48:16 +02:00
dependabot[bot] 62b1354fba
build(deps): bump github.com/docker/docker (#1459)
Bumps [github.com/docker/docker](https://github.com/docker/docker) from 26.0.1+incompatible to 26.1.0+incompatible.
- [Release notes](https://github.com/docker/docker/releases)
- [Commits](https://github.com/docker/docker/compare/v26.0.1...v26.1.0)

---
updated-dependencies:
- dependency-name: github.com/docker/docker
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-26 12:47:46 +02:00
Thomas Anderson e99c98b4c2
feat: log the rule which is the cause of blocking (#1460)
Co-authored-by: ThinkChaos <ThinkChaos@users.noreply.github.com>
2024-04-24 12:58:29 -04:00
Pascal Gauthier 05d579a1cd Add support for time-base scheduling of blacklists
Signed-off-by: Pascal Gauthier <pgauthier@nihilisme.ca>
2024-04-12 09:20:45 -04:00
17 changed files with 796 additions and 22 deletions

View File

@ -23,6 +23,7 @@ Blocky is a DNS proxy and ad-blocker for the local network written in Go with fo
- **Blocking** - Blocking of DNS queries with external lists (Ad-block, malware) and allowlisting - **Blocking** - Blocking of DNS queries with external lists (Ad-block, malware) and allowlisting
- Definition of allow/denylists per client group (Kids, Smart home devices, etc.) - Definition of allow/denylists per client group (Kids, Smart home devices, etc.)
- Denylists can be configured with a schedules map (active/inactive depending of days and hours ranges)
- Periodical reload of external allow/denylists - Periodical reload of external allow/denylists
- Regex support - Regex support
- Blocking of request domain, response CNAME (deep CNAME inspection) and response IP addresses (against IP lists) - Blocking of request domain, response CNAME (deep CNAME inspection) and response IP addresses (against IP lists)

View File

@ -50,7 +50,12 @@ func (cache stringMap) contains(searchString string) bool {
}) })
if idx < searchBucketLen { if idx < searchBucketLen {
return cache[searchLen][idx*searchLen:idx*searchLen+searchLen] == strings.ToLower(normalized) blockRule := cache[searchLen][idx*searchLen : idx*searchLen+searchLen]
if blockRule == normalized {
log.PrefixedLog("string_map").Debugf("block rule '%s' matched with '%s'", blockRule, searchString)
return true
}
} }
return false return false
@ -132,7 +137,7 @@ func (cache regexCache) elementCount() int {
func (cache regexCache) contains(searchString string) bool { func (cache regexCache) contains(searchString string) bool {
for _, regex := range cache { for _, regex := range cache {
if regex.MatchString(searchString) { if regex.MatchString(searchString) {
log.PrefixedLog("regexCache").Debugf("regex '%s' matched with '%s'", regex, searchString) log.PrefixedLog("regex_cache").Debugf("regex '%s' matched with '%s'", regex, searchString)
return true return true
} }

View File

@ -11,6 +11,7 @@ type Blocking struct {
Denylists map[string][]BytesSource `yaml:"denylists"` Denylists map[string][]BytesSource `yaml:"denylists"`
Allowlists map[string][]BytesSource `yaml:"allowlists"` Allowlists map[string][]BytesSource `yaml:"allowlists"`
ClientGroupsBlock map[string][]string `yaml:"clientGroupsBlock"` ClientGroupsBlock map[string][]string `yaml:"clientGroupsBlock"`
Schedules Schedules `yaml:"schedules"`
BlockType string `yaml:"blockType" default:"ZEROIP"` BlockType string `yaml:"blockType" default:"ZEROIP"`
BlockTTL Duration `yaml:"blockTTL" default:"6h"` BlockTTL Duration `yaml:"blockTTL" default:"6h"`
Loading SourceLoading `yaml:"loading"` Loading SourceLoading `yaml:"loading"`
@ -80,6 +81,26 @@ func (c *Blocking) LogConfig(logger *logrus.Entry) {
log.WithIndent(logger, " ", func(logger *logrus.Entry) { log.WithIndent(logger, " ", func(logger *logrus.Entry) {
c.logListGroups(logger, c.Allowlists) c.logListGroups(logger, c.Allowlists)
}) })
logger.Info("schedules:")
log.WithIndent(logger, " ", func(logger *logrus.Entry) {
c.logSchedules(logger, c.Schedules)
})
}
func (c *Blocking) logSchedules(logger *logrus.Entry, schedules map[string][]Schedule) {
for group, schedulesList := range schedules {
logger.Infof("%s:", group)
for _, schedule := range schedulesList {
logger.Infof(" - days: %s hoursRanges: %s", schedule.Days, schedule.HoursRanges)
}
_, ok := c.Denylists[group]
if !ok {
logger.Warnf(" !! %s not found in denylists, schedule will have no effect", group)
}
}
} }
func (c *Blocking) logListGroups(logger *logrus.Entry, listGroups map[string][]BytesSource) { func (c *Blocking) logListGroups(logger *logrus.Entry, listGroups map[string][]BytesSource) {

View File

@ -14,6 +14,8 @@ var _ = Describe("BlockingConfig", func() {
suiteBeforeEach() suiteBeforeEach()
BeforeEach(func() { BeforeEach(func() {
hr, _ := parseHoursRange("09:00-17:00")
cfg = Blocking{ cfg = Blocking{
BlockType: "ZEROIP", BlockType: "ZEROIP",
BlockTTL: Duration(time.Minute), BlockTTL: Duration(time.Minute),
@ -23,6 +25,15 @@ var _ = Describe("BlockingConfig", func() {
ClientGroupsBlock: map[string][]string{ ClientGroupsBlock: map[string][]string{
"default": {"gr1"}, "default": {"gr1"},
}, },
Schedules: Schedules{
"gr1": []Schedule{
{
Days: []day{day(time.Monday)},
HoursRanges: []hoursRange{hr},
},
},
"gr2": []Schedule{},
},
} }
}) })
@ -57,6 +68,9 @@ var _ = Describe("BlockingConfig", func() {
Expect(hook.Calls).ShouldNot(BeEmpty()) Expect(hook.Calls).ShouldNot(BeEmpty())
Expect(hook.Messages[0]).Should(Equal("clientGroupsBlock:")) Expect(hook.Messages[0]).Should(Equal("clientGroupsBlock:"))
Expect(hook.Messages).Should(ContainElement(Equal("schedules:")))
Expect(hook.Messages).Should(ContainElement(Equal(" - days: [Monday] hoursRanges: [09:00-17:00]")))
Expect(hook.Messages).Should(ContainElement(Equal(" !! gr2 not found in denylists, schedule will have no effect")))
Expect(hook.Messages).Should(ContainElement(Equal("blockType = ZEROIP"))) Expect(hook.Messages).Should(ContainElement(Equal("blockType = ZEROIP")))
}) })
}) })

150
config/schedule.go Normal file
View File

@ -0,0 +1,150 @@
package config
import (
"fmt"
"strings"
"time"
)
const (
validHRParts = 2
)
// Schedule of active blocking
type Schedule struct {
Days []day `yaml:"days"`
HoursRanges []hoursRange `yaml:"hoursRanges"`
Active bool
}
type day time.Weekday
type hoursRange struct {
Start time.Time
End time.Time
}
// String implements the fmt.Stringer interface
func (d day) String() string {
return time.Weekday(d).String()
}
// String implements the fmt.Stringer interface
func (hr hoursRange) String() string {
return hr.Start.Format("15:04") + "-" + hr.End.Format("15:04")
}
// UnmarshalText implements the encoding.TextUnmarshaler interface
func (d *day) UnmarshalText(text []byte) error {
input := string(text)
result, err := parseDay(input)
if err != nil {
return err
}
*d = day(result)
return nil
}
// UnmarshalText implements the encoding.TextUnmarshaler interface
func (hr *hoursRange) UnmarshalText(text []byte) error {
input := string(text)
result, err := parseHoursRange(input)
if err != nil {
return err
}
*hr = result
return nil
}
func (hr *hoursRange) setRefTime() {
*hr = hoursRange{
Start: getRefTime(hr.Start),
End: getRefTime(hr.End),
}
}
func parseDay(day string) (time.Weekday, error) {
daysMap := map[string]time.Weekday{
"Mon": time.Monday, "Tue": time.Tuesday, "Wed": time.Wednesday,
"Thu": time.Thursday, "Fri": time.Friday, "Sat": time.Saturday, "Sun": time.Sunday,
}
if day, exists := daysMap[day]; exists {
return day, nil
}
return time.Sunday, fmt.Errorf("invalid day: %s", day)
}
func parseHoursRange(hours string) (hoursRange, error) {
parts := strings.Split(hours, "-")
if len(parts) != validHRParts {
return hoursRange{}, fmt.Errorf("invalid hours range format: %s", hours)
}
start, err := time.Parse("15:04", parts[0])
if err != nil {
return hoursRange{}, fmt.Errorf("invalid start hour: %s", hours)
}
end, err := time.Parse("15:04", parts[1])
if err != nil {
return hoursRange{}, fmt.Errorf("invalid end hour: %s", hours)
}
if start.After(end) {
return hoursRange{}, fmt.Errorf("start hour is after end hour: %s", hours)
}
return hoursRange{
Start: start,
End: end,
}, nil
}
func (s *Schedule) isActive(nowFunc func() time.Time) bool {
now := nowFunc()
dayActive := false
curDay := now.Weekday()
for _, d := range s.Days {
if d == day(curDay) {
dayActive = true
break
}
}
if !dayActive {
return false
}
curTime := now
for _, hrsRange := range s.HoursRanges {
hrsRange.setRefTime()
refTime := getRefTime(curTime)
if refTime == hrsRange.Start {
return true
}
if refTime.After(hrsRange.Start) && refTime.Before(hrsRange.End) {
return true
}
}
return false
}
func getRefTime(t time.Time) time.Time {
return time.Date(0, 1, 1, t.Hour(), t.Minute(), t.Second(), 0, time.UTC)
}

141
config/schedule_test.go Normal file
View File

@ -0,0 +1,141 @@
package config
import (
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Schedule", func() {
var cfg Schedule
BeforeEach(func() {
hr, _ := parseHoursRange("09:00-17:00")
cfg = Schedule{
Days: []day{day(time.Monday)},
HoursRanges: []hoursRange{hr},
Active: false,
}
})
Describe("day stringer", func() {
When("valid day", func() {
It("should return the correct day string", func() {
day := cfg.Days[0].String()
Expect(day).To(Equal(time.Monday.String()))
})
})
})
Describe("hoursRange stringer", func() {
When("valid range", func() {
It("should return the correct range string", func() {
hr := cfg.HoursRanges[0].String()
Expect(hr).To(Equal("09:00-17:00"))
})
})
})
Describe("parseDay", func() {
When("valid day", func() {
It("should return the correct weekday", func() {
day, err := parseDay("Mon")
Expect(err).Should(Succeed())
Expect(day).To(Equal(time.Monday))
day, err = parseDay("Fri")
Expect(err).Should(Succeed())
Expect(day).To(Equal(time.Friday))
})
})
When("invalid day", func() {
It("should return an error", func() {
_, err := parseDay("invalid_day")
Expect(err.Error()).To(Equal("invalid day: invalid_day"))
})
})
})
Describe("parseHoursRange", func() {
When("valid hour range", func() {
It("should return the correct hours range", func() {
hr, err := parseHoursRange("09:00-17:00")
Expect(err).Should(Succeed())
Expect(hr.Start.Format("15:04")).To(Equal("09:00"))
Expect(hr.End.Format("15:04")).To(Equal("17:00"))
})
})
When("invalid hour range", func() {
It("should return an error", func() {
_, err := parseHoursRange("09-17:00")
Expect(err.Error()).To(Equal("invalid start hour: 09-17:00"))
})
})
When("invalid hours range format", func() {
It("should return an error", func() {
_, err := parseHoursRange("18:00")
Expect(err.Error()).To(Equal("invalid hours range format: 18:00"))
})
})
When("invalid start hour", func() {
It("should return an error", func() {
_, err := parseHoursRange("25:00-17:00")
Expect(err.Error()).To(Equal("invalid start hour: 25:00-17:00"))
})
})
When("with invalid end hour", func() {
It("should return an error", func() {
_, err := parseHoursRange("09:00-17:60")
Expect(err.Error()).To(Equal("invalid end hour: 09:00-17:60"))
})
})
When("with start hour after end hour", func() {
It("should return an error", func() {
_, err := parseHoursRange("20:00-08:00")
Expect(err.Error()).To(Equal("start hour is after end hour: 20:00-08:00"))
})
})
})
Describe("isActive", func() {
When("active schedule hour", func() {
It("should return true", func() {
fakeTime := getFakeTime(time.Monday, "10:00")
active := cfg.isActive(fakeTime)
Expect(active).To(BeTrue())
})
})
When("active schedule start time hour ", func() {
It("should return true", func() {
fakeTime := getFakeTime(time.Monday, "09:00")
active := cfg.isActive(fakeTime)
Expect(active).To(BeTrue())
})
})
When("inactive schedule hour", func() {
It("should return true", func() {
fakeTime := getFakeTime(time.Monday, "07:00")
active := cfg.isActive(fakeTime)
Expect(active).To(BeFalse())
})
})
When("inactive schedule day", func() {
It("should return false", func() {
fakeTime := getFakeTime(time.Sunday, "09:00")
active := cfg.isActive(fakeTime)
Expect(active).To(BeFalse())
})
})
})
})

107
config/schedules.go Normal file
View File

@ -0,0 +1,107 @@
package config
import (
"context"
"time"
"github.com/0xERR0R/blocky/evt"
"github.com/0xERR0R/blocky/log"
"github.com/sirupsen/logrus"
)
const (
refreshInterval = time.Minute
)
// Schedules is the list of schedule for the defined blacklist
type Schedules map[string][]Schedule
// Refresh update Schedule to set the Active field boolean
func (s Schedules) Refresh(ctx context.Context, nowFunc func() time.Time) {
logger := log.PrefixedLog("refresh_schedules")
s.setActive(nowFunc, logger) // initial schedules refresh (after blocky start)
syncSchedulesWithSystemTime(s, nowFunc, logger)
ticker := time.NewTicker(refreshInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
s.setActive(nowFunc, logger)
case <-ctx.Done():
return
}
}
}
func syncSchedulesWithSystemTime(s Schedules, nowFunc func() time.Time, logger *logrus.Entry) {
waitTime := getWaitTimeBeforeNextMinute()
time.Sleep(waitTime)
s.setActive(nowFunc, logger) // now in sync with system time
}
func getWaitTimeBeforeNextMinute() time.Duration {
return time.Until(
time.Date(
time.Now().Year(),
time.Now().Month(),
time.Now().Day(),
time.Now().Hour(),
time.Now().Minute()+1, 0, 0,
time.Now().Location(),
),
)
}
func (s Schedules) setActive(nowFunc func() time.Time, logger *logrus.Entry) {
if nowFunc == nil {
nowFunc = time.Now
}
for group, schedList := range s {
for i, schedule := range schedList {
active := false
activeStr := "inactive"
if schedule.isActive(nowFunc) {
active = true
activeStr = "active"
}
if schedule.Active != active {
s[group][i] = Schedule{
Days: schedule.Days,
HoursRanges: schedule.HoursRanges,
Active: active,
}
activeFlag := 0
if active {
activeFlag = 1
}
evt.Bus().Publish(evt.SchedulesActive, group, activeFlag)
logger.Infof("group %s is now %s", group, activeStr)
}
}
}
}
// IsActive checks if the schedules of the group is active at the current time
func (s Schedules) IsActive(group string, nowFunc func() time.Time) bool {
if nowFunc == nil {
nowFunc = time.Now
}
for _, schedule := range s[group] {
if schedule.isActive(nowFunc) {
return true
}
}
return false
}

107
config/schedules_test.go Normal file
View File

@ -0,0 +1,107 @@
package config
import (
"context"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Schedules", func() {
const (
group = "testGroup"
)
var (
schedules Schedules
ctx context.Context
cancel context.CancelFunc
)
BeforeEach(func() {
schedules = make(Schedules)
ctx, cancel = context.WithCancel(context.Background())
hr, _ := parseHoursRange("09:00-17:00")
schedules[group] = []Schedule{
{
Days: []day{day(time.Saturday)},
HoursRanges: []hoursRange{hr},
},
}
})
AfterEach(func() {
cancel()
})
Describe("Refresh", func() {
When("refresh loop running", func() {
go schedules.Refresh(ctx, nil)
time.Sleep(time.Second)
It("should set the Active field", func() {
now := time.Now()
weekDay := now.Weekday()
hour := now.Hour()
if weekDay == time.Saturday && hour >= 9 && hour < 17 {
Expect(schedules[group][0].Active).To(BeTrue())
} else {
Expect(schedules[group][0].Active).To(BeFalse())
}
})
})
It("should set the Active field to true when schedule is active", func() {
fakeTime := getFakeTime(time.Saturday, "10:00")
go schedules.Refresh(ctx, fakeTime)
time.Sleep(time.Second)
Expect(schedules[group][0].Active).To(BeTrue())
})
It("should set the Active field to false when schedule is inactive", func() {
fakeTime := getFakeTime(time.Saturday, "19:00")
schedules[group][0].Active = true
go schedules.Refresh(ctx, fakeTime)
time.Sleep(time.Second)
Expect(schedules[group][0].Active).To(BeFalse())
})
})
Describe("IsActive", func() {
When("schedule is active", func() {
It("should return true", func() {
fakeTime := getFakeTime(time.Saturday, "10:00")
isActive := schedules.IsActive(group, fakeTime)
Expect(isActive).To(BeTrue())
})
})
When("schedule is inactive", func() {
It("should return false", func() {
fakeTime := getFakeTime(time.Sunday, "10:00")
isActive := schedules.IsActive(group, fakeTime)
Expect(isActive).To(BeFalse())
})
})
})
})
func getFakeTime(wk time.Weekday, hr string) func() time.Time {
return func() time.Time {
t, err := time.Parse("15:04", hr)
if err != nil {
panic(err)
}
return getRefTime(t).AddDate(0, 0, int(wk)+1)
}
}

View File

@ -77,6 +77,8 @@ blocking:
*.example.com *.example.com
special: special:
- https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts - https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts
youtube:
- https://raw.githubusercontent.com/gieljnssns/Social-media-Blocklists/master/pihole-youtube.txt
# definition of allowlist groups. # definition of allowlist groups.
# Note: if the same group has both allow/denylists, allowlists take precedence. Meaning if a domain is both blocked and allowed, it will be allowed. # Note: if the same group has both allow/denylists, allowlists take precedence. Meaning if a domain is both blocked and allowed, it will be allowed.
# If a group has only allowlist entries, only domains from this list are allowed, and all others be blocked. # If a group has only allowlist entries, only domains from this list are allowed, and all others be blocked.
@ -89,6 +91,16 @@ blocking:
allowlistdomain.com allowlistdomain.com
# this is a regex # this is a regex
/^banners?[_.-]/ /^banners?[_.-]/
# definition of the blacklist schedules. These define the active schedule (blocking). When inactive, the list will be skipped.
schedules:
youtube:
- days: ["Mon", "Tue", "Wed", "Thu"]
hoursRanges:
- "06:45-08:30"
- "19:00-23:30"
- days: ["Sun"]
hoursRanges:
- "20:00-23:30"
# definition: which groups should be applied for which client # definition: which groups should be applied for which client
clientGroupsBlock: clientGroupsBlock:
# default will be used, if no special definition for a client name exists # default will be used, if no special definition for a client name exists
@ -101,6 +113,7 @@ blocking:
- ads - ads
192.168.178.1/24: 192.168.178.1/24:
- special - special
- youtube
# which response will be sent, if query is blocked: # which response will be sent, if query is blocked:
# zeroIp: 0.0.0.0 will be returned (default) # zeroIp: 0.0.0.0 will be returned (default)
# nxDomain: return NXDOMAIN as return code # nxDomain: return NXDOMAIN as return code

View File

@ -89,7 +89,6 @@ This applies to all of them. The default strategy is blocking.
For `init.strategy`, the "init" is testing the given resolvers for each group. The potentially fatal error, depending on the strategy, is if a group has no functional resolvers. For `init.strategy`, the "init" is testing the given resolvers for each group. The potentially fatal error, depending on the strategy, is if a group has no functional resolvers.
### Upstream Groups ### Upstream Groups
To resolve a DNS query, blocky needs external public or private DNS resolvers. Blocky supports DNS resolvers with To resolve a DNS query, blocky needs external public or private DNS resolvers. Blocky supports DNS resolvers with
@ -198,7 +197,6 @@ Currently available strategies:
- 9.8.7.6 - 9.8.7.6
``` ```
## Bootstrap DNS configuration ## Bootstrap DNS configuration
These DNS servers are used to resolve upstream DoH and DoT servers that are specified as host names, and list domains. These DNS servers are used to resolve upstream DoH and DoT servers that are specified as host names, and list domains.
@ -291,10 +289,11 @@ This configuration will also resolve any subdomain of the defined domain, recurs
CNAME records are supported by utilizing the `zone` parameter. The zone file is a multiline string containing a [DNS Zone File](https://en.wikipedia.org/wiki/Zone_file#Example_file). CNAME records are supported by utilizing the `zone` parameter. The zone file is a multiline string containing a [DNS Zone File](https://en.wikipedia.org/wiki/Zone_file#Example_file).
For records defined using the `zone` parameter, the `customTTL` parameter is unused. Instead, the TTL is defined in the zone directly. For records defined using the `zone` parameter, the `customTTL` parameter is unused. Instead, the TTL is defined in the zone directly.
The following directives are supported in the zone file: The following directives are supported in the zone file:
* `$ORIGIN` - sets the origin for relative domain names
* `$TTL` - sets the default TTL for records in the zone - `$ORIGIN` - sets the origin for relative domain names
* `$INCLUDE` - includes another zone file relative to the blocky executable - `$TTL` - sets the default TTL for records in the zone
* `$GENERATE` - generates a range of records - `$INCLUDE` - includes another zone file relative to the blocky executable
- `$GENERATE` - generates a range of records
With the optional parameter `rewrite` you can replace domain part of the query with the defined part **before** the With the optional parameter `rewrite` you can replace domain part of the query with the defined part **before** the
resolver lookup is performed. resolver lookup is performed.
@ -455,6 +454,54 @@ The supported list formats are:
!!! warning !!! warning
You must also define a client group mapping, otherwise the allow/denylist definitions will have no effect. You must also define a client group mapping, otherwise the allow/denylist definitions will have no effect.
#### Schedules for blacklists
It's possible to create schedules for your blacklists. When the schedule is active, the blocking will occur, when inactive, the blacklist will be skipped.
!!! example
```yaml
blocking:
blackLists:
ads:
- https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt
- https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
- |
# inline definition using YAML literal block scalar style
# content is in plain domain list format
someadsdomain.com
anotheradsdomain.com
*.wildcard.example.com # blocks wildcard.example.com and all subdomains
- |
# inline definition with a regex
/^banners?[_.-]/
special:
- https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews/hosts
youtube:
- https://raw.githubusercontent.com/gieljnssns/Social-media-Blocklists/master/pihole-youtube.txt
whiteLists:
ads:
- whitelist.txt
- /path/to/file.txt
- |
# inline definition with YAML literal block scalar style
whitelistdomain.com
schedules:
youtube:
- days: ["Mon", "Tue", "Wed", "Thu"]
hoursRanges:
- "06:45-08:30"
- "19:00-23:30"
- days: ["Sun"]
hoursRanges:
- "20:00-23:30"
```
In this example, the **youtube** blacklist will be active at the specified days, between the specified hours ranges.
!!! note
Whitelists is respected when a sheduled blacklist is active.
#### Wildcard support #### Wildcard support
You can use wildcards to block a domain and all its subdomains. You can use wildcards to block a domain and all its subdomains.
@ -735,7 +782,6 @@ Configuration parameters:
strategy: fast strategy: fast
``` ```
## Deliver EDE codes as EDNS0 option ## Deliver EDE codes as EDNS0 option
DNS responses can be extended with EDE codes according to [RFC8914](https://datatracker.ietf.org/doc/rfc8914/). DNS responses can be extended with EDE codes according to [RFC8914](https://datatracker.ietf.org/doc/rfc8914/).

View File

@ -123,6 +123,94 @@ var _ = Describe("External lists and query blocking", func() {
It("should download external list on startup and block queries", func(ctx context.Context) { It("should download external list on startup and block queries", func(ctx context.Context) {
msg := util.NewMsgWithQuestion("blockeddomain.com.", A) msg := util.NewMsgWithQuestion("blockeddomain.com.", A)
Expect(doDNSRequest(ctx, blocky, msg)).
Should(
SatisfyAll(
BeDNSRecord("blockeddomain.com.", A, "0.0.0.0"),
HaveTTL(BeNumerically("==", 6*60*60)),
))
Expect(getContainerLogs(ctx, blocky)).Should(BeEmpty())
})
})
})
Describe("Query blocking against external blacklists with schedule inactive", func() {
When("external blacklists are defined and available", func() {
BeforeEach(func(ctx context.Context) {
e2eNet = getRandomNetwork(ctx)
_, err = createDNSMokkaContainer(ctx, "moka", e2eNet, `A blockeddomain.com/NOERROR("A 1.2.3.4 123")`)
Expect(err).Should(Succeed())
_, err = createHTTPServerContainer(ctx, "httpserver", e2eNet, "list.txt", "blockeddomain.com")
Expect(err).Should(Succeed())
blocky, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
" groups:",
" default:",
" - moka",
"blocking:",
" blackLists:",
" ads:",
" - http://httpserver:8080/list.txt",
" schedules:",
" ads:",
" - days: [\"Sun\",]",
" hoursRanges: [\"00:00-00:01\"]",
" clientGroupsBlock:",
" default:",
" - ads",
)
Expect(err).Should(Succeed())
})
It("should download external list on startup and resolve queries", func(ctx context.Context) {
msg := util.NewMsgWithQuestion("blockeddomain.com.", A)
Expect(doDNSRequest(ctx, blocky, msg)).
Should(
SatisfyAll(
BeDNSRecord("blockeddomain.com.", A, "1.2.3.4"),
HaveTTL(BeNumerically("==", 123)),
))
Expect(getContainerLogs(ctx, blocky)).Should(BeEmpty())
})
})
})
Describe("Query blocking against external blacklists with schedule active", func() {
When("external blacklists are defined and available", func() {
BeforeEach(func(ctx context.Context) {
_, err = createHTTPServerContainer(ctx, "httpserver", e2eNet, "list.txt", "blockeddomain.com")
Expect(err).Should(Succeed())
blocky, err = createBlockyContainer(ctx, e2eNet,
"log:",
" level: warn",
"upstreams:",
" groups:",
" default:",
" - moka",
"blocking:",
" blackLists:",
" ads:",
" - http://httpserver:8080/list.txt",
" schedules:",
" ads:",
" - days: [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"]",
" hoursRanges: [\"00:00-23:59\"]",
" clientGroupsBlock:",
" default:",
" - ads",
)
Expect(err).Should(Succeed())
})
It("should download external list on startup and block queries", func(ctx context.Context) {
msg := util.NewMsgWithQuestion("blockeddomain.com.", A)
Expect(doDNSRequest(ctx, blocky, msg)). Expect(doDNSRequest(ctx, blocky, msg)).
Should( Should(
SatisfyAll( SatisfyAll(

View File

@ -34,6 +34,12 @@ const (
// ApplicationStarted fires on start of the application. Parameter: version number, build time // ApplicationStarted fires on start of the application. Parameter: version number, build time
ApplicationStarted = "application:started" ApplicationStarted = "application:started"
// SchedulesActive fires, if a schedule is active. Parameter: schedule name, active counter (0/1)
SchedulesActive = "schedules:active"
// schedulesTotal fires, if a schedules is enabled. Parameter: total number of schedules
SchedulesTotal = "schedules:total"
) )
//nolint:gochecknoglobals //nolint:gochecknoglobals

8
go.mod
View File

@ -6,7 +6,7 @@ require (
github.com/abice/go-enum v0.6.0 github.com/abice/go-enum v0.6.0
github.com/alicebob/miniredis/v2 v2.32.1 github.com/alicebob/miniredis/v2 v2.32.1
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef
github.com/avast/retry-go/v4 v4.5.1 github.com/avast/retry-go/v4 v4.6.0
github.com/creasty/defaults v1.7.0 github.com/creasty/defaults v1.7.0
github.com/go-chi/chi/v5 v5.0.12 github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
@ -17,10 +17,10 @@ require (
github.com/hashicorp/golang-lru v1.0.2 github.com/hashicorp/golang-lru v1.0.2
github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-colorable v0.1.13
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/dns v1.1.58 github.com/miekg/dns v1.1.59
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0
github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.32.0 github.com/onsi/gomega v1.33.0
github.com/prometheus/client_golang v1.19.0 github.com/prometheus/client_golang v1.19.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
@ -38,7 +38,7 @@ require (
github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/ThinkChaos/parcour v0.0.0-20230710171753-fbf917c9eaef github.com/ThinkChaos/parcour v0.0.0-20230710171753-fbf917c9eaef
github.com/deepmap/oapi-codegen v1.16.2 github.com/deepmap/oapi-codegen v1.16.2
github.com/docker/docker v26.0.1+incompatible github.com/docker/docker v26.1.0+incompatible
github.com/docker/go-connections v0.5.0 github.com/docker/go-connections v0.5.0
github.com/dosgo/zigtool v0.0.0-20210923085854-9c6fc1d62198 github.com/dosgo/zigtool v0.0.0-20210923085854-9c6fc1d62198
github.com/oapi-codegen/runtime v1.1.1 github.com/oapi-codegen/runtime v1.1.1

16
go.sum
View File

@ -29,8 +29,8 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7D
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM= github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef h1:2JGTg6JapxP9/R33ZaagQtAM4EkkSYnIAlOG5EI8gkM=
github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII= github.com/asaskevich/EventBus v0.0.0-20200907212545-49d423059eef/go.mod h1:JS7hed4L1fj0hXcyEejnW57/7LCetXggd+vwrRnYeII=
github.com/avast/retry-go/v4 v4.5.1 h1:AxIx0HGi4VZ3I02jr78j5lZ3M6x1E0Ivxa6b0pUUh7o= github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA=
github.com/avast/retry-go/v4 v4.5.1/go.mod h1:/sipNsvNB3RRuT5iNcb6h73nw3IBmXJ/H3XrCQYSOpc= github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
@ -64,8 +64,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v26.0.1+incompatible h1:t39Hm6lpXuXtgkF0dm1t9a5HkbUfdGy6XbWexmGr+hA= github.com/docker/docker v26.1.0+incompatible h1:W1G9MPNbskA6VZWL7b3ZljTh0pXI68FpINx0GKaOdaM=
github.com/docker/docker v26.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@ -193,8 +193,8 @@ github.com/mattn/goveralls v0.0.12 h1:PEEeF0k1SsTjOBQ8FOmrOAoCu4ytuMaWCnWe94zxbC
github.com/mattn/goveralls v0.0.12/go.mod h1:44ImGEUfmqH8bBtaMrYKsM65LXfNLWmwaxFGjZwgMSQ= github.com/mattn/goveralls v0.0.12/go.mod h1:44ImGEUfmqH8bBtaMrYKsM65LXfNLWmwaxFGjZwgMSQ=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -225,8 +225,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=

View File

@ -15,6 +15,7 @@ import (
func RegisterEventListeners() { func RegisterEventListeners() {
registerBlockingEventListeners() registerBlockingEventListeners()
registerCachingEventListeners() registerCachingEventListeners()
registerSchedulesEventListeners()
registerApplicationEventListeners() registerApplicationEventListeners()
} }
@ -38,6 +39,22 @@ func versionNumberGauge() *prometheus.GaugeVec {
return denylistCnt return denylistCnt
} }
func registerSchedulesEventListeners() {
schedulesActiveGaugeVec := schedulesActiveGaugeVec()
schedulesTotalGauge := schedulesTotalGauge()
RegisterMetric(schedulesActiveGaugeVec)
RegisterMetric(schedulesTotalGauge)
subscribe(evt.SchedulesActive, func(group string, activeCnt int) {
schedulesActiveGaugeVec.With(prometheus.Labels{"group": group}).Set(float64(activeCnt))
})
subscribe(evt.SchedulesTotal, func(cnt int) {
schedulesTotalGauge.Set(float64(cnt))
})
}
func registerBlockingEventListeners() { func registerBlockingEventListeners() {
enabledGauge := enabledGauge() enabledGauge := enabledGauge()
@ -83,6 +100,24 @@ func enabledGauge() prometheus.Gauge {
return enabledGauge return enabledGauge
} }
func schedulesActiveGaugeVec() *prometheus.GaugeVec {
schedulesActiveGaugeVec := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "blocky_schedules_active",
Help: "Active schedules",
}, []string{"group"})
return schedulesActiveGaugeVec
}
func schedulesTotalGauge() prometheus.Gauge {
schedulesTotal := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "blocky_schedules_total",
Help: "Total number of schedules",
})
return schedulesTotal
}
func denylistGauge() *prometheus.GaugeVec { func denylistGauge() *prometheus.GaugeVec {
denylistCnt := prometheus.NewGaugeVec( denylistCnt := prometheus.NewGaugeVec(
prometheus.GaugeOpts{ prometheus.GaugeOpts{

View File

@ -131,6 +131,13 @@ func NewBlockingResolver(ctx context.Context,
cfg.Loading, cfg.Allowlists, downloader) cfg.Loading, cfg.Allowlists, downloader)
allowlistOnlyGroups := determineAllowlistOnlyGroups(&cfg) allowlistOnlyGroups := determineAllowlistOnlyGroups(&cfg)
schedulesCount := len(cfg.Schedules)
if schedulesCount > 0 {
evt.Bus().Publish(evt.SchedulesTotal, schedulesCount)
go cfg.Schedules.Refresh(ctx, nil)
}
err = multierror.Append(err, blErr, wlErr).ErrorOrNil() err = multierror.Append(err, blErr, wlErr).ErrorOrNil()
if err != nil { if err != nil {
return nil, err return nil, err
@ -386,6 +393,23 @@ func (r *BlockingResolver) handleDenylist(ctx context.Context, groupsToCheck []s
} }
if groups := r.matches(groupsToCheck, r.denylistMatcher, domain); len(groups) > 0 { if groups := r.matches(groupsToCheck, r.denylistMatcher, domain); len(groups) > 0 {
for group := range r.cfg.Schedules {
if slices.Contains(groups, group) {
if r.cfg.Schedules.IsActive(group, time.Now) {
resp, err := r.handleBlocked(logger, request, question, fmt.Sprintf("BLOCKED (SCHEDULE %s active)",
strings.Join(groups, ",")))
return true, resp, err
}
logger.WithField("groups", groups).Infof("domain is in an inactive schedule")
resp, err := r.next.Resolve(ctx, request)
return true, resp, err
}
}
resp, err := r.handleBlocked(logger, request, question, fmt.Sprintf("BLOCKED (%s)", strings.Join(groups, ","))) resp, err := r.handleBlocked(logger, request, question, fmt.Sprintf("BLOCKED (%s)", strings.Join(groups, ",")))
return true, resp, err return true, resp, err

View File

@ -1,5 +1,10 @@
package trie package trie
import (
"github.com/0xERR0R/blocky/log"
"strings"
)
// Trie stores a set of strings and can quickly check // Trie stores a set of strings and can quickly check
// if it contains an element, or one of its parents. // if it contains an element, or one of its parents.
// //
@ -108,8 +113,12 @@ func (n *parent) insert(key string, split SplitFunc) {
} }
func (n *parent) hasParentOf(key string, split SplitFunc) bool { func (n *parent) hasParentOf(key string, split SplitFunc) bool {
searchString := key
rule := ""
for { for {
label, rest := split(key) label, rest := split(key)
rule = strings.Join([]string{label, rule}, ".")
child, ok := n.children[label] child, ok := n.children[label]
if !ok { if !ok {
@ -132,7 +141,14 @@ func (n *parent) hasParentOf(key string, split SplitFunc) bool {
case terminal: case terminal:
// Continue down the trie // Continue down the trie
return child.hasParentOf(rest, split) matched := child.hasParentOf(rest, split)
if matched {
rule = strings.Join([]string{child.String(), rule}, ".")
rule = strings.Trim(rule, ".")
log.PrefixedLog("trie").Debugf("wildcard block rule '%s' matched with '%s'", rule, searchString)
}
return matched
} }
} }
} }