mirror of
https://github.com/folbricht/routedns.git
synced 2025-12-19 08:29:50 -06:00
Support naming blocklists to help with logging (#201)
* Support naming blocklists to help with logging * Support naming of lists in response blocklists too * Add list name to client-blocklist as well
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Blocklist is a resolver that returns NXDOMAIN or a spoofed IP for every query that
|
||||
@@ -92,8 +93,8 @@ func (r *Blocklist) Resolve(q *dns.Msg, ci ClientInfo) (*dns.Msg, error) {
|
||||
|
||||
// Forward to upstream or the optional allowlist-resolver immediately if there's a match in the allowlist
|
||||
if allowlistDB != nil {
|
||||
if _, _, rule, ok := allowlistDB.Match(question); ok {
|
||||
log = log.WithField("rule", rule)
|
||||
if _, _, match, ok := allowlistDB.Match(question); ok {
|
||||
log = log.WithFields(logrus.Fields{"list": match.List, "rule": match.Rule})
|
||||
r.metrics.allowed.Add(1)
|
||||
if r.AllowListResolver != nil {
|
||||
log.WithField("resolver", r.AllowListResolver.String()).Debug("matched allowlist, forwarding")
|
||||
@@ -104,14 +105,14 @@ func (r *Blocklist) Resolve(q *dns.Msg, ci ClientInfo) (*dns.Msg, error) {
|
||||
}
|
||||
}
|
||||
|
||||
ip, name, rule, ok := blocklistDB.Match(question)
|
||||
ip, name, match, ok := blocklistDB.Match(question)
|
||||
if !ok {
|
||||
// Didn't match anything, pass it on to the next resolver
|
||||
log.WithField("resolver", r.resolver.String()).Debug("forwarding unmodified query to resolver")
|
||||
r.metrics.allowed.Add(1)
|
||||
return r.resolver.Resolve(q, ci)
|
||||
}
|
||||
log = log.WithField("rule", rule)
|
||||
log = log.WithFields(logrus.Fields{"list": match.List, "rule": match.Rule})
|
||||
r.metrics.blocked.Add(1)
|
||||
|
||||
// If we got a name for the PTR query, respond to it
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestBlocklistRegexp(t *testing.T) {
|
||||
`(^|\.)block\.test`,
|
||||
`(^|\.)evil\.test`,
|
||||
})
|
||||
m, err := NewRegexpDB(loader)
|
||||
m, err := NewRegexpDB("testlist", loader)
|
||||
require.NoError(t, err)
|
||||
|
||||
opt := BlocklistOptions{
|
||||
@@ -51,9 +51,9 @@ func TestBlocklistAllow(t *testing.T) {
|
||||
allowloader := NewStaticLoader([]string{
|
||||
`(^|\.)good\.evil\.test`,
|
||||
})
|
||||
blockDB, err := NewRegexpDB(blockloader)
|
||||
blockDB, err := NewRegexpDB("testlist", blockloader)
|
||||
require.NoError(t, err)
|
||||
allowDB, err := NewRegexpDB(allowloader)
|
||||
allowDB, err := NewRegexpDB("testlist", allowloader)
|
||||
require.NoError(t, err)
|
||||
|
||||
opt := BlocklistOptions{
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
// .domain.com: matches domain.com and all subdomains
|
||||
// *.domain.com: matches all subdomains but not domain.com
|
||||
type DomainDB struct {
|
||||
name string
|
||||
root node
|
||||
loader BlocklistLoader
|
||||
}
|
||||
@@ -23,7 +24,7 @@ type node map[string]node
|
||||
var _ BlocklistDB = &DomainDB{}
|
||||
|
||||
// NewDomainDB returns a new instance of a matcher for a list of regular expressions.
|
||||
func NewDomainDB(loader BlocklistLoader) (*DomainDB, error) {
|
||||
func NewDomainDB(name string, loader BlocklistLoader) (*DomainDB, error) {
|
||||
rules, err := loader.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -55,14 +56,14 @@ func NewDomainDB(loader BlocklistLoader) (*DomainDB, error) {
|
||||
n = subNode
|
||||
}
|
||||
}
|
||||
return &DomainDB{root, loader}, nil
|
||||
return &DomainDB{name, root, loader}, nil
|
||||
}
|
||||
|
||||
func (m *DomainDB) Reload() (BlocklistDB, error) {
|
||||
return NewDomainDB(m.loader)
|
||||
return NewDomainDB(m.name, m.loader)
|
||||
}
|
||||
|
||||
func (m *DomainDB) Match(q dns.Question) (net.IP, string, string, bool) {
|
||||
func (m *DomainDB) Match(q dns.Question) (net.IP, string, *BlocklistMatch, bool) {
|
||||
s := strings.TrimSuffix(q.Name, ".")
|
||||
var matched []string
|
||||
parts := strings.Split(s, ".")
|
||||
@@ -71,18 +72,36 @@ func (m *DomainDB) Match(q dns.Question) (net.IP, string, string, bool) {
|
||||
part := parts[i]
|
||||
subNode, ok := n[part]
|
||||
if !ok {
|
||||
return nil, "", "", false
|
||||
return nil, "", nil, false
|
||||
}
|
||||
matched = append(matched, part)
|
||||
if _, ok := subNode[""]; ok { // exact and sub-domain match
|
||||
return nil, "", matchedDomainParts(".", matched), true
|
||||
return nil,
|
||||
"",
|
||||
&BlocklistMatch{
|
||||
List: m.name,
|
||||
Rule: matchedDomainParts(".", matched),
|
||||
},
|
||||
true
|
||||
}
|
||||
if _, ok := subNode["*"]; ok && i > 0 { // wildcard match on sub-domains
|
||||
return nil, "", matchedDomainParts("*.", matched), true
|
||||
return nil,
|
||||
"",
|
||||
&BlocklistMatch{
|
||||
List: m.name,
|
||||
Rule: matchedDomainParts("*.", matched),
|
||||
},
|
||||
true
|
||||
}
|
||||
n = subNode
|
||||
}
|
||||
return nil, "", matchedDomainParts("", matched), len(n) == 0 // exact match
|
||||
return nil,
|
||||
"",
|
||||
&BlocklistMatch{
|
||||
List: m.name,
|
||||
Rule: matchedDomainParts("", matched),
|
||||
},
|
||||
len(n) == 0 // exact match
|
||||
}
|
||||
|
||||
func (m *DomainDB) String() string {
|
||||
|
||||
@@ -18,7 +18,7 @@ func TestDomainDB(t *testing.T) {
|
||||
".domain4.com",
|
||||
})
|
||||
|
||||
m, err := NewDomainDB(loader)
|
||||
m, err := NewDomainDB("testlist", loader)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
@@ -61,7 +61,7 @@ func TestDomainDBError(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
loader := NewStaticLoader([]string{test.name})
|
||||
_, err := NewDomainDB(loader)
|
||||
_, err := NewDomainDB("testlist", loader)
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
// IP4 and IP6 records can be spoofed independently, however it's not possible to block only one type. If
|
||||
// IP4 is given but no IP6, then a domain match will still result in an NXDOMAIN for the IP6 address.
|
||||
type HostsDB struct {
|
||||
name string
|
||||
filters map[string]ipRecords
|
||||
ptrMap map[string]string // PTR lookup map
|
||||
loader BlocklistLoader
|
||||
@@ -24,7 +25,7 @@ type ipRecords struct {
|
||||
var _ BlocklistDB = &HostsDB{}
|
||||
|
||||
// NewHostsDB returns a new instance of a matcher for a list of regular expressions.
|
||||
func NewHostsDB(loader BlocklistLoader) (*HostsDB, error) {
|
||||
func NewHostsDB(name string, loader BlocklistLoader) (*HostsDB, error) {
|
||||
rules, err := loader.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -69,24 +70,36 @@ func NewHostsDB(loader BlocklistLoader) (*HostsDB, error) {
|
||||
}
|
||||
ptrMap[reverseAddr] = names[0]
|
||||
}
|
||||
return &HostsDB{filters, ptrMap, loader}, nil
|
||||
return &HostsDB{name, filters, ptrMap, loader}, nil
|
||||
}
|
||||
|
||||
func (m *HostsDB) Reload() (BlocklistDB, error) {
|
||||
return NewHostsDB(m.loader)
|
||||
return NewHostsDB(m.name, m.loader)
|
||||
}
|
||||
|
||||
func (m *HostsDB) Match(q dns.Question) (net.IP, string, string, bool) {
|
||||
func (m *HostsDB) Match(q dns.Question) (net.IP, string, *BlocklistMatch, bool) {
|
||||
if q.Qtype == dns.TypePTR {
|
||||
name, ok := m.ptrMap[q.Name]
|
||||
return nil, name, "", ok
|
||||
return nil, name, nil, ok
|
||||
}
|
||||
name := strings.TrimSuffix(q.Name, ".")
|
||||
ips, ok := m.filters[name]
|
||||
if q.Qtype == dns.TypeA {
|
||||
return ips.ip4, "", ips.ip4.String() + " " + name, ok
|
||||
return ips.ip4,
|
||||
"",
|
||||
&BlocklistMatch{
|
||||
List: m.name,
|
||||
Rule: ips.ip4.String() + " " + name,
|
||||
},
|
||||
ok
|
||||
}
|
||||
return ips.ip6, "", ips.ip6.String() + " " + name, ok
|
||||
return ips.ip6,
|
||||
"",
|
||||
&BlocklistMatch{
|
||||
List: m.name,
|
||||
Rule: ips.ip6.String() + " " + name,
|
||||
},
|
||||
ok
|
||||
}
|
||||
|
||||
func (m *HostsDB) String() string {
|
||||
|
||||
@@ -20,7 +20,7 @@ func TestHostsDB(t *testing.T) {
|
||||
"192.168.1.1 domain6.com",
|
||||
})
|
||||
|
||||
m, err := NewHostsDB(loader)
|
||||
m, err := NewHostsDB("testlist", loader)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
@@ -39,8 +39,9 @@ func TestHostsDB(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
q := dns.Question{Name: test.q, Qtype: test.typ, Qclass: dns.ClassINET}
|
||||
ip, _, _, ok := m.Match(q)
|
||||
ip, _, match, ok := m.Match(q)
|
||||
require.Equal(t, test.match, ok, "query: %s", test.q)
|
||||
require.Equal(t, test.ip, ip, "query: %s", test.q)
|
||||
require.Equal(t, "testlist", match.List)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ func (m MultiDB) Reload() (BlocklistDB, error) {
|
||||
return NewMultiDB(newDBs...)
|
||||
}
|
||||
|
||||
func (m MultiDB) Match(q dns.Question) (net.IP, string, string, bool) {
|
||||
func (m MultiDB) Match(q dns.Question) (net.IP, string, *BlocklistMatch, bool) {
|
||||
for _, db := range m.dbs {
|
||||
if ip, name, rule, ok := db.Match(q); ok {
|
||||
return ip, name, rule, ok
|
||||
if ip, name, match, ok := db.Match(q); ok {
|
||||
return ip, name, match, ok
|
||||
}
|
||||
}
|
||||
return nil, "", "", false
|
||||
return nil, "", nil, false
|
||||
}
|
||||
|
||||
func (m MultiDB) String() string {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
// RegexpDB holds a list of regular expressions against which it evaluates DNS queries.
|
||||
type RegexpDB struct {
|
||||
name string
|
||||
rules []*regexp.Regexp
|
||||
loader BlocklistLoader
|
||||
}
|
||||
@@ -17,7 +18,7 @@ type RegexpDB struct {
|
||||
var _ BlocklistDB = &RegexpDB{}
|
||||
|
||||
// NewRegexpDB returns a new instance of a matcher for a list of regular expressions.
|
||||
func NewRegexpDB(loader BlocklistLoader) (*RegexpDB, error) {
|
||||
func NewRegexpDB(name string, loader BlocklistLoader) (*RegexpDB, error) {
|
||||
rules, err := loader.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -35,20 +36,20 @@ func NewRegexpDB(loader BlocklistLoader) (*RegexpDB, error) {
|
||||
filters = append(filters, re)
|
||||
}
|
||||
|
||||
return &RegexpDB{filters, loader}, nil
|
||||
return &RegexpDB{name, filters, loader}, nil
|
||||
}
|
||||
|
||||
func (m *RegexpDB) Reload() (BlocklistDB, error) {
|
||||
return NewRegexpDB(m.loader)
|
||||
return NewRegexpDB(m.name, m.loader)
|
||||
}
|
||||
|
||||
func (m *RegexpDB) Match(q dns.Question) (net.IP, string, string, bool) {
|
||||
func (m *RegexpDB) Match(q dns.Question) (net.IP, string, *BlocklistMatch, bool) {
|
||||
for _, rule := range m.rules {
|
||||
if rule.MatchString(q.Name) {
|
||||
return nil, "", rule.String(), true
|
||||
return nil, "", &BlocklistMatch{List: m.name, Rule: rule.String()}, true
|
||||
}
|
||||
}
|
||||
return nil, "", "", false
|
||||
return nil, "", nil, false
|
||||
}
|
||||
|
||||
func (m *RegexpDB) String() string {
|
||||
|
||||
@@ -14,7 +14,15 @@ type BlocklistDB interface {
|
||||
|
||||
// Returns true if the question matches a rule. If the IP is not nil,
|
||||
// respond with the given IP. NXDOMAIN otherwise.
|
||||
Match(q dns.Question) (net.IP, string, string, bool)
|
||||
Match(q dns.Question) (net.IP, string, *BlocklistMatch, bool)
|
||||
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
// BlocklistMatch is returned by blocklists when a match is found. It contains
|
||||
// information about what rule matched, what list it was from etc. Used mostly
|
||||
// for logging.
|
||||
type BlocklistMatch struct {
|
||||
List string // Identifier or name of the blocklist
|
||||
Rule string // Identifier for the rule that matched
|
||||
}
|
||||
|
||||
14
cidr-db.go
14
cidr-db.go
@@ -9,6 +9,7 @@ import (
|
||||
// Network ranges are stored in a trie (one for IP4 and one for IP6) to allow for
|
||||
// efficient matching
|
||||
type CidrDB struct {
|
||||
name string
|
||||
ip4, ip6 *ipBlocklistTrie
|
||||
loader BlocklistLoader
|
||||
}
|
||||
@@ -16,12 +17,13 @@ type CidrDB struct {
|
||||
var _ IPBlocklistDB = &CidrDB{}
|
||||
|
||||
// NewCidrDB returns a new instance of a matcher for a list of networks.
|
||||
func NewCidrDB(loader BlocklistLoader) (*CidrDB, error) {
|
||||
func NewCidrDB(name string, loader BlocklistLoader) (*CidrDB, error) {
|
||||
rules, err := loader.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db := &CidrDB{
|
||||
name: name,
|
||||
ip4: new(ipBlocklistTrie),
|
||||
ip6: new(ipBlocklistTrie),
|
||||
loader: loader,
|
||||
@@ -53,14 +55,16 @@ func NewCidrDB(loader BlocklistLoader) (*CidrDB, error) {
|
||||
}
|
||||
|
||||
func (m *CidrDB) Reload() (IPBlocklistDB, error) {
|
||||
return NewCidrDB(m.loader)
|
||||
return NewCidrDB(m.name, m.loader)
|
||||
}
|
||||
|
||||
func (m *CidrDB) Match(ip net.IP) (string, bool) {
|
||||
func (m *CidrDB) Match(ip net.IP) (*BlocklistMatch, bool) {
|
||||
if addr := ip.To4(); addr == nil {
|
||||
return m.ip6.hasIP(ip)
|
||||
rule, ok := m.ip6.hasIP(ip)
|
||||
return &BlocklistMatch{List: m.name, Rule: rule}, ok
|
||||
}
|
||||
return m.ip4.hasIP(ip)
|
||||
rule, ok := m.ip4.hasIP(ip)
|
||||
return &BlocklistMatch{List: m.name, Rule: rule}, ok
|
||||
}
|
||||
|
||||
func (m *CidrDB) Close() error {
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestCidrDB(t *testing.T) {
|
||||
"1.2.0.0/16",
|
||||
"2a03:2880:f101:83::0/64",
|
||||
})
|
||||
db, err := NewCidrDB(loader)
|
||||
db, err := NewCidrDB("testlist", loader)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
|
||||
@@ -49,8 +49,8 @@ func NewClientBlocklist(id string, resolver Resolver, opt ClientBlocklistOptions
|
||||
// REFUSED if the client IP is on the blocklist, or sends the query to an alternative
|
||||
// resolver if one is configured.
|
||||
func (r *ClientBlocklist) Resolve(q *dns.Msg, ci ClientInfo) (*dns.Msg, error) {
|
||||
if rule, ok := r.BlocklistDB.Match(ci.SourceIP); ok {
|
||||
log := Log.WithFields(logrus.Fields{"id": r.id, "qname": qName(q), "rule": rule, "ip": ci.SourceIP})
|
||||
if match, ok := r.BlocklistDB.Match(ci.SourceIP); ok {
|
||||
log := Log.WithFields(logrus.Fields{"id": r.id, "qname": qName(q), "list": match.List, "rule": match.Rule, "ip": ci.SourceIP})
|
||||
r.metrics.blocked.Add(1)
|
||||
if r.BlocklistResolver != nil {
|
||||
log.WithField("resolver", r.BlocklistResolver).Debug("client on blocklist, forwarding to blocklist-resolver")
|
||||
|
||||
@@ -127,6 +127,7 @@ type group struct {
|
||||
|
||||
// Block/Allowlist items for blocklist-v2
|
||||
type list struct {
|
||||
Name string
|
||||
Format string
|
||||
Source string
|
||||
CacheDir string `toml:"cache-dir"` // Where to store copies of remote blocklists for faster startup
|
||||
|
||||
@@ -323,7 +323,7 @@ func instantiateGroup(id string, g group, resolvers map[string]rdns.Resolver) er
|
||||
if len(g.Blocklist) > 0 && g.Source != "" {
|
||||
return fmt.Errorf("static blocklist can't be used with 'source' in '%s'", id)
|
||||
}
|
||||
blocklistDB, err := newBlocklistDB(list{Format: g.Format, Source: g.Source}, g.Blocklist)
|
||||
blocklistDB, err := newBlocklistDB(list{Name: id, Format: g.Format, Source: g.Source}, g.Blocklist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -347,7 +347,7 @@ func instantiateGroup(id string, g group, resolvers map[string]rdns.Resolver) er
|
||||
}
|
||||
var blocklistDB rdns.BlocklistDB
|
||||
if len(g.Blocklist) > 0 {
|
||||
blocklistDB, err = newBlocklistDB(list{Format: g.BlocklistFormat}, g.Blocklist)
|
||||
blocklistDB, err = newBlocklistDB(list{Name: id, Format: g.BlocklistFormat}, g.Blocklist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -506,7 +506,7 @@ func instantiateGroup(id string, g group, resolvers map[string]rdns.Resolver) er
|
||||
}
|
||||
var blocklistDB rdns.IPBlocklistDB
|
||||
if len(g.Blocklist) > 0 {
|
||||
blocklistDB, err = newIPBlocklistDB(list{Format: g.BlocklistFormat}, g.LocationDB, g.Blocklist)
|
||||
blocklistDB, err = newIPBlocklistDB(list{Name: id, Format: g.BlocklistFormat}, g.LocationDB, g.Blocklist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -579,7 +579,7 @@ func instantiateGroup(id string, g group, resolvers map[string]rdns.Resolver) er
|
||||
}
|
||||
var blocklistDB rdns.IPBlocklistDB
|
||||
if len(g.Blocklist) > 0 {
|
||||
blocklistDB, err = newIPBlocklistDB(list{Format: g.BlocklistFormat}, g.LocationDB, g.Blocklist)
|
||||
blocklistDB, err = newIPBlocklistDB(list{Name: id, Format: g.BlocklistFormat}, g.LocationDB, g.Blocklist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -680,6 +680,10 @@ func newBlocklistDB(l list, rules []string) (rdns.BlocklistDB, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := l.Name
|
||||
if name == "" {
|
||||
name = l.Source
|
||||
}
|
||||
var loader rdns.BlocklistLoader
|
||||
if len(rules) > 0 {
|
||||
loader = rdns.NewStaticLoader(rules)
|
||||
@@ -698,11 +702,11 @@ func newBlocklistDB(l list, rules []string) (rdns.BlocklistDB, error) {
|
||||
}
|
||||
switch l.Format {
|
||||
case "regexp", "":
|
||||
return rdns.NewRegexpDB(loader)
|
||||
return rdns.NewRegexpDB(name, loader)
|
||||
case "domain":
|
||||
return rdns.NewDomainDB(loader)
|
||||
return rdns.NewDomainDB(name, loader)
|
||||
case "hosts":
|
||||
return rdns.NewHostsDB(loader)
|
||||
return rdns.NewHostsDB(name, loader)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format '%s'", l.Format)
|
||||
}
|
||||
@@ -713,6 +717,10 @@ func newIPBlocklistDB(l list, locationDB string, rules []string) (rdns.IPBlockli
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := l.Name
|
||||
if name == "" {
|
||||
name = l.Source
|
||||
}
|
||||
var loader rdns.BlocklistLoader
|
||||
if len(rules) > 0 {
|
||||
loader = rdns.NewStaticLoader(rules)
|
||||
@@ -732,9 +740,9 @@ func newIPBlocklistDB(l list, locationDB string, rules []string) (rdns.IPBlockli
|
||||
|
||||
switch l.Format {
|
||||
case "cidr", "":
|
||||
return rdns.NewCidrDB(loader)
|
||||
return rdns.NewCidrDB(name, loader)
|
||||
case "location":
|
||||
return rdns.NewGeoIPDB(loader, locationDB)
|
||||
return rdns.NewGeoIPDB(name, loader, locationDB)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format '%s'", l.Format)
|
||||
}
|
||||
|
||||
@@ -531,7 +531,7 @@ Options:
|
||||
- `blocklist-resolver` - Alternative resolver for queries matching the blocklist, rather than responding with NXDOMAIN. Optional.
|
||||
- `blocklist-format` - The format the blocklist is provided in. Only used if `blocklist-source` is not provided. Can be `regexp`, `domain`, or `hosts`. Defaults to `regexp`.
|
||||
- `blocklist-refresh` - Time interval (in seconds) in which external (remote or local) blocklists are reloaded. Optional.
|
||||
- `blocklist-source` - An array of blocklists, each with `format` and `source`.
|
||||
- `blocklist-source` - An array of blocklists, each with `format`, `source` and optionally `name`.
|
||||
- `allowlist-resolver` - Alternative resolver for queries matching the allowlist, rather than forwarding to the default resolver.
|
||||
- `allowlist-format` - The format the allowlist is provided in. Only used if `allowlist-source` is not provided. Can be `regexp`, `domain`, or `hosts`. Defaults to `regexp`.
|
||||
- `allowlist-refresh` - Time interval (in seconds) in which external allowlists are reloaded. Optional.
|
||||
@@ -580,7 +580,7 @@ blocklist = [
|
||||
]
|
||||
```
|
||||
|
||||
Blocklist that loads two rule-sets. One from an HTTP server, the other from a file on disk. Both are reloaded once a day.
|
||||
Blocklist that loads two rule-sets. One from an HTTP server, the other from a file on disk. Both are reloaded once a day. A `name` can be provided which will be used in logs instead of `source`.
|
||||
|
||||
```toml
|
||||
[groups.cloudflare-blocklist]
|
||||
@@ -588,7 +588,7 @@ type = "blocklist-v2"
|
||||
resolvers = ["cloudflare-dot"]
|
||||
blocklist-refresh = 86400
|
||||
blocklist-source = [
|
||||
{format = "domain", source = "https://raw.githubusercontent.com/cbuijs/accomplist/master/deugniets/routedns.blocklist.domain.list"},
|
||||
{name = "cbuijs/blocklist" format = "domain", source = "https://raw.githubusercontent.com/cbuijs/accomplist/master/deugniets/routedns.blocklist.domain.list"},
|
||||
{format = "regexp", source = "/path/to/local/regexp.list"},
|
||||
]
|
||||
```
|
||||
@@ -646,7 +646,7 @@ Options:
|
||||
- For `response-blocklist-ip`, the value can be `cidr`, or `location`. Defaults to `cidr`.
|
||||
- For `response-blocklist-name`, the value can be `regexp`, `domain`, or `hosts`. Defaults to `regexp`.
|
||||
- `blocklist-refresh` - Time interval (in seconds) in which external (remote or local) blocklists are reloaded. Optional.
|
||||
- `blocklist-source` - An array of blocklists, each with `format`, `source` and optionally `cache-dir` (see notes for [Query Blockists](#Query-Blocklist)).
|
||||
- `blocklist-source` - An array of blocklists, each with `format`, `source` and optionally `cache-dir` (see notes for [Query Blockists](#Query-Blocklist)) as well as `name` which assigns a name to the list used in logs (defaults to `source`).
|
||||
- `filter` - If set to `true` in `response-blocklist-ip`, matching records will be removed from responses rather than the whole response. If there is no answer record left after applying the filter, NXDOMAIN will be returned unless an alternative `blocklist-resolver` is defined.
|
||||
- `location-db` - If location-based IP blocking is used, this specifies the GeoIP data file to load. Optional. Defaults to /usr/share/GeoIP/GeoLite2-City.mmdb
|
||||
|
||||
@@ -700,7 +700,7 @@ blocklist-source = [
|
||||
]
|
||||
```
|
||||
|
||||
Response blocklist that is cached on local disk for faster startup
|
||||
Response blocklist that is cached on local disk for faster startup. By default, logs will contain the source (in this case the URL) of a match, but different name can be specified with `name`.
|
||||
|
||||
```toml
|
||||
[groups.cloudflare-blocklist]
|
||||
@@ -708,7 +708,7 @@ type = "response-blocklist-ip"
|
||||
resolvers = ["cloudflare-dot"]
|
||||
blocklist-refresh = 86400
|
||||
blocklist-source = [
|
||||
{source = "https://host/block.cidr.txt", cache-dir="/var/tmp"},
|
||||
{name = "my-block-list", source = "https://host/block.cidr.txt", cache-dir="/var/tmp"},
|
||||
]
|
||||
```
|
||||
|
||||
@@ -744,7 +744,7 @@ Options:
|
||||
- `blocklist-resolver` - Alternative resolver for responses matching a rule, the query will be re-sent to this resolver. Optional.
|
||||
- `blocklist-format` - The format the blocklist is provided in. Only used if `blocklist-source` is not provided. Values can be `cidr`, or `location`. Defaults to `cidr`.
|
||||
- `blocklist-refresh` - Time interval (in seconds) in which external (remote or local) blocklists are reloaded. Optional.
|
||||
- `blocklist-source` - An array of blocklists, each with `format` and `source`.
|
||||
- `blocklist-source` - An array of blocklists, each with `format` and `source` and optionally `name`.
|
||||
- `location-db` - If location-based IP blocking is used, this specifies the GeoIP data file to load. Optional. Defaults to /usr/share/GeoIP/GeoLite2-City.mmdb
|
||||
|
||||
Examples:
|
||||
|
||||
17
geoip-db.go
17
geoip-db.go
@@ -13,6 +13,7 @@ import (
|
||||
// its location is looked up in a database and the result is compared to the
|
||||
// blocklist rules.
|
||||
type GeoIPDB struct {
|
||||
name string
|
||||
loader BlocklistLoader
|
||||
geoDB *maxminddb.Reader
|
||||
geoDBFile string
|
||||
@@ -22,7 +23,7 @@ type GeoIPDB struct {
|
||||
var _ IPBlocklistDB = &GeoIPDB{}
|
||||
|
||||
// NewGeoIPDB returns a new instance of a matcher for a location rules.
|
||||
func NewGeoIPDB(loader BlocklistLoader, geoDBFile string) (*GeoIPDB, error) {
|
||||
func NewGeoIPDB(name string, loader BlocklistLoader, geoDBFile string) (*GeoIPDB, error) {
|
||||
if geoDBFile == "" {
|
||||
geoDBFile = "/usr/share/GeoIP/GeoLite2-City.mmdb"
|
||||
}
|
||||
@@ -51,6 +52,7 @@ func NewGeoIPDB(loader BlocklistLoader, geoDBFile string) (*GeoIPDB, error) {
|
||||
db[value] = struct{}{}
|
||||
}
|
||||
return &GeoIPDB{
|
||||
name: name,
|
||||
geoDB: geoDB,
|
||||
geoDBFile: geoDBFile,
|
||||
db: db,
|
||||
@@ -59,10 +61,10 @@ func NewGeoIPDB(loader BlocklistLoader, geoDBFile string) (*GeoIPDB, error) {
|
||||
}
|
||||
|
||||
func (m *GeoIPDB) Reload() (IPBlocklistDB, error) {
|
||||
return NewGeoIPDB(m.loader, m.geoDBFile)
|
||||
return NewGeoIPDB(m.name, m.loader, m.geoDBFile)
|
||||
}
|
||||
|
||||
func (m *GeoIPDB) Match(ip net.IP) (string, bool) {
|
||||
func (m *GeoIPDB) Match(ip net.IP) (*BlocklistMatch, bool) {
|
||||
var record struct {
|
||||
Continent struct {
|
||||
GeoNameID uint64 `maxminddb:"geoname_id"`
|
||||
@@ -80,7 +82,7 @@ func (m *GeoIPDB) Match(ip net.IP) (string, bool) {
|
||||
|
||||
if err := m.geoDB.Lookup(ip, &record); err != nil {
|
||||
Log.WithField("ip", ip).WithError(err).Error("failed to lookup ip in geo location database")
|
||||
return "", false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Try to find the continent, country, or city GeoName ID in the blocklist
|
||||
@@ -90,10 +92,13 @@ func (m *GeoIPDB) Match(ip net.IP) (string, bool) {
|
||||
}
|
||||
for _, id := range ids {
|
||||
if _, ok := m.db[id]; ok {
|
||||
return fmt.Sprintf("%d", id), true
|
||||
return &BlocklistMatch{
|
||||
List: m.name,
|
||||
Rule: fmt.Sprintf("%d", id),
|
||||
}, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (m *GeoIPDB) Close() error {
|
||||
|
||||
@@ -28,13 +28,13 @@ func (m MultiIPDB) Reload() (IPBlocklistDB, error) {
|
||||
return NewMultiIPDB(newDBs...)
|
||||
}
|
||||
|
||||
func (m MultiIPDB) Match(ip net.IP) (string, bool) {
|
||||
func (m MultiIPDB) Match(ip net.IP) (*BlocklistMatch, bool) {
|
||||
for _, db := range m.dbs {
|
||||
if rule, ok := db.Match(ip); ok {
|
||||
return rule, ok
|
||||
if match, ok := db.Match(ip); ok {
|
||||
return match, ok
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (m MultiIPDB) Close() error {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// IPBlocklistDB is a database containing IPs used in blocklists.
|
||||
type IPBlocklistDB interface {
|
||||
Reload() (IPBlocklistDB, error)
|
||||
Match(ip net.IP) (string, bool)
|
||||
Match(ip net.IP) (*BlocklistMatch, bool)
|
||||
Close() error
|
||||
fmt.Stringer
|
||||
}
|
||||
@@ -103,8 +103,8 @@ func (r *ResponseBlocklistIP) blockIfMatch(query, answer *dns.Msg, ci ClientInfo
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if rule, ok := r.BlocklistDB.Match(ip); ok {
|
||||
log := logger(r.id, query, ci).WithFields(logrus.Fields{"rule": rule, "ip": ip})
|
||||
if match, ok := r.BlocklistDB.Match(ip); ok {
|
||||
log := logger(r.id, query, ci).WithFields(logrus.Fields{"list": match.List, "rule": match.Rule, "ip": ip})
|
||||
if r.BlocklistResolver != nil {
|
||||
log.WithField("resolver", r.BlocklistResolver).Debug("blocklist match, forwarding to blocklist-resolver")
|
||||
return r.BlocklistResolver.Resolve(query, ci)
|
||||
@@ -147,8 +147,8 @@ func (r *ResponseBlocklistIP) filterRR(query *dns.Msg, ci ClientInfo, rrs []dns.
|
||||
newRRs = append(newRRs, rr)
|
||||
continue
|
||||
}
|
||||
if rule, ok := r.BlocklistDB.Match(ip); ok {
|
||||
logger(r.id, query, ci).WithFields(logrus.Fields{"rule": rule, "ip": ip}).Debug("filtering response")
|
||||
if match, ok := r.BlocklistDB.Match(ip); ok {
|
||||
logger(r.id, query, ci).WithFields(logrus.Fields{"list": match.List, "rule": match.Rule, "ip": ip}).Debug("filtering response")
|
||||
continue
|
||||
}
|
||||
newRRs = append(newRRs, rr)
|
||||
|
||||
Reference in New Issue
Block a user