mirror of
https://github.com/safing/portmaster
synced 2025-09-02 10:39:22 +00:00
Custom filter list:
subdomain and cname cheks Automatic realod when settings is changed periodicly check for file changes
This commit is contained in:
parent
35697989e5
commit
62c100714a
5 changed files with 126 additions and 16 deletions
|
@ -616,14 +616,24 @@ matchLoop:
|
|||
}
|
||||
|
||||
func checkCustomFilterList(_ context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
|
||||
// block if the domain name appears in the custom filter list
|
||||
// block if the domain name appears in the custom filter list (check for subdomains if enabled)
|
||||
if conn.Entity.Domain != "" {
|
||||
if customlists.LookupDomain(conn.Entity.Domain) {
|
||||
if customlists.LookupDomain(conn.Entity.Domain, p.FilterSubDomains()) {
|
||||
conn.Block("Domains appears in the custom user list", customlists.CfgOptionCustomListBlockingKey)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// block if any of the CNAME appears in the custom filter list (check for subdomains if enabled)
|
||||
if len(conn.Entity.CNAME) > 0 && p.FilterCNAMEs() {
|
||||
for _, cname := range conn.Entity.CNAME {
|
||||
if customlists.LookupDomain(cname, p.FilterSubDomains()) {
|
||||
conn.Block("CNAME appears in the custom user list", customlists.CfgOptionCustomListBlockingKey)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// block if ip addresses appears in the custom filter list
|
||||
if conn.Entity.IP != nil {
|
||||
if customlists.LookupIP(&conn.Entity.IP) {
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
package customlists
|
||||
|
||||
import "github.com/safing/portbase/config"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portbase/config"
|
||||
)
|
||||
|
||||
var (
|
||||
// CfgOptionCustomListBlockingKey is the config key for the listen address..
|
||||
CfgOptionCustomListBlockingKey = "filter/customListBlocking"
|
||||
cfgOptionCustomListBlockingOrder = 37
|
||||
cfgOptionCustomListBlockingOrder = 35
|
||||
cfgOptionCustomListCategoryAnnotation = "Filter Lists"
|
||||
)
|
||||
|
||||
|
@ -14,11 +18,35 @@ var (
|
|||
)
|
||||
|
||||
func registerConfig() error {
|
||||
help := strings.ReplaceAll(`Put all domains, Ip addresses, country codes and autonomous system that you want to block in a file in which each entry is on a new line.
|
||||
Lines that start with a '#' symbol are ignored.
|
||||
Everything after the first space/tab is ignored.
|
||||
Example:
|
||||
"""
|
||||
# Domains:
|
||||
example.com
|
||||
google.com
|
||||
|
||||
# IP addresses
|
||||
1.2.3.4
|
||||
4.3.2.1
|
||||
|
||||
# Countries
|
||||
AU
|
||||
BG
|
||||
|
||||
# Autonomous Systems
|
||||
AS123
|
||||
"""
|
||||
> * All the records are stored in RAM, careful with large block lists.
|
||||
> * Hosts files are not supported.`, "\"", "`")
|
||||
|
||||
// register a setting for the file path in the ui
|
||||
err := config.Register(&config.Option{
|
||||
Name: "Custom Filter List",
|
||||
Key: CfgOptionCustomListBlockingKey,
|
||||
Description: "Path to the file that contains a list of Domain, IP addresses, country codes and autonomous systems that you want to block",
|
||||
Help: help,
|
||||
OptType: config.OptTypeString,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
ReleaseLevel: config.ReleaseLevelStable,
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/notifications"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
)
|
||||
|
||||
|
@ -19,11 +20,18 @@ var (
|
|||
domainsFilterList map[string]struct{}
|
||||
)
|
||||
|
||||
const numberOfZeroIPsUntilWarning = 100
|
||||
|
||||
func parseFile(filePath string) error {
|
||||
// ignore empty file path
|
||||
if filePath == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// open the file if possible
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
log.Warningf("Custom filter: failed to parse file: \"%s\"", filePath)
|
||||
log.Warningf("intel/customlists: failed to parse file: \"%s\"", filePath)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
@ -34,11 +42,13 @@ func parseFile(filePath string) error {
|
|||
autonomousSystemsFilterList = make(map[uint]struct{})
|
||||
domainsFilterList = make(map[string]struct{})
|
||||
|
||||
var numberOfZeroIPs uint64
|
||||
|
||||
// read filter file line by line
|
||||
scanner := bufio.NewScanner(file)
|
||||
// the scanner will error out if the line is greater than 64K, in this case it is enough
|
||||
for scanner.Scan() {
|
||||
parseLine(scanner.Text())
|
||||
parseLine(scanner.Text(), &numberOfZeroIPs)
|
||||
}
|
||||
|
||||
// check for scanner error
|
||||
|
@ -46,12 +56,17 @@ func parseFile(filePath string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
log.Infof("Custom filter: list loaded successful: %s", filePath)
|
||||
if numberOfZeroIPs >= numberOfZeroIPsUntilWarning {
|
||||
log.Warning("intel/customlists: Too many zero IP addresses.")
|
||||
notifications.NotifyWarn("too_many_zero_ips", "Too many zero IP addresses. Check your custom filter list.", "Hosts file format is not spported.")
|
||||
}
|
||||
|
||||
log.Infof("intel/customlists: list loaded successful: %s", filePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseLine(line string) {
|
||||
func parseLine(line string, numberOfZeroIPs *uint64) {
|
||||
// ignore empty lines and comment lines
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
return
|
||||
|
@ -69,6 +84,13 @@ func parseLine(line string) {
|
|||
ip := net.ParseIP(field)
|
||||
if ip != nil {
|
||||
ipAddressesFilterList[ip.String()] = struct{}{}
|
||||
|
||||
// check if its zero ip
|
||||
for i := 0; i < len(ip); i++ {
|
||||
if ip[i] != 0 {
|
||||
*numberOfZeroIPs++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if it's a Autonomous system (example AS123)
|
||||
|
|
|
@ -5,14 +5,20 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/modules"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
var module *modules.Module
|
||||
|
||||
const configChangeEvent = "config change"
|
||||
const (
|
||||
configModuleName = "config"
|
||||
configChangeEvent = "config change"
|
||||
)
|
||||
|
||||
// Helper variables for parsing the input file
|
||||
var (
|
||||
|
@ -23,6 +29,8 @@ var (
|
|||
var (
|
||||
filterListFilePath string
|
||||
filterListFileModifiedTime time.Time
|
||||
|
||||
parseLock sync.RWMutex
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -38,7 +46,7 @@ func prep() error {
|
|||
|
||||
// register to hook to update after config change.
|
||||
if err := module.RegisterEventHook(
|
||||
module.Name,
|
||||
configModuleName,
|
||||
configChangeEvent,
|
||||
"update custom filter list",
|
||||
func(ctx context.Context, obj interface{}) error {
|
||||
|
@ -54,17 +62,20 @@ func prep() error {
|
|||
|
||||
func start() error {
|
||||
// register timer to run every periodically and check for file updates
|
||||
module.NewTask("Custom filter list file update check", func(context.Context, *modules.Task) error {
|
||||
module.NewTask("intel/customlists list file update check", func(context.Context, *modules.Task) error {
|
||||
_ = checkAndUpdateFilterList()
|
||||
return nil
|
||||
}).Repeat(10 * time.Minute)
|
||||
|
||||
// parse the file for the first time at start
|
||||
// parse the file at startup
|
||||
_ = parseFile(getFilePath())
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkAndUpdateFilterList() error {
|
||||
parseLock.Lock()
|
||||
defer parseLock.Unlock()
|
||||
|
||||
// get path and try to get its info
|
||||
filePath := getFilePath()
|
||||
fileInfo, err := os.Stat(filePath)
|
||||
|
@ -92,10 +103,21 @@ func LookupIP(ip *net.IP) bool {
|
|||
}
|
||||
|
||||
// LookupDomain checks if the Domain is in a custom filter list
|
||||
func LookupDomain(domain string) bool {
|
||||
func LookupDomain(fullDomain string, filterSubdomains bool) bool {
|
||||
if filterSubdomains {
|
||||
listOfDomains := splitDomain(fullDomain)
|
||||
for _, domain := range listOfDomains {
|
||||
_, ok := domainsFilterList[domain]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, ok := domainsFilterList[fullDomain]
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LookupASN checks if the Autonomous system number is in a custom filter list
|
||||
func LookupASN(number uint) bool {
|
||||
|
@ -108,3 +130,29 @@ func LookupCountry(countryCode string) bool {
|
|||
_, ok := countryCodesFilterList[countryCode]
|
||||
return ok
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -64,9 +64,11 @@ var (
|
|||
cfgOptionFilterLists config.StringArrayOption
|
||||
cfgOptionFilterListsOrder = 34
|
||||
|
||||
// Setting "Custom Filter List" at order 35
|
||||
|
||||
CfgOptionFilterSubDomainsKey = "filter/includeSubdomains"
|
||||
cfgOptionFilterSubDomains config.IntOption // security level option
|
||||
cfgOptionFilterSubDomainsOrder = 35
|
||||
cfgOptionFilterSubDomainsOrder = 36
|
||||
|
||||
// DNS Filtering.
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue