-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathblocked.go
More file actions
261 lines (208 loc) · 6.6 KB
/
blocked.go
File metadata and controls
261 lines (208 loc) · 6.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package filtering
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"slices"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/rulelist"
"github.com/AdguardTeam/AdGuardHome/internal/schedule"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/validate"
"github.com/AdguardTeam/urlfilter/rules"
)
// serviceRules maps a service ID to its filtering rules.
var serviceRules map[string][]*rules.NetworkRule
// serviceIDs contains service IDs sorted alphabetically.
var serviceIDs []string
// initBlockedServices initializes package-level blocked service data. l must
// not be nil.
func initBlockedServices(ctx context.Context, l *slog.Logger) {
svcLen := len(blockedServices)
serviceIDs = make([]string, svcLen)
serviceRules = make(map[string][]*rules.NetworkRule, svcLen)
for i, s := range blockedServices {
netRules := make([]*rules.NetworkRule, 0, len(s.Rules))
for _, text := range s.Rules {
rule, err := rules.NewNetworkRule(text, rulelist.IDBlockedService)
if err == nil {
netRules = append(netRules, rule)
continue
}
l.ErrorContext(
ctx,
"parsing blocked service rule",
"svc", s.ID,
"rule", text,
slogutil.KeyError, err,
)
}
serviceIDs[i] = s.ID
serviceRules[s.ID] = netRules
}
slices.Sort(serviceIDs)
l.DebugContext(ctx, "initialized services", "svc_len", svcLen)
}
// BlockedServices is the configuration of blocked services.
//
// TODO(s.chzhen): Move to a higher-level package to allow importing the client
// package into the filtering package.
type BlockedServices struct {
// Schedule is blocked services schedule for every day of the week.
Schedule *schedule.Weekly `json:"schedule" yaml:"schedule"`
// IDs is the names of blocked services.
IDs []string `json:"ids" yaml:"ids"`
}
// Clone returns a deep copy of blocked services.
func (s *BlockedServices) Clone() (c *BlockedServices) {
if s == nil {
return nil
}
return &BlockedServices{
Schedule: s.Schedule.Clone(),
IDs: slices.Clone(s.IDs),
}
}
// FilterUnknownIDs filters out unknown service IDs within s and logs them at
// warning level. It does nothing if s is nil.
func (s *BlockedServices) FilterUnknownIDs(ctx context.Context, logger *slog.Logger) {
if s == nil {
// [BlockedServices.Validate] handles this case.
return
}
s.IDs = slices.DeleteFunc(s.IDs, func(id string) (ok bool) {
_, isKnown := serviceRules[id]
if !isKnown {
logger.WarnContext(ctx, "filtered unknown service", "id", id)
}
return !isKnown
})
}
// type check
var _ validate.Interface = (*BlockedServices)(nil)
// Validate implements the [validate.Interface] interface for *BlockedServices.
func (s *BlockedServices) Validate() (err error) {
if s == nil {
return errors.ErrNoValue
}
var errs []error
for _, id := range s.IDs {
_, ok := serviceRules[id]
if !ok {
errs = append(errs, fmt.Errorf("unknown blocked-service %q", id))
}
}
return errors.Join(errs...)
}
// ApplyBlockedServices - set blocked services settings for this DNS request
func (d *DNSFilter) ApplyBlockedServices(setts *Settings) {
d.confMu.RLock()
defer d.confMu.RUnlock()
setts.ServicesRules = []ServiceEntry{}
bsvc := d.conf.BlockedServices
// TODO(s.chzhen): Use startTime from [dnsforward.dnsContext].
if !bsvc.Schedule.Contains(time.Now()) {
d.ApplyBlockedServicesList(setts, bsvc.IDs)
}
}
// ApplyBlockedServicesList appends filtering rules to the settings.
func (d *DNSFilter) ApplyBlockedServicesList(setts *Settings, list []string) {
for _, name := range list {
rules, ok := serviceRules[name]
if !ok {
d.logger.ErrorContext(context.TODO(), "unknown service name", "name", name)
continue
}
setts.ServicesRules = append(setts.ServicesRules, ServiceEntry{
Name: name,
Rules: rules,
})
}
}
func (d *DNSFilter) handleBlockedServicesIDs(w http.ResponseWriter, r *http.Request) {
aghhttp.WriteJSONResponseOK(r.Context(), d.logger, w, r, serviceIDs)
}
func (d *DNSFilter) handleBlockedServicesAll(w http.ResponseWriter, r *http.Request) {
aghhttp.WriteJSONResponseOK(r.Context(), d.logger, w, r, struct {
BlockedServices []blockedService `json:"blocked_services"`
ServiceGroups []serviceGroup `json:"groups"`
}{
BlockedServices: blockedServices,
ServiceGroups: serviceGroups,
})
}
// handleBlockedServicesList is the handler for the GET
// /control/blocked_services/list HTTP API.
//
// Deprecated: Use handleBlockedServicesGet.
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
var list []string
func() {
d.confMu.Lock()
defer d.confMu.Unlock()
list = d.conf.BlockedServices.IDs
}()
aghhttp.WriteJSONResponseOK(r.Context(), d.logger, w, r, list)
}
// handleBlockedServicesSet is the handler for the POST
// /control/blocked_services/set HTTP API.
//
// Deprecated: Use handleBlockedServicesUpdate.
func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
list := []string{}
err := json.NewDecoder(r.Body).Decode(&list)
if err != nil {
aghhttp.ErrorAndLog(ctx, d.logger, r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
func() {
d.confMu.Lock()
defer d.confMu.Unlock()
d.conf.BlockedServices.IDs = list
d.logger.DebugContext(ctx, "updated blocked services list", "len", len(list))
}()
d.conf.ConfModifier.Apply(ctx)
}
// handleBlockedServicesGet is the handler for the GET
// /control/blocked_services/get HTTP API.
func (d *DNSFilter) handleBlockedServicesGet(w http.ResponseWriter, r *http.Request) {
var bsvc *BlockedServices
func() {
d.confMu.RLock()
defer d.confMu.RUnlock()
bsvc = d.conf.BlockedServices.Clone()
}()
aghhttp.WriteJSONResponseOK(r.Context(), d.logger, w, r, bsvc)
}
// handleBlockedServicesUpdate is the handler for the PUT
// /control/blocked_services/update HTTP API.
func (d *DNSFilter) handleBlockedServicesUpdate(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
l := d.logger
bsvc := &BlockedServices{}
err := json.NewDecoder(r.Body).Decode(bsvc)
if err != nil {
aghhttp.ErrorAndLog(ctx, l, r, w, http.StatusBadRequest, "json.Decode: %s", err)
return
}
err = bsvc.Validate()
if err != nil {
aghhttp.ErrorAndLog(ctx, l, r, w, http.StatusUnprocessableEntity, "validating: %s", err)
return
}
if bsvc.Schedule == nil {
bsvc.Schedule = schedule.EmptyWeekly()
}
func() {
d.confMu.Lock()
defer d.confMu.Unlock()
d.conf.BlockedServices = bsvc
}()
l.DebugContext(ctx, "updated blocked services schedule", "len", len(bsvc.IDs))
d.conf.ConfModifier.Apply(ctx)
}