Merge pull request #35 from safing/feature/filter-subdomains

Add support to filter sub-domains as well
This commit is contained in:
Daniel 2020-04-15 10:12:41 +02:00 committed by GitHub
commit eb6bf7eae9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 107 additions and 15 deletions

View file

@ -41,9 +41,9 @@ func DecideOnConnection(conn *network.Connection, pkt packet.Packet) { //nolint:
log.Infof("filter: re-evaluating verdict on %s", conn)
conn.Verdict = network.VerdictUndecided
//if conn.Entity != nil {
//conn.Entity.ResetLists()
//}
if conn.Entity != nil {
conn.Entity.ResetLists()
}
}
// grant self

View file

@ -5,6 +5,7 @@ import (
"fmt"
"net"
"sort"
"strings"
"sync"
"github.com/safing/portbase/log"
@ -12,6 +13,7 @@ import (
"github.com/safing/portmaster/intel/geoip"
"github.com/safing/portmaster/network/netutils"
"github.com/safing/portmaster/status"
"golang.org/x/net/publicsuffix"
)
// Entity describes a remote endpoint in many different ways.
@ -29,6 +31,7 @@ type Entity struct {
countryListLoaded bool
asnListLoaded bool
reverseResolveEnabled bool
resolveSubDomainLists bool
Protocol uint8
Port uint16
@ -63,6 +66,34 @@ func (e *Entity) FetchData() {
e.getLists()
}
// ResetLists resets the current list data and forces
// all list sources to be re-acquired when calling GetLists().
func (e *Entity) ResetLists() {
// TODO(ppacher): our actual goal is to reset the domain
// list right now so we could be more efficient by keeping
// the other lists around.
e.Lists = nil
e.ListsMap = nil
e.domainListLoaded = false
e.ipListLoaded = false
e.countryListLoaded = false
e.asnListLoaded = false
e.resolveSubDomainLists = false
e.loadDomainListOnce = sync.Once{}
e.loadIPListOnce = sync.Once{}
e.loadCoutryListOnce = sync.Once{}
e.loadAsnListOnce = sync.Once{}
}
// ResolveSubDomainLists enables or disables list lookups for
// sub-domains.
func (e *Entity) ResolveSubDomainLists(enabled bool) {
if e.domainListLoaded {
log.Warningf("intel/filterlists: tried to change sub-domain resolving for %s but lists are already fetched", e.Domain)
}
e.resolveSubDomainLists = enabled
}
// Domain and IP
// EnableReverseResolving enables reverse resolving the domain from the IP on demand.
@ -190,19 +221,53 @@ func (e *Entity) getDomainLists() {
}
e.loadDomainListOnce.Do(func() {
log.Debugf("intel: loading domain list for %s", domain)
list, err := filterlists.LookupDomain(domain)
if err != nil {
log.Errorf("intel: failed to get domain blocklists for %s: %s", domain, err)
e.loadDomainListOnce = sync.Once{}
return
var domains = []string{domain}
if e.resolveSubDomainLists {
domains = splitDomain(domain)
log.Debugf("intel: subdomain list resolving is enabled, checking %v", domains)
}
for _, d := range domains {
log.Debugf("intel: loading domain list for %s", d)
list, err := filterlists.LookupDomain(d)
if err != nil {
log.Errorf("intel: failed to get domain blocklists for %s: %s", d, err)
e.loadDomainListOnce = sync.Once{}
return
}
e.mergeList(list)
}
e.domainListLoaded = true
e.mergeList(list)
})
}
func splitDomain(domain string) []string {
domain = strings.Trim(domain, ".")
suffix, _ := publicsuffix.PublicSuffix(domain)
if suffix == domain {
return []string{domain}
}
domainWithoutSuffix := domain[:len(domain)-len(suffix)]
domainWithoutSuffix = strings.Trim(domainWithoutSuffix, ".")
splitted := strings.FieldsFunc(domainWithoutSuffix, func(r rune) bool {
return r == '.'
})
domains := make([]string, 0, len(splitted))
for idx := range splitted {
d := strings.Join(splitted[idx:], ".") + "." + suffix
if d[len(d)-1] != '.' {
d += "."
}
domains = append(domains, d)
}
return domains
}
func (e *Entity) getASNLists() {
if e.asnListLoaded {
return

View file

@ -201,11 +201,13 @@ func triggerOnlineStatusInvestigation() {
func monitorOnlineStatus(ctx context.Context) error {
for {
timeout := time.Minute
if GetOnlineStatus() != StatusOnline {
timeout = time.Second
log.Debugf("checking online status again in %s because current status is %s", timeout, GetOnlineStatus())
}
timeout := 5 * time.Minute
/*
if GetOnlineStatus() != StatusOnline {
timeout = time.Second
log.Debugf("checking online status again in %s because current status is %s", timeout, GetOnlineStatus())
}
*/
// wait for trigger
select {
case <-ctx.Done():

View file

@ -27,6 +27,9 @@ var (
CfgOptionFilterListKey = "filter/lists"
cfgOptionFilterLists config.StringArrayOption
CfgOptionFilterSubDomainsKey = "filter/includeSubdomains"
cfgOptionFilterSubDomains config.IntOption // security level option
CfgOptionBlockScopeLocalKey = "filter/blockLocal"
cfgOptionBlockScopeLocal config.IntOption // security level option
@ -155,6 +158,21 @@ Examples:
cfgOptionFilterLists = config.Concurrent.GetAsStringArray(CfgOptionFilterListKey, []string{})
cfgStringArrayOptions[CfgOptionFilterListKey] = cfgOptionFilterLists
err = config.Register(&config.Option{
Name: "Filter SubDomains",
Key: CfgOptionFilterSubDomainsKey,
Description: "Also filter sub-domains if a parent domain is blocked by a filter list",
OptType: config.OptTypeInt,
ExternalOptType: "security level",
DefaultValue: status.SecurityLevelOff,
ValidationRegex: "^(0|4|6|7)$",
})
if err != nil {
return err
}
cfgOptionFilterSubDomains = config.Concurrent.GetAsInt(CfgOptionFilterSubDomainsKey, int64(status.SecurityLevelOff))
cfgIntOptions[CfgOptionFilterSubDomainsKey] = cfgOptionFilterSubDomains
// Block Scope Local
err = config.Register(&config.Option{
Name: "Block Scope Local",

View file

@ -42,6 +42,7 @@ type LayeredProfile struct {
EnforceSPN config.BoolOption
RemoveOutOfScopeDNS config.BoolOption
RemoveBlockedDNS config.BoolOption
FilterSubDomains config.BoolOption
}
// NewLayeredProfile returns a new layered profile based on the given local profile.
@ -93,6 +94,10 @@ func NewLayeredProfile(localProfile *Profile) *LayeredProfile {
CfgOptionRemoveBlockedDNSKey,
cfgOptionRemoveBlockedDNS,
)
new.FilterSubDomains = new.wrapSecurityLevelOption(
CfgOptionFilterSubDomainsKey,
cfgOptionFilterSubDomains,
)
// TODO: load linked profiles.
@ -220,6 +225,8 @@ func (lp *LayeredProfile) MatchServiceEndpoint(entity *intel.Entity) (result end
// MatchFilterLists matches the entity against the set of filter
// lists.
func (lp *LayeredProfile) MatchFilterLists(entity *intel.Entity) (result endpoints.EPResult, reason string) {
entity.ResolveSubDomainLists(lp.FilterSubDomains())
lookupMap, hasLists := entity.GetListsMap()
if !hasLists {
return endpoints.NoMatch, ""