Skip to content

Commit a52a9dd

Browse files
committed
added hosts list functionality
1 parent 02aa7a0 commit a52a9dd

File tree

6 files changed

+174
-6
lines changed

6 files changed

+174
-6
lines changed

.testdata/hosts.list

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# This is a comment
2+
! This is a comment
3+
; This is a comment
4+
5+
0.0.0.0 example.com
6+
0.0.0.0 example.net
7+
0.0.0.0 example.com
8+
0.0.0.0 noop example.com

README.md

+18-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ The *filter* plugin is used to block domain name resolution, simliar to
1515
```nginx
1616
filter {
1717
ACTION TYPE DATA
18-
ACTION list TYPE DATA
1918
}
2019
```
2120

@@ -29,8 +28,23 @@ filter {
2928
* Adblock Plus: `||example.com^`
3029
* DNSMasq Address: `address=/example.com/#`
3130

32-
If the **ACTION** is a `list`, then **DATA** is a `[ file | http | https ]` URL.
33-
Lists must contain only the **TYPE** specified.
31+
```nginx
32+
filter {
33+
ACTION list TYPE DATA
34+
}
35+
```
36+
37+
* **ACTION**: `[ allow | block ]` What action to take
38+
* **DATA**: Lists of the following data types
39+
* `domain`: A raw domain to match. Subdomains are not matched
40+
* `hosts`: A hostsfile formatted list
41+
* `regex`: A Go-formatted Regular Expression
42+
* `wildcard`: Common wildcard formats
43+
* Generic: `*.example.com`
44+
* Adblock Plus: `||example.com^`
45+
* DNSMasq Address: `address=/example.com/#`
46+
* **DATA**: A `[ file | http | https ]` URL. Must contain only the **TYPE**
47+
specified.
3448

3549
```nginx
3650
filter {
@@ -88,7 +102,7 @@ filter {
88102

89103
This flexibility is extended to wildcard lists as well. AdblockPlus and DNSMasq
90104
formats are supported for flexibility and ease of migration from other
91-
solutions. Hosts, zone, and Unbound configuration files are not supported.
105+
solutions. Zone and Unbound configuration files are not supported.
92106

93107
## Examples
94108

action.go

+54-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ type ActionConfig struct {
4141
regex map[string]*regexp.Regexp
4242

4343
domainLists ActionList
44+
hostsLists ActionList
4445
regexLists ActionList
4546
wildcardLists ActionList
47+
48+
hostsRegexp *regexp.Regexp
4649
}
4750

4851
// NewActionConfig returns an action ready to accept configurations
@@ -52,8 +55,10 @@ func NewActionConfig(action ActionType) ActionConfig {
5255
domains: make(map[string]bool),
5356
regex: make(map[string]*regexp.Regexp),
5457
domainLists: make(ActionList),
58+
hostsLists: make(ActionList),
5559
regexLists: make(ActionList),
5660
wildcardLists: make(ActionList),
61+
hostsRegexp: regexp.MustCompile(`\s+|\t+`),
5762
}
5863
}
5964

@@ -76,6 +81,18 @@ func (a ActionConfig) AddDomainList(url string) error {
7681
return nil
7782
}
7883

84+
// AddHostsList to match contents
85+
func (a ActionConfig) AddHostsList(url string) error {
86+
if _, ok := a.hostsLists[url]; !ok {
87+
loadFunc, err := GetListLoadFunc(url)
88+
if err != nil {
89+
return err
90+
}
91+
a.hostsLists[url] = loadFunc
92+
}
93+
return nil
94+
}
95+
7996
// AddRegex to match
8097
func (a ActionConfig) AddRegex(expr string) error {
8198
comp, err := regexp.Compile(expr)
@@ -141,6 +158,13 @@ func (a ActionConfig) BuildDomains() map[string]bool {
141158
domains[k] = true
142159
}
143160

161+
a.buildDomainsLists(domains)
162+
a.buildDomainHostsLists(domains)
163+
164+
return domains
165+
}
166+
167+
func (a ActionConfig) buildDomainsLists(domains map[string]bool) {
144168
for dom, load := range a.domainLists {
145169
file, err := load(dom)
146170
if err != nil {
@@ -163,15 +187,43 @@ func (a ActionConfig) BuildDomains() map[string]bool {
163187
}
164188
if bytes.Contains(line, []byte(" ")) {
165189
// Skip formats that contain whitespace characters
166-
// Host files should use the stock 'hosts' plugin
167190
// Zone files should use the stock 'file' plugin
168191
continue
169192
}
170193
domains[string(line)] = true
171194
}
172195
}
196+
}
173197

174-
return domains
198+
func (a ActionConfig) buildDomainHostsLists(domains map[string]bool) {
199+
for dom, load := range a.hostsLists {
200+
file, err := load(dom)
201+
if err != nil {
202+
log.Errorf(
203+
"there was a problem fetching %s hosts list %q; %s",
204+
a.configType,
205+
dom,
206+
err,
207+
)
208+
continue
209+
}
210+
defer file.Close()
211+
scanner := bufio.NewScanner(file)
212+
scanner.Split(bufio.ScanLines)
213+
for scanner.Scan() {
214+
line := scanner.Bytes()
215+
line = bytes.TrimSpace(line)
216+
if a.shouldSkip(line) {
217+
continue
218+
}
219+
line = a.hostsRegexp.ReplaceAll(line, []byte(" "))
220+
hostsLine := strings.Split(string(line), " ")
221+
if len(hostsLine) != 2 {
222+
continue
223+
}
224+
domains[hostsLine[1]] = true
225+
}
226+
}
175227
}
176228

177229
// BuildRegExps consolidates individual regular expressions then loads and

action_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ func TestActionList404(t *testing.T) {
99
block list domain https://httpbin.org/status/404
1010
block list regex https://httpbin.org/status/404
1111
block list wildcard https://httpbin.org/noop
12+
block list hosts https://httpbin.org/status/404
1213
}`
1314
filter := NewTestFilter(t, corefile)
1415
filter.Build()
@@ -43,6 +44,20 @@ func TestActionListDomain(t *testing.T) {
4344
}
4445
}
4546

47+
func TestActionListHosts(t *testing.T) {
48+
corefile := `filter {
49+
block list hosts file://.testdata/hosts.list
50+
}`
51+
filter := NewTestFilter(t, corefile)
52+
filter.Build()
53+
if len(filter.blockDomains) != 2 {
54+
t.Errorf(
55+
"expected two domains; found %d",
56+
len(filter.blockDomains),
57+
)
58+
}
59+
}
60+
4661
func TestActionListRegex(t *testing.T) {
4762
corefile := `filter {
4863
block list regex file://.testdata/regex.list

setup.go

+21
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ func parseActionList(c *caddy.Controller, f *Filter, a ActionType) error {
172172
if err := parseActionListDomain(c, f, a); err != nil {
173173
return err
174174
}
175+
case "hosts":
176+
if err := parseActionListHosts(c, f, a); err != nil {
177+
return err
178+
}
175179
case "regex":
176180
if err := parseActionListRegex(c, f, a); err != nil {
177181
return err
@@ -208,6 +212,23 @@ func parseActionListDomain(c *caddy.Controller, f *Filter, a ActionType) error {
208212
return ensureEOL(c)
209213
}
210214

215+
func parseActionListHosts(c *caddy.Controller, f *Filter, a ActionType) error {
216+
if !c.NextArg() {
217+
return c.Errf("no %s hosts list specified", a)
218+
}
219+
switch a {
220+
case ActionTypeAllow:
221+
if err := f.allowConfig.AddHostsList(c.Val()); err != nil {
222+
return err
223+
}
224+
case ActionTypeBlock:
225+
if err := f.blockConfig.AddHostsList(c.Val()); err != nil {
226+
return err
227+
}
228+
}
229+
return ensureEOL(c)
230+
}
231+
211232
func parseActionListRegex(c *caddy.Controller, f *Filter, a ActionType) error {
212233
if !c.NextArg() {
213234
return c.Errf("no %s regex list specified", a)

setup_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,35 @@ func TestAllowList(t *testing.T) {
351351
}`,
352352
false,
353353
},
354+
{
355+
"allow list hosts no url",
356+
`filter {
357+
allow list hosts
358+
}`,
359+
true,
360+
},
361+
{
362+
"allow list hosts invalid url",
363+
`filter {
364+
allow list hosts "file://invalid url"
365+
}`,
366+
true,
367+
},
368+
{
369+
"allow list hosts from file",
370+
`filter {
371+
allow list hosts file://.testdata/hosts.list
372+
}`,
373+
false,
374+
},
375+
{
376+
"allow list hosts duplicate",
377+
`filter {
378+
allow list hosts file://.testdata/hosts.list
379+
allow list hosts file://.testdata/hosts.list
380+
}`,
381+
false,
382+
},
354383
{
355384
"allow list regex no url",
356385
`filter {
@@ -500,6 +529,35 @@ func TestBlockList(t *testing.T) {
500529
}`,
501530
false,
502531
},
532+
{
533+
"block list hosts no url",
534+
`filter {
535+
block list hosts
536+
}`,
537+
true,
538+
},
539+
{
540+
"block list hosts invalid url",
541+
`filter {
542+
block list hosts "file://invalid url"
543+
}`,
544+
true,
545+
},
546+
{
547+
"block list hosts from file",
548+
`filter {
549+
block list hosts file://.testdata/hosts.list
550+
}`,
551+
false,
552+
},
553+
{
554+
"block list hosts duplicate",
555+
`filter {
556+
block list hosts file://.testdata/hosts.list
557+
block list hosts file://.testdata/hosts.list
558+
}`,
559+
false,
560+
},
503561
{
504562
"block list regex no url",
505563
`filter {

0 commit comments

Comments
 (0)