This commit is contained in:
Pascal Gauthier 2024-04-26 08:43:37 -04:00 committed by GitHub
commit 7b9c83ecd2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 760 additions and 7 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
- 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
- Regex support
- Blocking of request domain, response CNAME (deep CNAME inspection) and response IP addresses (against IP lists)

View File

@ -11,6 +11,7 @@ type Blocking struct {
Denylists map[string][]BytesSource `yaml:"denylists"`
Allowlists map[string][]BytesSource `yaml:"allowlists"`
ClientGroupsBlock map[string][]string `yaml:"clientGroupsBlock"`
Schedules Schedules `yaml:"schedules"`
BlockType string `yaml:"blockType" default:"ZEROIP"`
BlockTTL Duration `yaml:"blockTTL" default:"6h"`
Loading SourceLoading `yaml:"loading"`
@ -80,6 +81,26 @@ func (c *Blocking) LogConfig(logger *logrus.Entry) {
log.WithIndent(logger, " ", func(logger *logrus.Entry) {
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) {

View File

@ -14,6 +14,8 @@ var _ = Describe("BlockingConfig", func() {
suiteBeforeEach()
BeforeEach(func() {
hr, _ := parseHoursRange("09:00-17:00")
cfg = Blocking{
BlockType: "ZEROIP",
BlockTTL: Duration(time.Minute),
@ -23,6 +25,15 @@ var _ = Describe("BlockingConfig", func() {
ClientGroupsBlock: map[string][]string{
"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.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")))
})
})

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
special:
- 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.
# 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.
@ -89,6 +91,16 @@ blocking:
allowlistdomain.com
# this is a regex
/^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
clientGroupsBlock:
# default will be used, if no special definition for a client name exists
@ -101,6 +113,7 @@ blocking:
- ads
192.168.178.1/24:
- special
- youtube
# which response will be sent, if query is blocked:
# zeroIp: 0.0.0.0 will be returned (default)
# 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.
### Upstream Groups
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
```
## Bootstrap DNS configuration
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).
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:
* `$ORIGIN` - sets the origin for relative domain names
* `$TTL` - sets the default TTL for records in the zone
* `$INCLUDE` - includes another zone file relative to the blocky executable
* `$GENERATE` - generates a range of records
- `$ORIGIN` - sets the origin for relative domain names
- `$TTL` - sets the default TTL for records in the zone
- `$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
resolver lookup is performed.
@ -455,6 +454,54 @@ The supported list formats are:
!!! warning
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
You can use wildcards to block a domain and all its subdomains.
@ -735,7 +782,6 @@ Configuration parameters:
strategy: fast
```
## Deliver EDE codes as EDNS0 option
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) {
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)).
Should(
SatisfyAll(

View File

@ -34,6 +34,12 @@ const (
// ApplicationStarted fires on start of the application. Parameter: version number, build time
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

View File

@ -15,6 +15,7 @@ import (
func RegisterEventListeners() {
registerBlockingEventListeners()
registerCachingEventListeners()
registerSchedulesEventListeners()
registerApplicationEventListeners()
}
@ -38,6 +39,22 @@ func versionNumberGauge() *prometheus.GaugeVec {
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() {
enabledGauge := enabledGauge()
@ -83,6 +100,24 @@ func enabledGauge() prometheus.Gauge {
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 {
denylistCnt := prometheus.NewGaugeVec(
prometheus.GaugeOpts{

View File

@ -131,6 +131,13 @@ func NewBlockingResolver(ctx context.Context,
cfg.Loading, cfg.Allowlists, downloader)
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()
if err != nil {
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 {
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, ",")))
return true, resp, err