mirror of
https://github.com/safing/portmaster
synced 2025-09-02 18:49:14 +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 {
|
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 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)
|
conn.Block("Domains appears in the custom user list", customlists.CfgOptionCustomListBlockingKey)
|
||||||
return true
|
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
|
// block if ip addresses appears in the custom filter list
|
||||||
if conn.Entity.IP != nil {
|
if conn.Entity.IP != nil {
|
||||||
if customlists.LookupIP(&conn.Entity.IP) {
|
if customlists.LookupIP(&conn.Entity.IP) {
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
package customlists
|
package customlists
|
||||||
|
|
||||||
import "github.com/safing/portbase/config"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/config"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// CfgOptionCustomListBlockingKey is the config key for the listen address..
|
// CfgOptionCustomListBlockingKey is the config key for the listen address..
|
||||||
CfgOptionCustomListBlockingKey = "filter/customListBlocking"
|
CfgOptionCustomListBlockingKey = "filter/customListBlocking"
|
||||||
cfgOptionCustomListBlockingOrder = 37
|
cfgOptionCustomListBlockingOrder = 35
|
||||||
cfgOptionCustomListCategoryAnnotation = "Filter Lists"
|
cfgOptionCustomListCategoryAnnotation = "Filter Lists"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,11 +18,35 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerConfig() error {
|
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
|
// register a setting for the file path in the ui
|
||||||
err := config.Register(&config.Option{
|
err := config.Register(&config.Option{
|
||||||
Name: "Custom Filter List",
|
Name: "Custom Filter List",
|
||||||
Key: CfgOptionCustomListBlockingKey,
|
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",
|
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,
|
OptType: config.OptTypeString,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
|
"github.com/safing/portbase/notifications"
|
||||||
"github.com/safing/portmaster/network/netutils"
|
"github.com/safing/portmaster/network/netutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,11 +20,18 @@ var (
|
||||||
domainsFilterList map[string]struct{}
|
domainsFilterList map[string]struct{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const numberOfZeroIPsUntilWarning = 100
|
||||||
|
|
||||||
func parseFile(filePath string) error {
|
func parseFile(filePath string) error {
|
||||||
|
// ignore empty file path
|
||||||
|
if filePath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// open the file if possible
|
// open the file if possible
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
@ -34,11 +42,13 @@ func parseFile(filePath string) error {
|
||||||
autonomousSystemsFilterList = make(map[uint]struct{})
|
autonomousSystemsFilterList = make(map[uint]struct{})
|
||||||
domainsFilterList = make(map[string]struct{})
|
domainsFilterList = make(map[string]struct{})
|
||||||
|
|
||||||
|
var numberOfZeroIPs uint64
|
||||||
|
|
||||||
// read filter file line by line
|
// read filter file line by line
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
// the scanner will error out if the line is greater than 64K, in this case it is enough
|
// the scanner will error out if the line is greater than 64K, in this case it is enough
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
parseLine(scanner.Text())
|
parseLine(scanner.Text(), &numberOfZeroIPs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for scanner error
|
// check for scanner error
|
||||||
|
@ -46,12 +56,17 @@ func parseFile(filePath string) error {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLine(line string) {
|
func parseLine(line string, numberOfZeroIPs *uint64) {
|
||||||
// ignore empty lines and comment lines
|
// ignore empty lines and comment lines
|
||||||
if len(line) == 0 || line[0] == '#' {
|
if len(line) == 0 || line[0] == '#' {
|
||||||
return
|
return
|
||||||
|
@ -69,6 +84,13 @@ func parseLine(line string) {
|
||||||
ip := net.ParseIP(field)
|
ip := net.ParseIP(field)
|
||||||
if ip != nil {
|
if ip != nil {
|
||||||
ipAddressesFilterList[ip.String()] = struct{}{}
|
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)
|
// check if it's a Autonomous system (example AS123)
|
||||||
|
|
|
@ -5,14 +5,20 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var module *modules.Module
|
var module *modules.Module
|
||||||
|
|
||||||
const configChangeEvent = "config change"
|
const (
|
||||||
|
configModuleName = "config"
|
||||||
|
configChangeEvent = "config change"
|
||||||
|
)
|
||||||
|
|
||||||
// Helper variables for parsing the input file
|
// Helper variables for parsing the input file
|
||||||
var (
|
var (
|
||||||
|
@ -23,6 +29,8 @@ var (
|
||||||
var (
|
var (
|
||||||
filterListFilePath string
|
filterListFilePath string
|
||||||
filterListFileModifiedTime time.Time
|
filterListFileModifiedTime time.Time
|
||||||
|
|
||||||
|
parseLock sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -38,7 +46,7 @@ func prep() error {
|
||||||
|
|
||||||
// register to hook to update after config change.
|
// register to hook to update after config change.
|
||||||
if err := module.RegisterEventHook(
|
if err := module.RegisterEventHook(
|
||||||
module.Name,
|
configModuleName,
|
||||||
configChangeEvent,
|
configChangeEvent,
|
||||||
"update custom filter list",
|
"update custom filter list",
|
||||||
func(ctx context.Context, obj interface{}) error {
|
func(ctx context.Context, obj interface{}) error {
|
||||||
|
@ -54,17 +62,20 @@ func prep() error {
|
||||||
|
|
||||||
func start() error {
|
func start() error {
|
||||||
// register timer to run every periodically and check for file updates
|
// 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()
|
_ = checkAndUpdateFilterList()
|
||||||
return nil
|
return nil
|
||||||
}).Repeat(10 * time.Minute)
|
}).Repeat(10 * time.Minute)
|
||||||
|
|
||||||
// parse the file for the first time at start
|
// parse the file at startup
|
||||||
_ = parseFile(getFilePath())
|
_ = parseFile(getFilePath())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkAndUpdateFilterList() error {
|
func checkAndUpdateFilterList() error {
|
||||||
|
parseLock.Lock()
|
||||||
|
defer parseLock.Unlock()
|
||||||
|
|
||||||
// get path and try to get its info
|
// get path and try to get its info
|
||||||
filePath := getFilePath()
|
filePath := getFilePath()
|
||||||
fileInfo, err := os.Stat(filePath)
|
fileInfo, err := os.Stat(filePath)
|
||||||
|
@ -92,9 +103,20 @@ func LookupIP(ip *net.IP) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupDomain checks if the Domain is in a custom filter list
|
// LookupDomain checks if the Domain is in a custom filter list
|
||||||
func LookupDomain(domain string) bool {
|
func LookupDomain(fullDomain string, filterSubdomains bool) bool {
|
||||||
_, ok := domainsFilterList[domain]
|
if filterSubdomains {
|
||||||
return ok
|
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
|
// LookupASN checks if the Autonomous system number is in a custom filter list
|
||||||
|
@ -108,3 +130,29 @@ func LookupCountry(countryCode string) bool {
|
||||||
_, ok := countryCodesFilterList[countryCode]
|
_, ok := countryCodesFilterList[countryCode]
|
||||||
return ok
|
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
|
cfgOptionFilterLists config.StringArrayOption
|
||||||
cfgOptionFilterListsOrder = 34
|
cfgOptionFilterListsOrder = 34
|
||||||
|
|
||||||
|
// Setting "Custom Filter List" at order 35
|
||||||
|
|
||||||
CfgOptionFilterSubDomainsKey = "filter/includeSubdomains"
|
CfgOptionFilterSubDomainsKey = "filter/includeSubdomains"
|
||||||
cfgOptionFilterSubDomains config.IntOption // security level option
|
cfgOptionFilterSubDomains config.IntOption // security level option
|
||||||
cfgOptionFilterSubDomainsOrder = 35
|
cfgOptionFilterSubDomainsOrder = 36
|
||||||
|
|
||||||
// DNS Filtering.
|
// DNS Filtering.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue