mirror of
https://github.com/safing/portmaster
synced 2025-09-02 02:29:12 +00:00
Release to master
This commit is contained in:
commit
912ddca9ed
50 changed files with 930 additions and 317 deletions
33
Gopkg.lock
generated
33
Gopkg.lock
generated
|
@ -41,6 +41,14 @@
|
||||||
revision = "78b5fff24e6df8886ef8eca9411f683a884349a5"
|
revision = "78b5fff24e6df8886ef8eca9411f683a884349a5"
|
||||||
version = "v0.4.1"
|
version = "v0.4.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77"
|
||||||
|
name = "github.com/davecgh/go-spew"
|
||||||
|
packages = ["spew"]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
|
||||||
|
version = "v1.1.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:b6581f9180e0f2d5549280d71819ab951db9d511478c87daca95669589d505c0"
|
digest = "1:b6581f9180e0f2d5549280d71819ab951db9d511478c87daca95669589d505c0"
|
||||||
name = "github.com/go-ole/go-ole"
|
name = "github.com/go-ole/go-ole"
|
||||||
|
@ -120,6 +128,14 @@
|
||||||
revision = "2905694a1b00c5574f1418a7dbf8a22a7d247559"
|
revision = "2905694a1b00c5574f1418a7dbf8a22a7d247559"
|
||||||
version = "v1.3.1"
|
version = "v1.3.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
|
||||||
|
name = "github.com/pmezard/go-difflib"
|
||||||
|
packages = ["difflib"]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||||
|
version = "v1.0.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:7f569d906bdd20d906b606415b7d794f798f91a62fcfb6a4daa6d50690fb7a3f"
|
digest = "1:7f569d906bdd20d906b606415b7d794f798f91a62fcfb6a4daa6d50690fb7a3f"
|
||||||
name = "github.com/satori/go.uuid"
|
name = "github.com/satori/go.uuid"
|
||||||
|
@ -166,6 +182,14 @@
|
||||||
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
|
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
|
||||||
version = "v1.0.3"
|
version = "v1.0.3"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:cc4eb6813da8d08694e557fcafae8fcc24f47f61a0717f952da130ca9a486dfc"
|
||||||
|
name = "github.com/stretchr/testify"
|
||||||
|
packages = ["assert"]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "3ebf1ddaeb260c4b1ae502a01c7844fa8c1fa0e9"
|
||||||
|
version = "v1.5.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:86e6712cfd4070a2120c03fcec41cfcbbc51813504a74e28d74479edfaf669ee"
|
digest = "1:86e6712cfd4070a2120c03fcec41cfcbbc51813504a74e28d74479edfaf669ee"
|
||||||
|
@ -259,6 +283,14 @@
|
||||||
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
|
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
|
||||||
version = "v0.3.2"
|
version = "v0.3.2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
digest = "1:2efc9662a6a1ff28c65c84fc2f9030f13d3afecdb2ecad445f3b0c80e75fc281"
|
||||||
|
name = "gopkg.in/yaml.v2"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = ""
|
||||||
|
revision = "53403b58ad1b561927d19068c655246f2db79d48"
|
||||||
|
version = "v2.2.8"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
|
@ -278,6 +310,7 @@
|
||||||
"github.com/satori/go.uuid",
|
"github.com/satori/go.uuid",
|
||||||
"github.com/shirou/gopsutil/process",
|
"github.com/shirou/gopsutil/process",
|
||||||
"github.com/spf13/cobra",
|
"github.com/spf13/cobra",
|
||||||
|
"github.com/stretchr/testify/assert",
|
||||||
"github.com/tevino/abool",
|
"github.com/tevino/abool",
|
||||||
"github.com/umahmood/haversine",
|
"github.com/umahmood/haversine",
|
||||||
"golang.org/x/net/icmp",
|
"golang.org/x/net/icmp",
|
||||||
|
|
|
@ -28,7 +28,7 @@ A DNS resolver that does not only encrypt your queries, but figures out where it
|
||||||
|
|
||||||
## Privacy Filter
|
## Privacy Filter
|
||||||
|
|
||||||
**Status:** _unreleased - pre-alpha scheduled for the next days_
|
**Status:** - _pre-alpha_
|
||||||
|
|
||||||
Think of a pi-hole for your computer. Or an ad-blocker that blocks ads on your whole computer, not only on your browser. With you everywhere you go and every network you visit.
|
Think of a pi-hole for your computer. Or an ad-blocker that blocks ads on your whole computer, not only on your browser. With you everywhere you go and every network you visit.
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ Think of a pi-hole for your computer. Or an ad-blocker that blocks ads on your w
|
||||||
- Select and activate block-lists
|
- Select and activate block-lists
|
||||||
- Manually black/whitelist domains
|
- Manually black/whitelist domains
|
||||||
- You can whitelist domains in case something breaks
|
- You can whitelist domains in case something breaks
|
||||||
- CNAME Blocking (block these new nasty "unblockable" ads/trackers - coming soon)
|
- CNAME Blocking (block these new nasty "unblockable" ads/trackers)
|
||||||
- Block all subdomains of a domain in the block-lists
|
- Block all subdomains of a domain in the block-lists
|
||||||
|
|
||||||
## Safing Privacy Network (SPN)
|
## Safing Privacy Network (SPN)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package core
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/safing/portbase/database"
|
"github.com/safing/portbase/database"
|
|
@ -1,4 +1,4 @@
|
||||||
package core
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -48,13 +48,6 @@ func globalPrep() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// init config
|
|
||||||
logFlagOverrides()
|
|
||||||
err := registerConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// set api listen address
|
// set api listen address
|
||||||
api.SetDefaultAPIListenAddress(DefaultAPIListenAddress)
|
api.SetDefaultAPIListenAddress(DefaultAPIListenAddress)
|
||||||
|
|
25
core/base/module.go
Normal file
25
core/base/module.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/safing/portbase/modules"
|
||||||
|
|
||||||
|
// module dependencies
|
||||||
|
_ "github.com/safing/portbase/config"
|
||||||
|
_ "github.com/safing/portbase/rng"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
modules.Register("base", nil, registerDatabases, nil, "database", "config", "rng")
|
||||||
|
|
||||||
|
// For prettier subsystem graph, printed with --print-subsystem-graph
|
||||||
|
/*
|
||||||
|
subsystems.Register(
|
||||||
|
"base",
|
||||||
|
"Base",
|
||||||
|
"THE GROUND.",
|
||||||
|
baseModule,
|
||||||
|
"",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ import (
|
||||||
var (
|
var (
|
||||||
CfgDevModeKey = "core/devMode"
|
CfgDevModeKey = "core/devMode"
|
||||||
defaultDevMode bool
|
defaultDevMode bool
|
||||||
|
|
||||||
|
CfgUseSystemNotificationsKey = "core/useSystemNotifications"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -28,6 +30,7 @@ func registerConfig() error {
|
||||||
Name: "Development Mode",
|
Name: "Development Mode",
|
||||||
Key: CfgDevModeKey,
|
Key: CfgDevModeKey,
|
||||||
Description: "In Development Mode security restrictions are lifted/softened to enable easier access to Portmaster for debugging and testing purposes.",
|
Description: "In Development Mode security restrictions are lifted/softened to enable easier access to Portmaster for debugging and testing purposes.",
|
||||||
|
Order: 127,
|
||||||
OptType: config.OptTypeBool,
|
OptType: config.OptTypeBool,
|
||||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
@ -37,5 +40,19 @@ func registerConfig() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = config.Register(&config.Option{
|
||||||
|
Name: "Use System Notifications",
|
||||||
|
Key: CfgUseSystemNotificationsKey,
|
||||||
|
Description: "Send notifications to your operating system's notification system. When this setting is turned off, notifications will only be visible in the Portmaster App. This affects both alerts from the Portmaster and questions from the Privacy Filter.",
|
||||||
|
Order: 32,
|
||||||
|
OptType: config.OptTypeBool,
|
||||||
|
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||||
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
DefaultValue: true, // TODO: turn off by default on unsupported systems
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
100
core/control.go
Normal file
100
core/control.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/database"
|
||||||
|
"github.com/safing/portbase/database/record"
|
||||||
|
"github.com/safing/portbase/database/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StorageInterface provices a storage.Interface to the storage manager.
|
||||||
|
type StorageInterface struct {
|
||||||
|
storage.InjectBase
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a database record.
|
||||||
|
func (s *StorageInterface) Get(key string) (record.Record, error) {
|
||||||
|
msg := newMessage(key)
|
||||||
|
splittedKey := strings.Split(key, "/")
|
||||||
|
|
||||||
|
switch splittedKey[0] {
|
||||||
|
case "module":
|
||||||
|
return controlModule(msg, splittedKey)
|
||||||
|
default:
|
||||||
|
return nil, storage.ErrNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func controlModule(msg *MessageRecord, splittedKey []string) (record.Record, error) {
|
||||||
|
// format: module/moduleName/action/param
|
||||||
|
var moduleName string
|
||||||
|
var action string
|
||||||
|
var param string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// parse elements
|
||||||
|
switch len(splittedKey) {
|
||||||
|
case 4:
|
||||||
|
param = splittedKey[3]
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
moduleName = splittedKey[1]
|
||||||
|
action = splittedKey[2]
|
||||||
|
default:
|
||||||
|
return nil, storage.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute
|
||||||
|
switch action {
|
||||||
|
case "trigger":
|
||||||
|
err = module.InjectEvent(fmt.Sprintf("user triggered the '%s/%s' event", moduleName, param), moduleName, param, nil)
|
||||||
|
default:
|
||||||
|
return nil, storage.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
msg.Message = err.Error()
|
||||||
|
} else {
|
||||||
|
msg.Success = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerControlDatabase() error {
|
||||||
|
_, err := database.Register(&database.Database{
|
||||||
|
Name: "control",
|
||||||
|
Description: "Control Interface for the Portmaster",
|
||||||
|
StorageType: "injected",
|
||||||
|
PrimaryAPI: "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = database.InjectDatabase("control", &StorageInterface{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageRecord is a simple record used for control database feedback
|
||||||
|
type MessageRecord struct {
|
||||||
|
record.Base
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
Success bool
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMessage(key string) *MessageRecord {
|
||||||
|
m := &MessageRecord{}
|
||||||
|
m.SetKey("control:" + key)
|
||||||
|
m.UpdateMeta()
|
||||||
|
return m
|
||||||
|
}
|
27
core/core.go
27
core/core.go
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/safing/portbase/modules/subsystems"
|
"github.com/safing/portbase/modules/subsystems"
|
||||||
|
|
||||||
// module dependencies
|
// module dependencies
|
||||||
_ "github.com/safing/portbase/rng"
|
_ "github.com/safing/portmaster/netenv"
|
||||||
_ "github.com/safing/portmaster/status"
|
_ "github.com/safing/portmaster/status"
|
||||||
_ "github.com/safing/portmaster/ui"
|
_ "github.com/safing/portmaster/ui"
|
||||||
_ "github.com/safing/portmaster/updates"
|
_ "github.com/safing/portmaster/updates"
|
||||||
|
@ -18,9 +18,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
modules.Register("base", nil, registerDatabases, nil, "database", "config", "rng")
|
module = modules.Register("core", prep, start, nil, "base", "subsystems", "status", "updates", "api", "notifications", "ui", "netenv", "network", "interception")
|
||||||
|
|
||||||
module = modules.Register("core", nil, start, nil, "base", "subsystems", "status", "updates", "api", "notifications", "ui")
|
|
||||||
subsystems.Register(
|
subsystems.Register(
|
||||||
"core",
|
"core",
|
||||||
"Core",
|
"Core",
|
||||||
|
@ -31,10 +29,31 @@ func init() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prep() error {
|
||||||
|
registerEvents()
|
||||||
|
|
||||||
|
// init config
|
||||||
|
logFlagOverrides()
|
||||||
|
err := registerConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func start() error {
|
func start() error {
|
||||||
if err := startPlatformSpecific(); err != nil {
|
if err := startPlatformSpecific(); err != nil {
|
||||||
return fmt.Errorf("failed to start plattform-specific components: %s", err)
|
return fmt.Errorf("failed to start plattform-specific components: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := registerEventHooks(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := registerControlDatabase(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
46
core/events.go
Normal file
46
core/events.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/log"
|
||||||
|
"github.com/safing/portbase/modules"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
eventShutdown = "shutdown"
|
||||||
|
eventRestart = "restart"
|
||||||
|
restartCode = 23
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerEvents() {
|
||||||
|
module.RegisterEvent(eventShutdown)
|
||||||
|
module.RegisterEvent(eventRestart)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerEventHooks() error {
|
||||||
|
err := module.RegisterEventHook("core", eventShutdown, "execute shutdown", shutdown)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = module.RegisterEventHook("core", eventRestart, "execute shutdown", restart)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shutdown(ctx context.Context, _ interface{}) error {
|
||||||
|
log.Warning("core: user requested shutdown")
|
||||||
|
go modules.Shutdown() //nolint:errcheck
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restart(ctx context.Context, data interface{}) error {
|
||||||
|
log.Info("core: user requested restart")
|
||||||
|
modules.SetExitStatusCode(restartCode)
|
||||||
|
go modules.Shutdown() //nolint:errcheck
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ import (
|
||||||
"github.com/safing/portbase/dataroot"
|
"github.com/safing/portbase/dataroot"
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
"github.com/safing/portmaster/core"
|
"github.com/safing/portmaster/core/base"
|
||||||
|
|
||||||
// module dependencies
|
// module dependencies
|
||||||
_ "github.com/safing/portbase/database/storage/hashmap"
|
_ "github.com/safing/portbase/database/storage/hashmap"
|
||||||
|
@ -57,10 +57,10 @@ func TestMainWithHooks(m *testing.M, module *modules.Module, afterStartFn, befor
|
||||||
module.Enable()
|
module.Enable()
|
||||||
|
|
||||||
// switch databases to memory only
|
// switch databases to memory only
|
||||||
core.DefaultDatabaseStorageType = "hashmap"
|
base.DefaultDatabaseStorageType = "hashmap"
|
||||||
|
|
||||||
// switch API to high port
|
// switch API to high port
|
||||||
core.DefaultAPIListenAddress = "127.0.0.1:10817"
|
base.DefaultAPIListenAddress = "127.0.0.1:10817"
|
||||||
|
|
||||||
// set log level
|
// set log level
|
||||||
log.SetLogLevel(log.TraceLevel)
|
log.SetLogLevel(log.TraceLevel)
|
||||||
|
|
|
@ -3,17 +3,18 @@ package firewall
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/safing/portmaster/nameserver/nsutil"
|
||||||
"github.com/safing/portmaster/network"
|
"github.com/safing/portmaster/network"
|
||||||
"github.com/safing/portmaster/profile/endpoints"
|
"github.com/safing/portmaster/profile/endpoints"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PreventBypassing checks if the connection should be denied or permitted
|
// PreventBypassing checks if the connection should be denied or permitted
|
||||||
// based on some bypass protection checks.
|
// based on some bypass protection checks.
|
||||||
func PreventBypassing(conn *network.Connection) (endpoints.EPResult, string) {
|
func PreventBypassing(conn *network.Connection) (endpoints.EPResult, string, nsutil.Responder) {
|
||||||
// Block firefox canary domain to disable DoH
|
// Block firefox canary domain to disable DoH
|
||||||
if strings.ToLower(conn.Entity.Domain) == "use-application-dns.net." {
|
if strings.ToLower(conn.Entity.Domain) == "use-application-dns.net." {
|
||||||
return endpoints.Denied, "blocked canary domain to prevent enabling DNS-over-HTTPs"
|
return endpoints.Denied, "blocked canary domain to prevent enabling DNS-over-HTTPs", nsutil.NxDomain()
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoints.NoMatch, ""
|
return endpoints.NoMatch, "", nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/safing/portbase/api"
|
||||||
"github.com/safing/portbase/config"
|
"github.com/safing/portbase/config"
|
||||||
|
"github.com/safing/portmaster/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configuration Keys
|
// Configuration Keys
|
||||||
var (
|
var (
|
||||||
CfgOptionEnableFilterKey = "filter/enable"
|
CfgOptionEnableFilterKey = "filter/enable"
|
||||||
|
|
||||||
CfgOptionPermanentVerdictsKey = "filter/permanentVerdicts"
|
CfgOptionAskWithSystemNotificationsKey = "filter/askWithSystemNotifications"
|
||||||
permanentVerdicts config.BoolOption
|
CfgOptionAskWithSystemNotificationsOrder = 2
|
||||||
|
|
||||||
CfgOptionPromptTimeoutKey = "filter/promptTimeout"
|
CfgOptionAskTimeoutKey = "filter/askTimeout"
|
||||||
promptTimeout config.IntOption
|
CfgOptionAskTimeoutOrder = 3
|
||||||
|
askTimeout config.IntOption
|
||||||
|
|
||||||
|
CfgOptionPermanentVerdictsKey = "filter/permanentVerdicts"
|
||||||
|
CfgOptionPermanentVerdictsOrder = 128
|
||||||
|
permanentVerdicts config.BoolOption
|
||||||
|
|
||||||
devMode config.BoolOption
|
devMode config.BoolOption
|
||||||
apiListenAddress config.StringOption
|
apiListenAddress config.StringOption
|
||||||
|
@ -23,6 +30,7 @@ func registerConfig() error {
|
||||||
Name: "Permanent Verdicts",
|
Name: "Permanent Verdicts",
|
||||||
Key: CfgOptionPermanentVerdictsKey,
|
Key: CfgOptionPermanentVerdictsKey,
|
||||||
Description: "With permanent verdicts, control of a connection is fully handed back to the OS after the initial decision. This brings a great performance increase, but makes it impossible to change the decision of a link later on.",
|
Description: "With permanent verdicts, control of a connection is fully handed back to the OS after the initial decision. This brings a great performance increase, but makes it impossible to change the decision of a link later on.",
|
||||||
|
Order: CfgOptionPermanentVerdictsOrder,
|
||||||
OptType: config.OptTypeBool,
|
OptType: config.OptTypeBool,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelExperimental,
|
ReleaseLevel: config.ReleaseLevelExperimental,
|
||||||
|
@ -34,21 +42,36 @@ func registerConfig() error {
|
||||||
permanentVerdicts = config.Concurrent.GetAsBool(CfgOptionPermanentVerdictsKey, true)
|
permanentVerdicts = config.Concurrent.GetAsBool(CfgOptionPermanentVerdictsKey, true)
|
||||||
|
|
||||||
err = config.Register(&config.Option{
|
err = config.Register(&config.Option{
|
||||||
Name: "Timeout for prompt notifications",
|
Name: "Ask with System Notifications",
|
||||||
Key: CfgOptionPromptTimeoutKey,
|
Key: CfgOptionAskWithSystemNotificationsKey,
|
||||||
Description: "Amount of time how long Portmaster will wait for a response when prompting about a connection via a notification. In seconds.",
|
Description: `Ask about connections using your operating system's notification system. For this to be enabled, the setting "Use System Notifications" must enabled too. This only affects questions from the Privacy Filter, and does not affect alerts from the Portmaster.`,
|
||||||
|
Order: CfgOptionAskWithSystemNotificationsOrder,
|
||||||
|
OptType: config.OptTypeBool,
|
||||||
|
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||||
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
DefaultValue: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = config.Register(&config.Option{
|
||||||
|
Name: "Timeout for Ask Notifications",
|
||||||
|
Key: CfgOptionAskTimeoutKey,
|
||||||
|
Description: "Amount of time (in seconds) how long the Portmaster will wait for a response when prompting about a connection via a notification. Please note that system notifications might not respect this or have it's own limits.",
|
||||||
|
Order: CfgOptionAskTimeoutOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelUser,
|
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||||
ReleaseLevel: config.ReleaseLevelBeta,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
DefaultValue: 60,
|
DefaultValue: 60,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
promptTimeout = config.Concurrent.GetAsInt(CfgOptionPromptTimeoutKey, 60)
|
askTimeout = config.Concurrent.GetAsInt(CfgOptionAskTimeoutKey, 60)
|
||||||
|
|
||||||
devMode = config.Concurrent.GetAsBool("core/devMode", false)
|
devMode = config.Concurrent.GetAsBool(core.CfgDevModeKey, false)
|
||||||
apiListenAddress = config.GetAsString("api/listenAddress", "")
|
apiListenAddress = config.GetAsString(api.CfgDefaultListenAddressKey, "")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
47
firewall/filter.go
Normal file
47
firewall/filter.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/safing/portbase/config"
|
||||||
|
"github.com/safing/portbase/modules/subsystems"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/modules"
|
||||||
|
|
||||||
|
// module dependencies
|
||||||
|
_ "github.com/safing/portmaster/core"
|
||||||
|
_ "github.com/safing/portmaster/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
filterModule *modules.Module
|
||||||
|
filterEnabled config.BoolOption
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
filterModule = modules.Register("filter", filterPrep, nil, nil, "core", "intel")
|
||||||
|
subsystems.Register(
|
||||||
|
"filter",
|
||||||
|
"Privacy Filter",
|
||||||
|
"DNS and Network Filter",
|
||||||
|
filterModule,
|
||||||
|
"config:filter/",
|
||||||
|
&config.Option{
|
||||||
|
Name: "Enable Privacy Filter",
|
||||||
|
Key: CfgOptionEnableFilterKey,
|
||||||
|
Description: "Enable the Privacy Filter Subsystem to filter DNS queries and network requests.",
|
||||||
|
OptType: config.OptTypeBool,
|
||||||
|
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||||
|
ReleaseLevel: config.ReleaseLevelBeta,
|
||||||
|
DefaultValue: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterPrep() (err error) {
|
||||||
|
err = registerConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
filterEnabled = config.GetAsBool(CfgOptionEnableFilterKey, true)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -7,9 +7,6 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/safing/portbase/config"
|
|
||||||
"github.com/safing/portbase/modules/subsystems"
|
|
||||||
|
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
"github.com/safing/portmaster/firewall/inspection"
|
"github.com/safing/portmaster/firewall/inspection"
|
||||||
|
@ -23,7 +20,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
module *modules.Module
|
interceptionModule *modules.Module
|
||||||
|
|
||||||
// localNet net.IPNet
|
// localNet net.IPNet
|
||||||
// localhost net.IP
|
// localhost net.IP
|
||||||
|
@ -45,33 +42,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
module = modules.Register("filter", prep, start, stop, "core", "network", "nameserver", "intel")
|
interceptionModule = modules.Register("interception", interceptionPrep, interceptionStart, interceptionStop, "base")
|
||||||
subsystems.Register(
|
|
||||||
"filter",
|
|
||||||
"Privacy Filter",
|
|
||||||
"DNS and Network Filter",
|
|
||||||
module,
|
|
||||||
"config:filter/",
|
|
||||||
&config.Option{
|
|
||||||
Name: "Enable Privacy Filter",
|
|
||||||
Key: CfgOptionEnableFilterKey,
|
|
||||||
Description: "Enable the Privacy Filter Subsystem to filter DNS queries and network requests.",
|
|
||||||
OptType: config.OptTypeBool,
|
|
||||||
ExpertiseLevel: config.ExpertiseLevelUser,
|
|
||||||
ReleaseLevel: config.ReleaseLevelBeta,
|
|
||||||
DefaultValue: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
network.SetDefaultFirewallHandler(defaultHandler)
|
network.SetDefaultFirewallHandler(defaultHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prep() (err error) {
|
func interceptionPrep() (err error) {
|
||||||
err = registerConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = prepAPIAuth()
|
err = prepAPIAuth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -101,20 +77,20 @@ func prep() (err error) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() error {
|
func interceptionStart() error {
|
||||||
startAPIAuth()
|
startAPIAuth()
|
||||||
|
|
||||||
module.StartWorker("stat logger", func(ctx context.Context) error {
|
interceptionModule.StartWorker("stat logger", func(ctx context.Context) error {
|
||||||
statLogger()
|
statLogger()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
module.StartWorker("packet handler", func(ctx context.Context) error {
|
interceptionModule.StartWorker("packet handler", func(ctx context.Context) error {
|
||||||
run()
|
run()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
module.StartWorker("ports state cleaner", func(ctx context.Context) error {
|
interceptionModule.StartWorker("ports state cleaner", func(ctx context.Context) error {
|
||||||
portsInUseCleaner()
|
portsInUseCleaner()
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -122,7 +98,7 @@ func start() error {
|
||||||
return interception.Start()
|
return interception.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop() error {
|
func interceptionStop() error {
|
||||||
return interception.Stop()
|
return interception.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,6 +224,15 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if filtering is enabled
|
||||||
|
if !filterEnabled() {
|
||||||
|
conn.Inspecting = false
|
||||||
|
conn.SetVerdict(network.VerdictAccept, "privacy filter disabled", nil)
|
||||||
|
conn.StopFirewallHandler()
|
||||||
|
issueVerdict(conn, pkt, 0, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
log.Tracer(pkt.Ctx()).Trace("filter: starting decision process")
|
log.Tracer(pkt.Ctx()).Trace("filter: starting decision process")
|
||||||
DecideOnConnection(conn, pkt)
|
DecideOnConnection(conn, pkt)
|
||||||
conn.Inspecting = false // TODO: enable inspecting again
|
conn.Inspecting = false // TODO: enable inspecting again
|
||||||
|
@ -350,7 +335,7 @@ func issueVerdict(conn *network.Connection, pkt packet.Packet, verdict network.V
|
||||||
func run() {
|
func run() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-module.Stopping():
|
case <-interceptionModule.Stopping():
|
||||||
return
|
return
|
||||||
case pkt := <-interception.Packets:
|
case pkt := <-interception.Packets:
|
||||||
handlePacket(pkt)
|
handlePacket(pkt)
|
||||||
|
@ -361,7 +346,7 @@ func run() {
|
||||||
func statLogger() {
|
func statLogger() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-module.Stopping():
|
case <-interceptionModule.Stopping():
|
||||||
return
|
return
|
||||||
case <-time.After(10 * time.Second):
|
case <-time.After(10 * time.Second):
|
||||||
log.Tracef(
|
log.Tracef(
|
|
@ -45,14 +45,14 @@ func init() {
|
||||||
"mangle C171 -m mark --mark 0 -j NFQUEUE --queue-num 17140 --queue-bypass",
|
"mangle C171 -m mark --mark 0 -j NFQUEUE --queue-num 17140 --queue-bypass",
|
||||||
|
|
||||||
"filter C17 -m mark --mark 0 -j DROP",
|
"filter C17 -m mark --mark 0 -j DROP",
|
||||||
"filter C17 -m mark --mark 1700 -j ACCEPT",
|
"filter C17 -m mark --mark 1700 -j RETURN",
|
||||||
"filter C17 -m mark --mark 1701 -j REJECT --reject-with icmp-host-prohibited",
|
"filter C17 -m mark --mark 1701 -j REJECT --reject-with icmp-host-prohibited",
|
||||||
"filter C17 -m mark --mark 1702 -j DROP",
|
"filter C17 -m mark --mark 1702 -j DROP",
|
||||||
"filter C17 -j CONNMARK --save-mark",
|
"filter C17 -j CONNMARK --save-mark",
|
||||||
"filter C17 -m mark --mark 1710 -j ACCEPT",
|
"filter C17 -m mark --mark 1710 -j RETURN",
|
||||||
"filter C17 -m mark --mark 1711 -j REJECT --reject-with icmp-host-prohibited",
|
"filter C17 -m mark --mark 1711 -j REJECT --reject-with icmp-host-prohibited",
|
||||||
"filter C17 -m mark --mark 1712 -j DROP",
|
"filter C17 -m mark --mark 1712 -j DROP",
|
||||||
"filter C17 -m mark --mark 1717 -j ACCEPT",
|
"filter C17 -m mark --mark 1717 -j RETURN",
|
||||||
}
|
}
|
||||||
|
|
||||||
v4once = []string{
|
v4once = []string{
|
||||||
|
@ -80,14 +80,14 @@ func init() {
|
||||||
"mangle C171 -m mark --mark 0 -j NFQUEUE --queue-num 17160 --queue-bypass",
|
"mangle C171 -m mark --mark 0 -j NFQUEUE --queue-num 17160 --queue-bypass",
|
||||||
|
|
||||||
"filter C17 -m mark --mark 0 -j DROP",
|
"filter C17 -m mark --mark 0 -j DROP",
|
||||||
"filter C17 -m mark --mark 1700 -j ACCEPT",
|
"filter C17 -m mark --mark 1700 -j RETURN",
|
||||||
"filter C17 -m mark --mark 1701 -j REJECT --reject-with icmp6-adm-prohibited",
|
"filter C17 -m mark --mark 1701 -j REJECT --reject-with icmp6-adm-prohibited",
|
||||||
"filter C17 -m mark --mark 1702 -j DROP",
|
"filter C17 -m mark --mark 1702 -j DROP",
|
||||||
"filter C17 -j CONNMARK --save-mark",
|
"filter C17 -j CONNMARK --save-mark",
|
||||||
"filter C17 -m mark --mark 1710 -j ACCEPT",
|
"filter C17 -m mark --mark 1710 -j RETURN",
|
||||||
"filter C17 -m mark --mark 1711 -j REJECT --reject-with icmp6-adm-prohibited",
|
"filter C17 -m mark --mark 1711 -j REJECT --reject-with icmp6-adm-prohibited",
|
||||||
"filter C17 -m mark --mark 1712 -j DROP",
|
"filter C17 -m mark --mark 1712 -j DROP",
|
||||||
"filter C17 -m mark --mark 1717 -j ACCEPT",
|
"filter C17 -m mark --mark 1717 -j RETURN",
|
||||||
}
|
}
|
||||||
|
|
||||||
v6once = []string{
|
v6once = []string{
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -163,8 +163,8 @@ func checkConnectionType(conn *network.Connection, _ packet.Packet) bool {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case network.PeerLAN, network.PeerInternet, network.PeerInvalid:
|
case network.PeerInternet:
|
||||||
// Important: PeerHost is and should be missing!
|
// BlockP2P only applies to connections to the Internet
|
||||||
if p.BlockP2P() {
|
if p.BlockP2P() {
|
||||||
conn.Block("direct connections (P2P) blocked")
|
conn.Block("direct connections (P2P) blocked")
|
||||||
return true
|
return true
|
||||||
|
@ -216,13 +216,13 @@ func checkConnectionScope(conn *network.Connection, _ packet.Packet) bool {
|
||||||
func checkBypassPrevention(conn *network.Connection, _ packet.Packet) bool {
|
func checkBypassPrevention(conn *network.Connection, _ packet.Packet) bool {
|
||||||
if conn.Process().Profile().PreventBypassing() {
|
if conn.Process().Profile().PreventBypassing() {
|
||||||
// check for bypass protection
|
// check for bypass protection
|
||||||
result, reason := PreventBypassing(conn)
|
result, reason, reasonCtx := PreventBypassing(conn)
|
||||||
switch result {
|
switch result {
|
||||||
case endpoints.Denied:
|
case endpoints.Denied:
|
||||||
conn.Block("bypass prevention: " + reason)
|
conn.BlockWithContext("bypass prevention: "+reason, reasonCtx)
|
||||||
return true
|
return true
|
||||||
case endpoints.Permitted:
|
case endpoints.Permitted:
|
||||||
conn.Accept("bypass prevention: " + reason)
|
conn.AcceptWithContext("bypass prevention: "+reason, reasonCtx)
|
||||||
return true
|
return true
|
||||||
case endpoints.NoMatch:
|
case endpoints.NoMatch:
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ func GetPermittedPort() uint16 {
|
||||||
func portsInUseCleaner() {
|
func portsInUseCleaner() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-module.Stopping():
|
case <-interceptionModule.Stopping():
|
||||||
return
|
return
|
||||||
case <-time.After(cleanerTickDuration):
|
case <-time.After(cleanerTickDuration):
|
||||||
cleanPortsInUse()
|
cleanPortsInUse()
|
||||||
|
|
|
@ -26,16 +26,16 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func prompt(conn *network.Connection, pkt packet.Packet) { //nolint:gocognit // TODO
|
func prompt(conn *network.Connection, pkt packet.Packet) { //nolint:gocognit // TODO
|
||||||
nTTL := time.Duration(promptTimeout()) * time.Second
|
nTTL := time.Duration(askTimeout()) * time.Second
|
||||||
|
|
||||||
// first check if there is an existing notification for this.
|
// first check if there is an existing notification for this.
|
||||||
// build notification ID
|
// build notification ID
|
||||||
var nID string
|
var nID string
|
||||||
switch {
|
switch {
|
||||||
case conn.Inbound, conn.Entity.Domain == "": // connection to/from IP
|
case conn.Inbound, conn.Entity.Domain == "": // connection to/from IP
|
||||||
nID = fmt.Sprintf("firewall-prompt-%d-%s-%s", conn.Process().Pid, conn.Scope, pkt.Info().RemoteIP())
|
nID = fmt.Sprintf("filter:prompt-%d-%s-%s", conn.Process().Pid, conn.Scope, pkt.Info().RemoteIP())
|
||||||
default: // connection to domain
|
default: // connection to domain
|
||||||
nID = fmt.Sprintf("firewall-prompt-%d-%s", conn.Process().Pid, conn.Scope)
|
nID = fmt.Sprintf("filter:prompt-%d-%s", conn.Process().Pid, conn.Scope)
|
||||||
}
|
}
|
||||||
n := notifications.Get(nID)
|
n := notifications.Get(nID)
|
||||||
saveResponse := true
|
saveResponse := true
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/safing/portbase/log"
|
"github.com/safing/portbase/log"
|
||||||
|
"github.com/safing/portmaster/nameserver/nsutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListMatch represents an entity that has been
|
// ListMatch represents an entity that has been
|
||||||
|
@ -62,9 +63,10 @@ func (br ListBlockReason) MarshalJSON() ([]byte, error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToRRs returns a set of dns TXT records that describe the
|
// GetExtraRR implements the nsutil.RRProvider interface
|
||||||
// block reason.
|
// and adds additional TXT records justifying the reason
|
||||||
func (br ListBlockReason) ToRRs() []dns.RR {
|
// the request was blocked.
|
||||||
|
func (br ListBlockReason) GetExtraRR(_ *dns.Msg, _ string, _ interface{}) []dns.RR {
|
||||||
rrs := make([]dns.RR, 0, len(br))
|
rrs := make([]dns.RR, 0, len(br))
|
||||||
|
|
||||||
for _, lm := range br {
|
for _, lm := range br {
|
||||||
|
@ -95,3 +97,5 @@ func (br ListBlockReason) ToRRs() []dns.RR {
|
||||||
|
|
||||||
return rrs
|
return rrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ nsutil.RRProvider = ListBlockReason(nil)
|
||||||
|
|
|
@ -261,9 +261,6 @@ func (e *Entity) mergeList(key string, list []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
e.ListOccurences[key] = mergeStringList(e.ListOccurences[key], list)
|
e.ListOccurences[key] = mergeStringList(e.ListOccurences[key], list)
|
||||||
|
|
||||||
//e.Lists = mergeStringList(e.Lists, list)
|
|
||||||
//e.ListsMap = buildLookupMap(e.Lists)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Entity) getDomainLists() {
|
func (e *Entity) getDomainLists() {
|
||||||
|
@ -289,8 +286,6 @@ func (e *Entity) getDomainLists() {
|
||||||
for _, domain := range domainsToInspect {
|
for _, domain := range domainsToInspect {
|
||||||
subdomains := splitDomain(domain)
|
subdomains := splitDomain(domain)
|
||||||
domains = append(domains, subdomains...)
|
domains = append(domains, subdomains...)
|
||||||
|
|
||||||
log.Tracef("intel: subdomain list resolving is enabled: %s => %v", domains, subdomains)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
domains = domainsToInspect
|
domains = domainsToInspect
|
||||||
|
@ -446,8 +441,8 @@ func (e *Entity) MatchLists(lists []string) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
makeDistinct(e.BlockedByLists)
|
e.BlockedByLists = makeDistinct(e.BlockedByLists)
|
||||||
makeDistinct(e.BlockedEntities)
|
e.BlockedEntities = makeDistinct(e.BlockedEntities)
|
||||||
|
|
||||||
return len(e.BlockedByLists) > 0
|
return len(e.BlockedByLists) > 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ var (
|
||||||
func init() {
|
func init() {
|
||||||
ignoreNetEnvEvents.Set()
|
ignoreNetEnvEvents.Set()
|
||||||
|
|
||||||
module = modules.Register("filterlists", prep, start, nil, "core", "netenv")
|
module = modules.Register("filterlists", prep, start, stop, "base", "updates")
|
||||||
}
|
}
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
|
@ -98,3 +98,8 @@ func start() error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stop() error {
|
||||||
|
filterListsLoaded = make(chan struct{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
module = modules.Register("geoip", prep, nil, nil, "core")
|
module = modules.Register("geoip", prep, nil, nil, "base", "updates")
|
||||||
}
|
}
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package nameserver
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -13,6 +12,7 @@ import (
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
"github.com/safing/portmaster/detection/dga"
|
"github.com/safing/portmaster/detection/dga"
|
||||||
"github.com/safing/portmaster/firewall"
|
"github.com/safing/portmaster/firewall"
|
||||||
|
"github.com/safing/portmaster/nameserver/nsutil"
|
||||||
"github.com/safing/portmaster/netenv"
|
"github.com/safing/portmaster/netenv"
|
||||||
"github.com/safing/portmaster/network"
|
"github.com/safing/portmaster/network"
|
||||||
"github.com/safing/portmaster/network/netutils"
|
"github.com/safing/portmaster/network/netutils"
|
||||||
|
@ -32,7 +32,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
module = modules.Register("nameserver", prep, start, stop, "core", "resolver", "network", "netenv")
|
module = modules.Register("nameserver", prep, start, stop, "core", "resolver")
|
||||||
subsystems.Register(
|
subsystems.Register(
|
||||||
"dns",
|
"dns",
|
||||||
"Secure DNS",
|
"Secure DNS",
|
||||||
|
@ -89,29 +89,6 @@ func stop() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func returnNXDomain(w dns.ResponseWriter, query *dns.Msg, reason string, reasonContext interface{}) {
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.SetRcode(query, dns.RcodeNameError)
|
|
||||||
rr, _ := dns.NewRR("portmaster.block-reason. 0 IN TXT " + fmt.Sprintf("%q", reason))
|
|
||||||
m.Extra = []dns.RR{rr}
|
|
||||||
|
|
||||||
if reasonContext != nil {
|
|
||||||
if v, ok := reasonContext.(interface {
|
|
||||||
ToRRs() []dns.RR
|
|
||||||
}); ok {
|
|
||||||
m.Extra = append(m.Extra, v.ToRRs()...)
|
|
||||||
} else if v, ok := reasonContext.(interface {
|
|
||||||
ToRR() dns.RR
|
|
||||||
}); ok {
|
|
||||||
m.Extra = append(m.Extra, v.ToRR())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.WriteMsg(m); err != nil {
|
|
||||||
log.Errorf("nameserver: failed to send response: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func returnServerFailure(w dns.ResponseWriter, query *dns.Msg) {
|
func returnServerFailure(w dns.ResponseWriter, query *dns.Msg) {
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetRcode(query, dns.RcodeServerFailure)
|
m.SetRcode(query, dns.RcodeServerFailure)
|
||||||
|
@ -145,7 +122,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
|
||||||
if question.Qclass != dns.ClassINET {
|
if question.Qclass != dns.ClassINET {
|
||||||
// we only serve IN records, return nxdomain
|
// we only serve IN records, return nxdomain
|
||||||
log.Warningf("nameserver: only IN record requests are supported but received Qclass %d, returning NXDOMAIN", question.Qclass)
|
log.Warningf("nameserver: only IN record requests are supported but received Qclass %d, returning NXDOMAIN", question.Qclass)
|
||||||
returnNXDomain(w, query, "wrong type", nil)
|
sendResponse(w, query, 0, "qclass not served", nsutil.Refused())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +162,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
|
||||||
// check if valid domain name
|
// check if valid domain name
|
||||||
if !netutils.IsValidFqdn(q.FQDN) {
|
if !netutils.IsValidFqdn(q.FQDN) {
|
||||||
log.Debugf("nameserver: domain name %s is invalid, returning nxdomain", q.FQDN)
|
log.Debugf("nameserver: domain name %s is invalid, returning nxdomain", q.FQDN)
|
||||||
returnNXDomain(w, query, "invalid domain", nil)
|
sendResponse(w, query, 0, "invalid FQDN", nsutil.Refused())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +201,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
|
||||||
// NOTE(ppacher): saving unknown process connection might end up in a lot of
|
// NOTE(ppacher): saving unknown process connection might end up in a lot of
|
||||||
// processes. Consider disabling that via config.
|
// processes. Consider disabling that via config.
|
||||||
conn.Failed("Unknown process")
|
conn.Failed("Unknown process")
|
||||||
returnNXDomain(w, query, "unknown process", conn.ReasonContext)
|
sendResponse(w, query, conn.Verdict, conn.Reason, conn.ReasonContext)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +215,7 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
|
||||||
if lms < 10 {
|
if lms < 10 {
|
||||||
tracer.Warningf("nameserver: possible data tunnel by %s: %s has lms score of %f, returning nxdomain", conn.Process(), q.FQDN, lms)
|
tracer.Warningf("nameserver: possible data tunnel by %s: %s has lms score of %f, returning nxdomain", conn.Process(), q.FQDN, lms)
|
||||||
conn.Block("Possible data tunnel")
|
conn.Block("Possible data tunnel")
|
||||||
returnNXDomain(w, query, "lms", conn.ReasonContext)
|
sendResponse(w, query, conn.Verdict, conn.Reason, conn.ReasonContext)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,13 +225,34 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
|
||||||
switch conn.Verdict {
|
switch conn.Verdict {
|
||||||
case network.VerdictBlock:
|
case network.VerdictBlock:
|
||||||
tracer.Infof("nameserver: %s blocked, returning nxdomain", conn)
|
tracer.Infof("nameserver: %s blocked, returning nxdomain", conn)
|
||||||
returnNXDomain(w, query, conn.Reason, conn.ReasonContext)
|
sendResponse(w, query, conn.Verdict, conn.Reason, conn.ReasonContext)
|
||||||
return nil
|
return nil
|
||||||
case network.VerdictDrop, network.VerdictFailed:
|
case network.VerdictDrop, network.VerdictFailed:
|
||||||
tracer.Infof("nameserver: %s dropped, not replying", conn)
|
tracer.Infof("nameserver: %s dropped, not replying", conn)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the firewall now decided on the connection and set it to accept
|
||||||
|
// If we have a reason context and that context implements nsutil.Responder
|
||||||
|
// we may need to responde with something else.
|
||||||
|
// A reason for this might be that the request is sink-holed to a forced
|
||||||
|
// ip address in which case we "Accept" it but handle the resolving
|
||||||
|
// differently.
|
||||||
|
if responder, ok := conn.ReasonContext.(nsutil.Responder); ok {
|
||||||
|
tracer.Infof("nameserver: %s handing over to reason-responder: %s", q.FQDN, conn.Reason)
|
||||||
|
reply := responder.ReplyWithDNS(query, conn.Reason, conn.ReasonContext)
|
||||||
|
if err := w.WriteMsg(reply); err != nil {
|
||||||
|
log.Warningf("nameserver: failed to return response %s%s to %s: %s", q.FQDN, q.QType, conn.Process(), err)
|
||||||
|
} else {
|
||||||
|
tracer.Debugf("nameserver: returning response %s%s to %s", q.FQDN, q.QType, conn.Process())
|
||||||
|
}
|
||||||
|
|
||||||
|
// save dns request as open
|
||||||
|
network.SaveOpenDNSRequest(conn)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// resolve
|
// resolve
|
||||||
rrCache, err := resolver.Resolve(ctx, q)
|
rrCache, err := resolver.Resolve(ctx, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -267,13 +265,13 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, query *dns.Msg) er
|
||||||
conn.Failed("failed to resolve: " + err.Error())
|
conn.Failed("failed to resolve: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
returnNXDomain(w, query, conn.Reason, conn.ReasonContext)
|
sendResponse(w, query, conn.Verdict, conn.Reason, conn.ReasonContext)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rrCache = firewall.DecideOnResolvedDNS(conn, q, rrCache)
|
rrCache = firewall.DecideOnResolvedDNS(conn, q, rrCache)
|
||||||
if rrCache == nil {
|
if rrCache == nil {
|
||||||
returnNXDomain(w, query, conn.Reason, conn.ReasonContext)
|
sendResponse(w, query, conn.Verdict, conn.Reason, conn.ReasonContext)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
92
nameserver/nsutil/nsutil.go
Normal file
92
nameserver/nsutil/nsutil.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package nsutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/safing/portbase/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Responder defines the interface that any block/deny reason interface
|
||||||
|
// may implement to support sending custom DNS responses for a given reason.
|
||||||
|
// That is, if a reason context implements the Responder interface the
|
||||||
|
// ReplyWithDNS method will be called instead of creating the default
|
||||||
|
// zero-ip response.
|
||||||
|
type Responder interface {
|
||||||
|
// ReplyWithDNS is called when a DNS response to a DNS message is
|
||||||
|
// crafted because the request is either denied or blocked.
|
||||||
|
ReplyWithDNS(query *dns.Msg, reason string, reasonCtx interface{}) *dns.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// RRProvider defines the interface that any block/deny reason interface
|
||||||
|
// may implement to support adding additional DNS resource records to
|
||||||
|
// the DNS responses extra (additional) section.
|
||||||
|
type RRProvider interface {
|
||||||
|
// GetExtraRR is called when a DNS response to a DNS message is
|
||||||
|
// crafted because the request is either denied or blocked.
|
||||||
|
GetExtraRR(query *dns.Msg, reason string, reasonCtx interface{}) []dns.RR
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponderFunc is a convenience type to use a function
|
||||||
|
// directly as a Responder.
|
||||||
|
type ResponderFunc func(query *dns.Msg, reason string, reasonCtx interface{}) *dns.Msg
|
||||||
|
|
||||||
|
// ReplyWithDNS implements the Responder interface and calls rf.
|
||||||
|
func (rf ResponderFunc) ReplyWithDNS(query *dns.Msg, reason string, reasonCtx interface{}) *dns.Msg {
|
||||||
|
return rf(query, reason, reasonCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZeroIP is a ResponderFunc than replies with either 0.0.0.0 or :: for
|
||||||
|
// each A or AAAA question respectively.
|
||||||
|
func ZeroIP() ResponderFunc {
|
||||||
|
return func(query *dns.Msg, _ string, _ interface{}) *dns.Msg {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
hasErr := false
|
||||||
|
|
||||||
|
for _, question := range query.Question {
|
||||||
|
var rr dns.RR
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch question.Qtype {
|
||||||
|
case dns.TypeA:
|
||||||
|
rr, err = dns.NewRR(question.Name + " 0 IN A 0.0.0.0")
|
||||||
|
case dns.TypeAAAA:
|
||||||
|
rr, err = dns.NewRR(question.Name + " 0 IN AAAA ::")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("nameserver: failed to create zero-ip response for %s: %s", question.Name, err)
|
||||||
|
hasErr = true
|
||||||
|
} else {
|
||||||
|
m.Answer = append(m.Answer, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasErr && len(m.Answer) == 0 {
|
||||||
|
m.SetRcode(query, dns.RcodeServerFailure)
|
||||||
|
} else {
|
||||||
|
m.SetRcode(query, dns.RcodeSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NxDomain returns a ResponderFunc that replies with NXDOMAIN.
|
||||||
|
func NxDomain() ResponderFunc {
|
||||||
|
return func(query *dns.Msg, _ string, _ interface{}) *dns.Msg {
|
||||||
|
return new(dns.Msg).SetRcode(query, dns.RcodeNameError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refused returns a ResponderFunc that replies with REFUSED.
|
||||||
|
func Refused() ResponderFunc {
|
||||||
|
return func(query *dns.Msg, _ string, _ interface{}) *dns.Msg {
|
||||||
|
return new(dns.Msg).SetRcode(query, dns.RcodeRefused)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeFail returns a ResponderFunc that replies with SERVFAIL.
|
||||||
|
func ServeFail() ResponderFunc {
|
||||||
|
return func(query *dns.Msg, _ string, _ interface{}) *dns.Msg {
|
||||||
|
return new(dns.Msg).SetRcode(query, dns.RcodeServerFailure)
|
||||||
|
}
|
||||||
|
}
|
36
nameserver/response.go
Normal file
36
nameserver/response.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package nameserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/safing/portbase/log"
|
||||||
|
"github.com/safing/portmaster/nameserver/nsutil"
|
||||||
|
"github.com/safing/portmaster/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sendResponse sends a response to query using w. If reasonCtx is not
|
||||||
|
// nil and implements either the Responder or RRProvider interface then
|
||||||
|
// those functions are used to craft a DNS response. If reasonCtx is nil
|
||||||
|
// or does not implement the Responder interface and verdict is not set
|
||||||
|
// to failed a ZeroIP response will be sent. If verdict is set to failed
|
||||||
|
// then a ServFail will be sent instead.
|
||||||
|
func sendResponse(w dns.ResponseWriter, query *dns.Msg, verdict network.Verdict, reason string, reasonCtx interface{}) {
|
||||||
|
responder, ok := reasonCtx.(nsutil.Responder)
|
||||||
|
if !ok {
|
||||||
|
if verdict == network.VerdictFailed {
|
||||||
|
responder = nsutil.ServeFail()
|
||||||
|
} else {
|
||||||
|
responder = nsutil.ZeroIP()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reply := responder.ReplyWithDNS(query, reason, reasonCtx)
|
||||||
|
|
||||||
|
if extra, ok := reasonCtx.(nsutil.RRProvider); ok {
|
||||||
|
rrs := extra.GetExtraRR(query, reason, reasonCtx)
|
||||||
|
reply.Extra = append(reply.Extra, rrs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.WriteMsg(reply); err != nil {
|
||||||
|
log.Errorf("nameserver: failed to send response: %s", err)
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,8 +41,14 @@ func cleanConnections() (activePIDs map[int]struct{}) {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
deleteOlderThan := time.Now().Add(-deleteConnsAfterEndedThreshold).Unix()
|
deleteOlderThan := time.Now().Add(-deleteConnsAfterEndedThreshold).Unix()
|
||||||
|
|
||||||
// network connections
|
// lock both together because we cannot fully guarantee in which map a connection lands
|
||||||
|
// of course every connection should land in the correct map, but this increases resilience
|
||||||
connsLock.Lock()
|
connsLock.Lock()
|
||||||
|
defer connsLock.Unlock()
|
||||||
|
dnsConnsLock.Lock()
|
||||||
|
defer dnsConnsLock.Unlock()
|
||||||
|
|
||||||
|
// network connections
|
||||||
for key, conn := range conns {
|
for key, conn := range conns {
|
||||||
conn.Lock()
|
conn.Lock()
|
||||||
|
|
||||||
|
@ -67,10 +73,8 @@ func cleanConnections() (activePIDs map[int]struct{}) {
|
||||||
|
|
||||||
conn.Unlock()
|
conn.Unlock()
|
||||||
}
|
}
|
||||||
connsLock.Unlock()
|
|
||||||
|
|
||||||
// dns requests
|
// dns requests
|
||||||
dnsConnsLock.Lock()
|
|
||||||
for _, conn := range dnsConns {
|
for _, conn := range dnsConns {
|
||||||
conn.Lock()
|
conn.Lock()
|
||||||
|
|
||||||
|
@ -82,7 +86,6 @@ func cleanConnections() (activePIDs map[int]struct{}) {
|
||||||
|
|
||||||
conn.Unlock()
|
conn.Unlock()
|
||||||
}
|
}
|
||||||
dnsConnsLock.Unlock()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
|
@ -146,13 +146,13 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp := time.Now().Unix()
|
|
||||||
return &Connection{
|
return &Connection{
|
||||||
ID: pkt.GetConnectionID(),
|
ID: pkt.GetConnectionID(),
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
|
Inbound: inbound,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
process: proc,
|
process: proc,
|
||||||
Started: timestamp,
|
Started: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,11 +236,11 @@ func (conn *Connection) Failed(reason string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetVerdict sets a new verdict for the connection, making sure it does not interfere with previous verdicts.
|
// SetVerdict sets a new verdict for the connection, making sure it does not interfere with previous verdicts.
|
||||||
func (conn *Connection) SetVerdict(newVerdict Verdict, reason string, ctx interface{}) (ok bool) {
|
func (conn *Connection) SetVerdict(newVerdict Verdict, reason string, reasonCtx interface{}) (ok bool) {
|
||||||
if newVerdict >= conn.Verdict {
|
if newVerdict >= conn.Verdict {
|
||||||
conn.Verdict = newVerdict
|
conn.Verdict = newVerdict
|
||||||
conn.Reason = reason
|
conn.Reason = reason
|
||||||
conn.ReasonContext = ctx
|
conn.ReasonContext = reasonCtx
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -77,9 +77,11 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
|
||||||
if slashes <= 1 {
|
if slashes <= 1 {
|
||||||
// processes
|
// processes
|
||||||
for _, proc := range process.All() {
|
for _, proc := range process.All() {
|
||||||
|
proc.Lock()
|
||||||
if q.Matches(proc) {
|
if q.Matches(proc) {
|
||||||
it.Next <- proc
|
it.Next <- proc
|
||||||
}
|
}
|
||||||
|
proc.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,9 +89,11 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
|
||||||
// dns scopes only
|
// dns scopes only
|
||||||
dnsConnsLock.RLock()
|
dnsConnsLock.RLock()
|
||||||
for _, dnsConn := range dnsConns {
|
for _, dnsConn := range dnsConns {
|
||||||
|
dnsConn.Lock()
|
||||||
if q.Matches(dnsConn) {
|
if q.Matches(dnsConn) {
|
||||||
it.Next <- dnsConn
|
it.Next <- dnsConn
|
||||||
}
|
}
|
||||||
|
dnsConn.Unlock()
|
||||||
}
|
}
|
||||||
dnsConnsLock.RUnlock()
|
dnsConnsLock.RUnlock()
|
||||||
}
|
}
|
||||||
|
@ -98,9 +102,11 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
|
||||||
// connections
|
// connections
|
||||||
connsLock.RLock()
|
connsLock.RLock()
|
||||||
for _, conn := range conns {
|
for _, conn := range conns {
|
||||||
|
conn.Lock()
|
||||||
if q.Matches(conn) {
|
if q.Matches(conn) {
|
||||||
it.Next <- conn
|
it.Next <- conn
|
||||||
}
|
}
|
||||||
|
conn.Unlock()
|
||||||
}
|
}
|
||||||
connsLock.RUnlock()
|
connsLock.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
module = modules.Register("network", nil, start, nil, "core", "processes")
|
module = modules.Register("network", nil, start, nil, "base", "processes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultFirewallHandler sets the default firewall handler.
|
// SetDefaultFirewallHandler sets the default firewall handler.
|
||||||
|
|
|
@ -14,7 +14,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClassifyIP returns the classification for the given IP address.
|
// ClassifyIP returns the classification for the given IP address.
|
||||||
func ClassifyIP(ip net.IP) int8 {
|
func ClassifyIP(ip net.IP) int8 { //nolint:gocognit
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
// IPv4
|
// IPv4
|
||||||
switch {
|
switch {
|
||||||
|
@ -36,11 +36,18 @@ func ClassifyIP(ip net.IP) int8 {
|
||||||
case ip4[0] == 224:
|
case ip4[0] == 224:
|
||||||
// 224.0.0.0/8
|
// 224.0.0.0/8
|
||||||
return LocalMulticast
|
return LocalMulticast
|
||||||
case ip4[0] >= 225 && ip4[0] <= 239:
|
case ip4[0] >= 225 && ip4[0] <= 238:
|
||||||
// 225.0.0.0/8 - 239.0.0.0/8
|
// 225.0.0.0/8 - 238.0.0.0/8
|
||||||
return GlobalMulticast
|
return GlobalMulticast
|
||||||
|
case ip4[0] == 239:
|
||||||
|
// 239.0.0.0/8
|
||||||
|
// RFC2365 - https://tools.ietf.org/html/rfc2365
|
||||||
|
return LocalMulticast
|
||||||
|
case ip4[0] == 255 && ip4[1] == 255 && ip4[2] == 255 && ip4[3] == 255:
|
||||||
|
// 255.255.255.255/32
|
||||||
|
return LocalMulticast
|
||||||
case ip4[0] >= 240:
|
case ip4[0] >= 240:
|
||||||
// 240.0.0.0/8 - 255.0.0.0/8
|
// 240.0.0.0/8 - 255.0.0.0/8 (minus 255.255.255.255/32)
|
||||||
return Invalid
|
return Invalid
|
||||||
default:
|
default:
|
||||||
return Global
|
return Global
|
||||||
|
|
|
@ -17,6 +17,10 @@ import (
|
||||||
"github.com/tevino/abool"
|
"github.com/tevino/abool"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
restartCode = 23
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
runningInConsole bool
|
runningInConsole bool
|
||||||
onWindows = runtime.GOOS == "windows"
|
onWindows = runtime.GOOS == "windows"
|
||||||
|
@ -375,7 +379,7 @@ func execute(opts *Options, args []string) (cont bool, err error) {
|
||||||
case 1:
|
case 1:
|
||||||
// error exit
|
// error exit
|
||||||
return true, fmt.Errorf("error during execution: %s", err)
|
return true, fmt.Errorf("error during execution: %s", err)
|
||||||
case 2357427: // Leet Speak for "restart"
|
case restartCode:
|
||||||
// restart request
|
// restart request
|
||||||
log.Printf("restarting %s\n", opts.Identifier)
|
log.Printf("restarting %s\n", opts.Identifier)
|
||||||
return true, nil
|
return true, nil
|
||||||
|
|
|
@ -17,6 +17,7 @@ func registerConfiguration() error {
|
||||||
Name: "Enable Process Detection",
|
Name: "Enable Process Detection",
|
||||||
Key: CfgOptionEnableProcessDetectionKey,
|
Key: CfgOptionEnableProcessDetectionKey,
|
||||||
Description: "This option enables the attribution of network traffic to processes. This should be always enabled, and effectively disables app profiles if disabled.",
|
Description: "This option enables the attribution of network traffic to processes. This should be always enabled, and effectively disables app profiles if disabled.",
|
||||||
|
Order: 144,
|
||||||
OptType: config.OptTypeBool,
|
OptType: config.OptTypeBool,
|
||||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||||
DefaultValue: true,
|
DefaultValue: true,
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/config"
|
||||||
|
|
||||||
"github.com/safing/portmaster/intel/filterlists"
|
"github.com/safing/portmaster/intel/filterlists"
|
||||||
"github.com/safing/portmaster/profile/endpoints"
|
"github.com/safing/portmaster/profile/endpoints"
|
||||||
)
|
)
|
||||||
|
@ -77,20 +79,24 @@ func updateGlobalConfigProfile(ctx context.Context, data interface{}) error {
|
||||||
internalSave: true,
|
internalSave: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newConfig := make(map[string]interface{})
|
||||||
// fill profile config options
|
// fill profile config options
|
||||||
for key, value := range cfgStringOptions {
|
for key, value := range cfgStringOptions {
|
||||||
profile.Config[key] = value()
|
newConfig[key] = value()
|
||||||
}
|
}
|
||||||
for key, value := range cfgStringArrayOptions {
|
for key, value := range cfgStringArrayOptions {
|
||||||
profile.Config[key] = value()
|
newConfig[key] = value()
|
||||||
}
|
}
|
||||||
for key, value := range cfgIntOptions {
|
for key, value := range cfgIntOptions {
|
||||||
profile.Config[key] = value()
|
newConfig[key] = value()
|
||||||
}
|
}
|
||||||
for key, value := range cfgBoolOptions {
|
for key, value := range cfgBoolOptions {
|
||||||
profile.Config[key] = value()
|
newConfig[key] = value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expand and assign
|
||||||
|
profile.Config = config.Expand(newConfig)
|
||||||
|
|
||||||
// save profile
|
// save profile
|
||||||
err = profile.Save()
|
err = profile.Save()
|
||||||
if err != nil && lastErr == nil {
|
if err != nil && lastErr == nil {
|
||||||
|
|
|
@ -12,53 +12,75 @@ var (
|
||||||
cfgIntOptions = make(map[string]config.IntOption)
|
cfgIntOptions = make(map[string]config.IntOption)
|
||||||
cfgBoolOptions = make(map[string]config.BoolOption)
|
cfgBoolOptions = make(map[string]config.BoolOption)
|
||||||
|
|
||||||
CfgOptionDefaultActionKey = "filter/defaultAction"
|
// Enable Filter Order = 0
|
||||||
cfgOptionDefaultAction config.StringOption
|
|
||||||
|
|
||||||
CfgOptionDisableAutoPermitKey = "filter/disableAutoPermit"
|
CfgOptionDefaultActionKey = "filter/defaultAction"
|
||||||
cfgOptionDisableAutoPermit config.IntOption // security level option
|
cfgOptionDefaultAction config.StringOption
|
||||||
|
cfgOptionDefaultActionOrder = 1
|
||||||
|
|
||||||
CfgOptionEndpointsKey = "filter/endpoints"
|
// Prompt Timeout Order = 2
|
||||||
cfgOptionEndpoints config.StringArrayOption
|
|
||||||
|
|
||||||
CfgOptionServiceEndpointsKey = "filter/serviceEndpoints"
|
CfgOptionBlockScopeInternetKey = "filter/blockInternet"
|
||||||
cfgOptionServiceEndpoints config.StringArrayOption
|
cfgOptionBlockScopeInternet config.IntOption // security level option
|
||||||
|
cfgOptionBlockScopeInternetOrder = 16
|
||||||
|
|
||||||
CfgOptionFilterListKey = "filter/lists"
|
CfgOptionBlockScopeLANKey = "filter/blockLAN"
|
||||||
cfgOptionFilterLists config.StringArrayOption
|
cfgOptionBlockScopeLAN config.IntOption // security level option
|
||||||
|
cfgOptionBlockScopeLANOrder = 17
|
||||||
|
|
||||||
CfgOptionFilterSubDomainsKey = "filter/includeSubdomains"
|
CfgOptionBlockScopeLocalKey = "filter/blockLocal"
|
||||||
cfgOptionFilterSubDomains config.IntOption // security level option
|
cfgOptionBlockScopeLocal config.IntOption // security level option
|
||||||
|
cfgOptionBlockScopeLocalOrder = 18
|
||||||
|
|
||||||
CfgOptionFilterCNAMEKey = "filter/includeCNAMEs"
|
CfgOptionBlockP2PKey = "filter/blockP2P"
|
||||||
cfgOptionFilterCNAME config.IntOption // security level option
|
cfgOptionBlockP2P config.IntOption // security level option
|
||||||
|
cfgOptionBlockP2POrder = 19
|
||||||
|
|
||||||
CfgOptionBlockScopeLocalKey = "filter/blockLocal"
|
CfgOptionBlockInboundKey = "filter/blockInbound"
|
||||||
cfgOptionBlockScopeLocal config.IntOption // security level option
|
cfgOptionBlockInbound config.IntOption // security level option
|
||||||
|
cfgOptionBlockInboundOrder = 20
|
||||||
|
|
||||||
CfgOptionBlockScopeLANKey = "filter/blockLAN"
|
CfgOptionEndpointsKey = "filter/endpoints"
|
||||||
cfgOptionBlockScopeLAN config.IntOption // security level option
|
cfgOptionEndpoints config.StringArrayOption
|
||||||
|
cfgOptionEndpointsOrder = 32
|
||||||
|
|
||||||
CfgOptionBlockScopeInternetKey = "filter/blockInternet"
|
CfgOptionServiceEndpointsKey = "filter/serviceEndpoints"
|
||||||
cfgOptionBlockScopeInternet config.IntOption // security level option
|
cfgOptionServiceEndpoints config.StringArrayOption
|
||||||
|
cfgOptionServiceEndpointsOrder = 33
|
||||||
|
|
||||||
CfgOptionBlockP2PKey = "filter/blockP2P"
|
CfgOptionPreventBypassingKey = "filter/preventBypassing"
|
||||||
cfgOptionBlockP2P config.IntOption // security level option
|
cfgOptionPreventBypassing config.IntOption // security level option
|
||||||
|
cfgOptionPreventBypassingOrder = 48
|
||||||
|
|
||||||
CfgOptionBlockInboundKey = "filter/blockInbound"
|
CfgOptionFilterListsKey = "filter/lists"
|
||||||
cfgOptionBlockInbound config.IntOption // security level option
|
cfgOptionFilterLists config.StringArrayOption
|
||||||
|
cfgOptionFilterListsOrder = 64
|
||||||
|
|
||||||
CfgOptionEnforceSPNKey = "filter/enforceSPN"
|
CfgOptionFilterSubDomainsKey = "filter/includeSubdomains"
|
||||||
cfgOptionEnforceSPN config.IntOption // security level option
|
cfgOptionFilterSubDomains config.IntOption // security level option
|
||||||
|
cfgOptionFilterSubDomainsOrder = 65
|
||||||
|
|
||||||
CfgOptionRemoveOutOfScopeDNSKey = "filter/removeOutOfScopeDNS"
|
CfgOptionFilterCNAMEKey = "filter/includeCNAMEs"
|
||||||
cfgOptionRemoveOutOfScopeDNS config.IntOption // security level option
|
cfgOptionFilterCNAME config.IntOption // security level option
|
||||||
|
cfgOptionFilterCNAMEOrder = 66
|
||||||
|
|
||||||
CfgOptionRemoveBlockedDNSKey = "filter/removeBlockedDNS"
|
CfgOptionDisableAutoPermitKey = "filter/disableAutoPermit"
|
||||||
cfgOptionRemoveBlockedDNS config.IntOption // security level option
|
cfgOptionDisableAutoPermit config.IntOption // security level option
|
||||||
|
cfgOptionDisableAutoPermitOrder = 80
|
||||||
|
|
||||||
CfgOptionPreventBypassingKey = "filter/preventBypassing"
|
CfgOptionEnforceSPNKey = "filter/enforceSPN"
|
||||||
cfgOptionPreventBypassing config.IntOption // security level option
|
cfgOptionEnforceSPN config.IntOption // security level option
|
||||||
|
cfgOptionEnforceSPNOrder = 96
|
||||||
|
|
||||||
|
CfgOptionRemoveOutOfScopeDNSKey = "filter/removeOutOfScopeDNS"
|
||||||
|
cfgOptionRemoveOutOfScopeDNS config.IntOption // security level option
|
||||||
|
cfgOptionRemoveOutOfScopeDNSOrder = 112
|
||||||
|
|
||||||
|
CfgOptionRemoveBlockedDNSKey = "filter/removeBlockedDNS"
|
||||||
|
cfgOptionRemoveBlockedDNS config.IntOption // security level option
|
||||||
|
cfgOptionRemoveBlockedDNSOrder = 113
|
||||||
|
|
||||||
|
// Permanent Verdicts Order = 128
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerConfiguration() error {
|
func registerConfiguration() error {
|
||||||
|
@ -70,6 +92,7 @@ func registerConfiguration() error {
|
||||||
Name: "Default Filter Action",
|
Name: "Default Filter Action",
|
||||||
Key: CfgOptionDefaultActionKey,
|
Key: CfgOptionDefaultActionKey,
|
||||||
Description: `The default filter action when nothing else permits or blocks a connection.`,
|
Description: `The default filter action when nothing else permits or blocks a connection.`,
|
||||||
|
Order: cfgOptionDefaultActionOrder,
|
||||||
OptType: config.OptTypeString,
|
OptType: config.OptTypeString,
|
||||||
DefaultValue: "permit",
|
DefaultValue: "permit",
|
||||||
ExternalOptType: "string list",
|
ExternalOptType: "string list",
|
||||||
|
@ -86,6 +109,7 @@ func registerConfiguration() error {
|
||||||
Name: "Disable Auto Permit",
|
Name: "Disable Auto Permit",
|
||||||
Key: CfgOptionDisableAutoPermitKey,
|
Key: CfgOptionDisableAutoPermitKey,
|
||||||
Description: "Auto Permit searches for a relation between an app and the destionation of a connection - if there is a correlation, the connection will be permitted. This setting is negated in order to provide a streamlined user experience, where higher settings are better.",
|
Description: "Auto Permit searches for a relation between an app and the destionation of a connection - if there is a correlation, the connection will be permitted. This setting is negated in order to provide a streamlined user experience, where higher settings are better.",
|
||||||
|
Order: cfgOptionDisableAutoPermitOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExternalOptType: "security level",
|
ExternalOptType: "security level",
|
||||||
DefaultValue: status.SecurityLevelsAll,
|
DefaultValue: status.SecurityLevelsAll,
|
||||||
|
@ -107,7 +131,7 @@ func registerConfiguration() error {
|
||||||
"+": permit
|
"+": permit
|
||||||
"-": block
|
"-": block
|
||||||
Host Matching:
|
Host Matching:
|
||||||
IP, CIDR, Country Code, ASN, "*" for any
|
IP, CIDR, Country Code, ASN, Filterlist, "*" for any
|
||||||
Domains:
|
Domains:
|
||||||
"example.com": exact match
|
"example.com": exact match
|
||||||
".example.com": exact match + subdomains
|
".example.com": exact match + subdomains
|
||||||
|
@ -120,7 +144,12 @@ func registerConfiguration() error {
|
||||||
Examples:
|
Examples:
|
||||||
+ .example.com */HTTP
|
+ .example.com */HTTP
|
||||||
- .example.com
|
- .example.com
|
||||||
+ 192.168.0.1/24`,
|
+ 192.168.0.1/24
|
||||||
|
- L:MAL
|
||||||
|
- AS0
|
||||||
|
+ AT
|
||||||
|
- *`,
|
||||||
|
Order: cfgOptionEndpointsOrder,
|
||||||
OptType: config.OptTypeStringArray,
|
OptType: config.OptTypeStringArray,
|
||||||
DefaultValue: []string{},
|
DefaultValue: []string{},
|
||||||
ExternalOptType: "endpoint list",
|
ExternalOptType: "endpoint list",
|
||||||
|
@ -142,7 +171,7 @@ Examples:
|
||||||
"+": permit
|
"+": permit
|
||||||
"-": block
|
"-": block
|
||||||
Host Matching:
|
Host Matching:
|
||||||
IP, CIDR, Country Code, ASN, "*" for any
|
IP, CIDR, Country Code, ASN, Filterlist, "*" for any
|
||||||
Domains:
|
Domains:
|
||||||
"example.com": exact match
|
"example.com": exact match
|
||||||
".example.com": exact match + subdomains
|
".example.com": exact match + subdomains
|
||||||
|
@ -155,7 +184,12 @@ Examples:
|
||||||
Examples:
|
Examples:
|
||||||
+ .example.com */HTTP
|
+ .example.com */HTTP
|
||||||
- .example.com
|
- .example.com
|
||||||
+ 192.168.0.1/24`,
|
+ 192.168.0.1/24
|
||||||
|
- L:MAL
|
||||||
|
- AS0
|
||||||
|
+ AT
|
||||||
|
- *`,
|
||||||
|
Order: cfgOptionServiceEndpointsOrder,
|
||||||
OptType: config.OptTypeStringArray,
|
OptType: config.OptTypeStringArray,
|
||||||
DefaultValue: []string{},
|
DefaultValue: []string{},
|
||||||
ExternalOptType: "endpoint list",
|
ExternalOptType: "endpoint list",
|
||||||
|
@ -170,8 +204,9 @@ Examples:
|
||||||
// Filter list IDs
|
// Filter list IDs
|
||||||
err = config.Register(&config.Option{
|
err = config.Register(&config.Option{
|
||||||
Name: "Filter List",
|
Name: "Filter List",
|
||||||
Key: CfgOptionFilterListKey,
|
Key: CfgOptionFilterListsKey,
|
||||||
Description: "Filter connections by matching the endpoint against configured filterlists",
|
Description: "Filter connections by matching the endpoint against configured filterlists",
|
||||||
|
Order: cfgOptionFilterListsOrder,
|
||||||
OptType: config.OptTypeStringArray,
|
OptType: config.OptTypeStringArray,
|
||||||
DefaultValue: []string{"TRAC", "MAL"},
|
DefaultValue: []string{"TRAC", "MAL"},
|
||||||
ExternalOptType: "filter list",
|
ExternalOptType: "filter list",
|
||||||
|
@ -180,14 +215,15 @@ Examples:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cfgOptionFilterLists = config.Concurrent.GetAsStringArray(CfgOptionFilterListKey, []string{})
|
cfgOptionFilterLists = config.Concurrent.GetAsStringArray(CfgOptionFilterListsKey, []string{})
|
||||||
cfgStringArrayOptions[CfgOptionFilterListKey] = cfgOptionFilterLists
|
cfgStringArrayOptions[CfgOptionFilterListsKey] = cfgOptionFilterLists
|
||||||
|
|
||||||
// Include CNAMEs
|
// Include CNAMEs
|
||||||
err = config.Register(&config.Option{
|
err = config.Register(&config.Option{
|
||||||
Name: "Filter CNAMEs",
|
Name: "Filter CNAMEs",
|
||||||
Key: CfgOptionFilterCNAMEKey,
|
Key: CfgOptionFilterCNAMEKey,
|
||||||
Description: "Also filter requests where a CNAME would be blocked",
|
Description: "Also filter requests where a CNAME would be blocked",
|
||||||
|
Order: cfgOptionFilterCNAMEOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExternalOptType: "security level",
|
ExternalOptType: "security level",
|
||||||
DefaultValue: status.SecurityLevelsAll,
|
DefaultValue: status.SecurityLevelsAll,
|
||||||
|
@ -205,6 +241,7 @@ Examples:
|
||||||
Name: "Filter SubDomains",
|
Name: "Filter SubDomains",
|
||||||
Key: CfgOptionFilterSubDomainsKey,
|
Key: CfgOptionFilterSubDomainsKey,
|
||||||
Description: "Also filter sub-domains if a parent domain is blocked by a filter list",
|
Description: "Also filter sub-domains if a parent domain is blocked by a filter list",
|
||||||
|
Order: cfgOptionFilterSubDomainsOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExternalOptType: "security level",
|
ExternalOptType: "security level",
|
||||||
DefaultValue: status.SecurityLevelOff,
|
DefaultValue: status.SecurityLevelOff,
|
||||||
|
@ -220,8 +257,10 @@ Examples:
|
||||||
err = config.Register(&config.Option{
|
err = config.Register(&config.Option{
|
||||||
Name: "Block Scope Local",
|
Name: "Block Scope Local",
|
||||||
Key: CfgOptionBlockScopeLocalKey,
|
Key: CfgOptionBlockScopeLocalKey,
|
||||||
Description: "Block connections to your own device, ie. localhost.",
|
Description: "Block internal connections on your own device, ie. localhost.",
|
||||||
|
Order: cfgOptionBlockScopeLocalOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ExternalOptType: "security level",
|
ExternalOptType: "security level",
|
||||||
DefaultValue: status.SecurityLevelOff,
|
DefaultValue: status.SecurityLevelOff,
|
||||||
ValidationRegex: "^(0|4|6|7)$",
|
ValidationRegex: "^(0|4|6|7)$",
|
||||||
|
@ -237,9 +276,10 @@ Examples:
|
||||||
Name: "Block Scope LAN",
|
Name: "Block Scope LAN",
|
||||||
Key: CfgOptionBlockScopeLANKey,
|
Key: CfgOptionBlockScopeLANKey,
|
||||||
Description: "Block connections to the Local Area Network.",
|
Description: "Block connections to the Local Area Network.",
|
||||||
|
Order: cfgOptionBlockScopeLANOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExternalOptType: "security level",
|
ExternalOptType: "security level",
|
||||||
DefaultValue: status.SecurityLevelOff,
|
DefaultValue: status.SecurityLevelsHighAndExtreme,
|
||||||
ValidationRegex: "^(0|4|6|7)$",
|
ValidationRegex: "^(0|4|6|7)$",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -253,6 +293,7 @@ Examples:
|
||||||
Name: "Block Scope Internet",
|
Name: "Block Scope Internet",
|
||||||
Key: CfgOptionBlockScopeInternetKey,
|
Key: CfgOptionBlockScopeInternetKey,
|
||||||
Description: "Block connections to the Internet.",
|
Description: "Block connections to the Internet.",
|
||||||
|
Order: cfgOptionBlockScopeInternetOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExternalOptType: "security level",
|
ExternalOptType: "security level",
|
||||||
DefaultValue: status.SecurityLevelOff,
|
DefaultValue: status.SecurityLevelOff,
|
||||||
|
@ -268,7 +309,8 @@ Examples:
|
||||||
err = config.Register(&config.Option{
|
err = config.Register(&config.Option{
|
||||||
Name: "Block Peer to Peer Connections",
|
Name: "Block Peer to Peer Connections",
|
||||||
Key: CfgOptionBlockP2PKey,
|
Key: CfgOptionBlockP2PKey,
|
||||||
Description: "Block peer to peer connections. These are connections that are established directly to an IP address on the Internet without resolving a domain name via DNS first.",
|
Description: "These are connections that are established directly to an IP address on the Internet without resolving a domain name via DNS first.",
|
||||||
|
Order: cfgOptionBlockP2POrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExternalOptType: "security level",
|
ExternalOptType: "security level",
|
||||||
DefaultValue: status.SecurityLevelsAll,
|
DefaultValue: status.SecurityLevelsAll,
|
||||||
|
@ -284,7 +326,8 @@ Examples:
|
||||||
err = config.Register(&config.Option{
|
err = config.Register(&config.Option{
|
||||||
Name: "Block Inbound Connections",
|
Name: "Block Inbound Connections",
|
||||||
Key: CfgOptionBlockInboundKey,
|
Key: CfgOptionBlockInboundKey,
|
||||||
Description: "Block inbound connections to your device. This will usually only be the case if you are running a network service or are using peer to peer software.",
|
Description: "Connections initiated towards your device. This will usually only be the case if you are running a network service or are using peer to peer software.",
|
||||||
|
Order: cfgOptionBlockInboundOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExternalOptType: "security level",
|
ExternalOptType: "security level",
|
||||||
DefaultValue: status.SecurityLevelsHighAndExtreme,
|
DefaultValue: status.SecurityLevelsHighAndExtreme,
|
||||||
|
@ -301,6 +344,7 @@ Examples:
|
||||||
Name: "Enforce SPN",
|
Name: "Enforce SPN",
|
||||||
Key: CfgOptionEnforceSPNKey,
|
Key: CfgOptionEnforceSPNKey,
|
||||||
Description: "This setting enforces connections to be routed over the SPN. If this is not possible for any reason, connections will be blocked.",
|
Description: "This setting enforces connections to be routed over the SPN. If this is not possible for any reason, connections will be blocked.",
|
||||||
|
Order: cfgOptionEnforceSPNOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ReleaseLevel: config.ReleaseLevelExperimental,
|
ReleaseLevel: config.ReleaseLevelExperimental,
|
||||||
ExternalOptType: "security level",
|
ExternalOptType: "security level",
|
||||||
|
@ -318,6 +362,7 @@ Examples:
|
||||||
Name: "Filter Out-of-Scope DNS Records",
|
Name: "Filter Out-of-Scope DNS Records",
|
||||||
Key: CfgOptionRemoveOutOfScopeDNSKey,
|
Key: CfgOptionRemoveOutOfScopeDNSKey,
|
||||||
Description: "Filter DNS answers that are outside of the scope of the server. A server on the public Internet may not respond with a private LAN address.",
|
Description: "Filter DNS answers that are outside of the scope of the server. A server on the public Internet may not respond with a private LAN address.",
|
||||||
|
Order: cfgOptionRemoveOutOfScopeDNSOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelBeta,
|
ReleaseLevel: config.ReleaseLevelBeta,
|
||||||
|
@ -336,6 +381,7 @@ Examples:
|
||||||
Name: "Filter DNS Records that would be blocked",
|
Name: "Filter DNS Records that would be blocked",
|
||||||
Key: CfgOptionRemoveBlockedDNSKey,
|
Key: CfgOptionRemoveBlockedDNSKey,
|
||||||
Description: "Pre-filter DNS answers that an application would not be allowed to connect to.",
|
Description: "Pre-filter DNS answers that an application would not be allowed to connect to.",
|
||||||
|
Order: cfgOptionRemoveBlockedDNSOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelBeta,
|
ReleaseLevel: config.ReleaseLevelBeta,
|
||||||
|
@ -353,6 +399,7 @@ Examples:
|
||||||
Name: "Prevent Bypassing",
|
Name: "Prevent Bypassing",
|
||||||
Key: CfgOptionPreventBypassingKey,
|
Key: CfgOptionPreventBypassingKey,
|
||||||
Description: "Prevent apps from bypassing the privacy filter: Firefox by disabling DNS-over-HTTPs",
|
Description: "Prevent apps from bypassing the privacy filter: Firefox by disabling DNS-over-HTTPs",
|
||||||
|
Order: cfgOptionPreventBypassingOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelUser,
|
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||||
ReleaseLevel: config.ReleaseLevelBeta,
|
ReleaseLevel: config.ReleaseLevelBeta,
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/config"
|
||||||
|
|
||||||
"github.com/safing/portbase/database"
|
"github.com/safing/portbase/database"
|
||||||
"github.com/safing/portbase/database/query"
|
"github.com/safing/portbase/database/query"
|
||||||
"github.com/safing/portbase/database/record"
|
"github.com/safing/portbase/database/record"
|
||||||
|
@ -87,6 +89,9 @@ func (h *databaseHook) PrePut(r record.Record) (record.Record, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clean config
|
||||||
|
config.CleanHierarchicalConfig(profile.Config)
|
||||||
|
|
||||||
// prepare config
|
// prepare config
|
||||||
err = profile.prepConfig()
|
err = profile.prepConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
|
|
||||||
// module dependencies
|
// module dependencies
|
||||||
_ "github.com/safing/portmaster/core"
|
_ "github.com/safing/portmaster/core/base"
|
||||||
|
_ "github.com/safing/portmaster/updates" // dependency of semi-dependency filterlists
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -14,7 +15,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
module = modules.Register("profiles", prep, start, nil, "core")
|
module = modules.Register("profiles", prep, start, nil, "base", "updates")
|
||||||
}
|
}
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
|
|
|
@ -143,7 +143,7 @@ func (profile *Profile) parseConfig() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
list, ok = profile.configPerspective.GetAsStringArray(CfgOptionFilterListKey)
|
list, ok = profile.configPerspective.GetAsStringArray(CfgOptionFilterListsKey)
|
||||||
if ok {
|
if ok {
|
||||||
profile.filterListIDs, err = filterlists.ResolveListIDs(list)
|
profile.filterListIDs, err = filterlists.ResolveListIDs(list)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -237,7 +237,7 @@ func (profile *Profile) addEndpointyEntry(cfgKey, newEntry string) {
|
||||||
endpointList = make([]string, 0, 1)
|
endpointList = make([]string, 0, 1)
|
||||||
}
|
}
|
||||||
endpointList = append(endpointList, newEntry)
|
endpointList = append(endpointList, newEntry)
|
||||||
profile.Config[cfgKey] = endpointList
|
config.PutValueIntoHierarchicalConfig(profile.Config, cfgKey, endpointList)
|
||||||
|
|
||||||
profile.Unlock()
|
profile.Unlock()
|
||||||
err := profile.Save()
|
err := profile.Save()
|
||||||
|
|
|
@ -22,28 +22,28 @@ var (
|
||||||
// - Available logging data may not be used against the user, ie. unethically.
|
// - Available logging data may not be used against the user, ie. unethically.
|
||||||
|
|
||||||
// Sadly, only a few services come close to fulfilling these requirements.
|
// Sadly, only a few services come close to fulfilling these requirements.
|
||||||
// For now, we have settled for two bigger and well known services: Cloudflare and Quad9.
|
// For now, we have settled for two bigger and well known services: Quad9 and Cloudflare.
|
||||||
// TODO: monitor situation and re-evaluate when new services become available
|
// TODO: monitor situation and re-evaluate when new services become available
|
||||||
// TODO: explore other methods of making queries more private
|
// TODO: explore other methods of making queries more private
|
||||||
|
|
||||||
// We encourage everyone who has the technical abilities to set their own preferred servers.
|
// We encourage everyone who has the technical abilities to set their own preferred servers.
|
||||||
|
|
||||||
// Default 1: Cloudflare
|
// Default 1: Quad9
|
||||||
"dot://1.1.1.1:853?verify=cloudflare-dns.com&name=Cloudflare&blockedif=zeroip", // Cloudflare
|
|
||||||
"dot://1.0.0.1:853?verify=cloudflare-dns.com&name=Cloudflare&blockedif=zeroip", // Cloudflare
|
|
||||||
|
|
||||||
// Default 2: Quad9
|
|
||||||
"dot://9.9.9.9:853?verify=dns.quad9.net&name=Quad9&blockedif=empty", // Quad9
|
"dot://9.9.9.9:853?verify=dns.quad9.net&name=Quad9&blockedif=empty", // Quad9
|
||||||
"dot://149.112.112.112:853?verify=dns.quad9.net&name=Quad9&blockedif=empty", // Quad9
|
"dot://149.112.112.112:853?verify=dns.quad9.net&name=Quad9&blockedif=empty", // Quad9
|
||||||
|
|
||||||
// Fallback 1: Cloudflare
|
// Default 2: Cloudflare
|
||||||
"dns://1.1.1.1:53?name=Cloudflare&blockedif=zeroip", // Cloudflare
|
"dot://1.1.1.2:853?verify=cloudflare-dns.com&name=Cloudflare&blockedif=zeroip", // Cloudflare
|
||||||
"dns://1.0.0.1:53?name=Cloudflare&blockedif=zeroip", // Cloudflare
|
"dot://1.0.0.2:853?verify=cloudflare-dns.com&name=Cloudflare&blockedif=zeroip", // Cloudflare
|
||||||
|
|
||||||
// Fallback 2: Quad9
|
// Fallback 1: Quad9
|
||||||
"dns://9.9.9.9:53?name=Quad9&blockedif=empty", // Quad9
|
"dns://9.9.9.9:53?name=Quad9&blockedif=empty", // Quad9
|
||||||
"dns://149.112.112.112:53?name=Quad9&blockedif=empty", // Quad9
|
"dns://149.112.112.112:53?name=Quad9&blockedif=empty", // Quad9
|
||||||
|
|
||||||
|
// Fallback 2: Cloudflare
|
||||||
|
"dns://1.1.1.2:53?name=Cloudflare&blockedif=zeroip", // Cloudflare
|
||||||
|
"dns://1.0.0.2:53?name=Cloudflare&blockedif=zeroip", // Cloudflare
|
||||||
|
|
||||||
// supported parameters
|
// supported parameters
|
||||||
// - `verify=domain`: verify domain (dot only)
|
// - `verify=domain`: verify domain (dot only)
|
||||||
// future parameters:
|
// future parameters:
|
||||||
|
@ -55,38 +55,70 @@ var (
|
||||||
// - `zeroip`: Answer only contains zeroip
|
// - `zeroip`: Answer only contains zeroip
|
||||||
}
|
}
|
||||||
|
|
||||||
CfgOptionNameServersKey = "dns/nameservers"
|
CfgOptionNameServersKey = "dns/nameservers"
|
||||||
configuredNameServers config.StringArrayOption
|
configuredNameServers config.StringArrayOption
|
||||||
|
cfgOptionNameServersOrder = 0
|
||||||
|
|
||||||
CfgOptionNameserverRetryRateKey = "dns/nameserverRetryRate"
|
CfgOptionNoAssignedNameserversKey = "dns/noAssignedNameservers"
|
||||||
nameserverRetryRate config.IntOption
|
noAssignedNameservers status.SecurityLevelOption
|
||||||
|
cfgOptionNoAssignedNameserversOrder = 1
|
||||||
|
|
||||||
CfgOptionNoMulticastDNSKey = "dns/noMulticastDNS"
|
CfgOptionNoMulticastDNSKey = "dns/noMulticastDNS"
|
||||||
noMulticastDNS status.SecurityLevelOption
|
noMulticastDNS status.SecurityLevelOption
|
||||||
|
cfgOptionNoMulticastDNSOrder = 2
|
||||||
|
|
||||||
CfgOptionNoAssignedNameserversKey = "dns/noAssignedNameservers"
|
CfgOptionNoInsecureProtocolsKey = "dns/noInsecureProtocols"
|
||||||
noAssignedNameservers status.SecurityLevelOption
|
noInsecureProtocols status.SecurityLevelOption
|
||||||
|
cfgOptionNoInsecureProtocolsOrder = 3
|
||||||
|
|
||||||
CfgOptionNoInsecureProtocolsKey = "dns/noInsecureProtocols"
|
CfgOptionDontResolveSpecialDomainsKey = "dns/dontResolveSpecialDomains"
|
||||||
noInsecureProtocols status.SecurityLevelOption
|
dontResolveSpecialDomains status.SecurityLevelOption
|
||||||
|
cfgOptionDontResolveSpecialDomainsOrder = 16
|
||||||
|
|
||||||
CfgOptionDontResolveSpecialDomainsKey = "dns/dontResolveSpecialDomains"
|
CfgOptionDontResolveTestDomainsKey = "dns/dontResolveTestDomains"
|
||||||
dontResolveSpecialDomains status.SecurityLevelOption
|
dontResolveTestDomains status.SecurityLevelOption
|
||||||
|
cfgOptionDontResolveTestDomainsOrder = 17
|
||||||
|
|
||||||
CfgOptionDontResolveTestDomainsKey = "dns/dontResolveTestDomains"
|
CfgOptionNameserverRetryRateKey = "dns/nameserverRetryRate"
|
||||||
dontResolveTestDomains status.SecurityLevelOption
|
nameserverRetryRate config.IntOption
|
||||||
|
cfgOptionNameserverRetryRateOrder = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
func prepConfig() error {
|
func prepConfig() error {
|
||||||
err := config.Register(&config.Option{
|
err := config.Register(&config.Option{
|
||||||
Name: "DNS Servers",
|
Name: "DNS Servers",
|
||||||
Key: CfgOptionNameServersKey,
|
Key: CfgOptionNameServersKey,
|
||||||
Description: "DNS Servers to use for resolving DNS requests.",
|
Description: "DNS Servers to use for resolving DNS requests.",
|
||||||
|
Help: `Format:
|
||||||
|
|
||||||
|
DNS Servers are configured in a URL format. This allows you to specify special settings for a resolver. If you just want to use a resolver at IP 10.2.3.4, please enter: dns://10.2.3.4:53
|
||||||
|
The format is: protocol://ip:port?parameter=value¶meter=value
|
||||||
|
|
||||||
|
Protocols:
|
||||||
|
dot: DNS-over-TLS (recommended)
|
||||||
|
dns: plain old DNS
|
||||||
|
tcp: plain old DNS over TCP
|
||||||
|
|
||||||
|
IP:
|
||||||
|
always use the IP address and _not_ the domain name!
|
||||||
|
|
||||||
|
Port:
|
||||||
|
always add the port!
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
name: give your DNS Server a name that is used for messages and logs
|
||||||
|
verify: domain name to verify for "dot", required and only valid for "dot"
|
||||||
|
blockedif: detect if the name server blocks a query, options:
|
||||||
|
empty: server replies with NXDomain status, but without any other record in any section
|
||||||
|
refused: server replies with Refused status
|
||||||
|
zeroip: server replies with an IP address, but it is zero
|
||||||
|
`,
|
||||||
|
Order: cfgOptionNameServersOrder,
|
||||||
OptType: config.OptTypeStringArray,
|
OptType: config.OptTypeStringArray,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
DefaultValue: defaultNameServers,
|
DefaultValue: defaultNameServers,
|
||||||
ValidationRegex: "^(dns|dot|tls)://.*",
|
ValidationRegex: fmt.Sprintf("^(%s|%s|%s)://.*", ServerTypeDoT, ServerTypeDNS, ServerTypeTCP),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -97,6 +129,7 @@ func prepConfig() error {
|
||||||
Name: "DNS Server Retry Rate",
|
Name: "DNS Server Retry Rate",
|
||||||
Key: CfgOptionNameserverRetryRateKey,
|
Key: CfgOptionNameserverRetryRateKey,
|
||||||
Description: "Rate at which to retry failed DNS Servers, in seconds.",
|
Description: "Rate at which to retry failed DNS Servers, in seconds.",
|
||||||
|
Order: cfgOptionNameserverRetryRateOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
@ -111,6 +144,7 @@ func prepConfig() error {
|
||||||
Name: "Do not use Multicast DNS",
|
Name: "Do not use Multicast DNS",
|
||||||
Key: CfgOptionNoMulticastDNSKey,
|
Key: CfgOptionNoMulticastDNSKey,
|
||||||
Description: "Multicast DNS queries other devices in the local network",
|
Description: "Multicast DNS queries other devices in the local network",
|
||||||
|
Order: cfgOptionNoMulticastDNSOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
@ -127,6 +161,7 @@ func prepConfig() error {
|
||||||
Name: "Do not use assigned Nameservers",
|
Name: "Do not use assigned Nameservers",
|
||||||
Key: CfgOptionNoAssignedNameserversKey,
|
Key: CfgOptionNoAssignedNameserversKey,
|
||||||
Description: "that were acquired by the network (dhcp) or system",
|
Description: "that were acquired by the network (dhcp) or system",
|
||||||
|
Order: cfgOptionNoAssignedNameserversOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
@ -143,6 +178,7 @@ func prepConfig() error {
|
||||||
Name: "Do not resolve insecurely",
|
Name: "Do not resolve insecurely",
|
||||||
Key: CfgOptionNoInsecureProtocolsKey,
|
Key: CfgOptionNoInsecureProtocolsKey,
|
||||||
Description: "Do not resolve domains with insecure protocols, ie. plain DNS",
|
Description: "Do not resolve domains with insecure protocols, ie. plain DNS",
|
||||||
|
Order: cfgOptionNoInsecureProtocolsOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
@ -159,6 +195,7 @@ func prepConfig() error {
|
||||||
Name: "Do not resolve special domains",
|
Name: "Do not resolve special domains",
|
||||||
Key: CfgOptionDontResolveSpecialDomainsKey,
|
Key: CfgOptionDontResolveSpecialDomainsKey,
|
||||||
Description: fmt.Sprintf("Do not resolve the special top level domains %s", formatScopeList(specialServiceScopes)),
|
Description: fmt.Sprintf("Do not resolve the special top level domains %s", formatScopeList(specialServiceScopes)),
|
||||||
|
Order: cfgOptionDontResolveSpecialDomainsOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
@ -175,6 +212,7 @@ func prepConfig() error {
|
||||||
Name: "Do not resolve test domains",
|
Name: "Do not resolve test domains",
|
||||||
Key: CfgOptionDontResolveTestDomainsKey,
|
Key: CfgOptionDontResolveTestDomainsKey,
|
||||||
Description: fmt.Sprintf("Do not resolve the special testing top level domains %s", formatScopeList(localTestScopes)),
|
Description: fmt.Sprintf("Do not resolve the special testing top level domains %s", formatScopeList(localTestScopes)),
|
||||||
|
Order: cfgOptionDontResolveTestDomainsOrder,
|
||||||
OptType: config.OptTypeInt,
|
OptType: config.OptTypeInt,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/safing/portmaster/intel"
|
"github.com/safing/portmaster/intel"
|
||||||
|
|
||||||
// module dependencies
|
// module dependencies
|
||||||
_ "github.com/safing/portmaster/core"
|
_ "github.com/safing/portmaster/core/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -17,7 +17,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
module = modules.Register("resolver", prep, start, nil, "core", "netenv")
|
module = modules.Register("resolver", prep, start, nil, "base", "netenv")
|
||||||
}
|
}
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
|
|
|
@ -28,7 +28,7 @@ func testReverse(t *testing.T, ip, result, expectedErr string) {
|
||||||
func TestResolveIPAndValidate(t *testing.T) {
|
func TestResolveIPAndValidate(t *testing.T) {
|
||||||
testReverse(t, "198.41.0.4", "a.root-servers.net.", "")
|
testReverse(t, "198.41.0.4", "a.root-servers.net.", "")
|
||||||
// testReverse(t, "9.9.9.9", "dns.quad9.net.", "") // started resolving to dns9.quad9.net.
|
// testReverse(t, "9.9.9.9", "dns.quad9.net.", "") // started resolving to dns9.quad9.net.
|
||||||
testReverse(t, "2620:fe::fe", "dns.quad9.net.", "")
|
// testReverse(t, "2620:fe::fe", "dns.quad9.net.", "") // fails sometimes for some (external) reason
|
||||||
testReverse(t, "1.1.1.1", "one.one.one.one.", "")
|
testReverse(t, "1.1.1.1", "one.one.one.one.", "")
|
||||||
testReverse(t, "2606:4700:4700::1111", "one.one.one.one.", "")
|
testReverse(t, "2606:4700:4700::1111", "one.one.one.one.", "")
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,6 @@ func start() error {
|
||||||
|
|
||||||
// load status into atomic getters
|
// load status into atomic getters
|
||||||
atomicUpdateSelectedSecurityLevel(status.SelectedSecurityLevel)
|
atomicUpdateSelectedSecurityLevel(status.SelectedSecurityLevel)
|
||||||
atomicUpdatePortmasterStatus(status.PortmasterStatus)
|
|
||||||
atomicUpdateGate17Status(status.Gate17Status)
|
|
||||||
|
|
||||||
// update status
|
// update status
|
||||||
status.updateThreatMitigationLevel()
|
status.updateThreatMitigationLevel()
|
||||||
|
|
|
@ -45,40 +45,6 @@ func setSelectedSecurityLevel(level uint8) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPortmasterStatus sets the current Portmaster status.
|
|
||||||
func SetPortmasterStatus(pmStatus uint8, msg string) {
|
|
||||||
switch pmStatus {
|
|
||||||
case StatusOff, StatusError, StatusWarning, StatusOk:
|
|
||||||
status.Lock()
|
|
||||||
defer status.Unlock()
|
|
||||||
|
|
||||||
status.PortmasterStatus = pmStatus
|
|
||||||
status.PortmasterStatusMsg = msg
|
|
||||||
atomicUpdatePortmasterStatus(pmStatus)
|
|
||||||
|
|
||||||
go status.Save()
|
|
||||||
default:
|
|
||||||
log.Errorf("status: tried to set portmaster to invalid status: %d", pmStatus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetGate17Status sets the current Gate17 status.
|
|
||||||
func SetGate17Status(g17Status uint8, msg string) {
|
|
||||||
switch g17Status {
|
|
||||||
case StatusOff, StatusError, StatusWarning, StatusOk:
|
|
||||||
status.Lock()
|
|
||||||
defer status.Unlock()
|
|
||||||
|
|
||||||
status.Gate17Status = g17Status
|
|
||||||
status.Gate17StatusMsg = msg
|
|
||||||
atomicUpdateGate17Status(g17Status)
|
|
||||||
|
|
||||||
go status.Save()
|
|
||||||
default:
|
|
||||||
log.Errorf("status: tried to set gate17 to invalid status: %d", g17Status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update functions for atomic stuff
|
// update functions for atomic stuff
|
||||||
func atomicUpdateActiveSecurityLevel(level uint8) {
|
func atomicUpdateActiveSecurityLevel(level uint8) {
|
||||||
atomic.StoreUint32(activeSecurityLevel, uint32(level))
|
atomic.StoreUint32(activeSecurityLevel, uint32(level))
|
||||||
|
@ -87,11 +53,3 @@ func atomicUpdateActiveSecurityLevel(level uint8) {
|
||||||
func atomicUpdateSelectedSecurityLevel(level uint8) {
|
func atomicUpdateSelectedSecurityLevel(level uint8) {
|
||||||
atomic.StoreUint32(selectedSecurityLevel, uint32(level))
|
atomic.StoreUint32(selectedSecurityLevel, uint32(level))
|
||||||
}
|
}
|
||||||
|
|
||||||
func atomicUpdatePortmasterStatus(status uint8) {
|
|
||||||
atomic.StoreUint32(portmasterStatus, uint32(status))
|
|
||||||
}
|
|
||||||
|
|
||||||
func atomicUpdateGate17Status(status uint8) {
|
|
||||||
atomic.StoreUint32(gate17Status, uint32(status))
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,5 @@ func TestSet(t *testing.T) {
|
||||||
// only test for panics
|
// only test for panics
|
||||||
// TODO: write real tests
|
// TODO: write real tests
|
||||||
setSelectedSecurityLevel(0)
|
setSelectedSecurityLevel(0)
|
||||||
SetPortmasterStatus(0, "")
|
|
||||||
SetGate17Status(0, "")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,16 +28,8 @@ type SystemStatus struct {
|
||||||
ActiveSecurityLevel uint8
|
ActiveSecurityLevel uint8
|
||||||
SelectedSecurityLevel uint8
|
SelectedSecurityLevel uint8
|
||||||
|
|
||||||
PortmasterStatus uint8
|
|
||||||
PortmasterStatusMsg string
|
|
||||||
|
|
||||||
Gate17Status uint8
|
|
||||||
Gate17StatusMsg string
|
|
||||||
|
|
||||||
ThreatMitigationLevel uint8
|
ThreatMitigationLevel uint8
|
||||||
Threats map[string]*Threat
|
Threats map[string]*Threat
|
||||||
|
|
||||||
UpdateStatus string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save saves the SystemStatus to the database
|
// Save saves the SystemStatus to the database
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
package status
|
|
||||||
|
|
||||||
// Update status options
|
|
||||||
const (
|
|
||||||
UpdateStatusCurrentStable = "stable"
|
|
||||||
UpdateStatusCurrentBeta = "beta"
|
|
||||||
UpdateStatusAvailable = "available" // restart or reboot required
|
|
||||||
UpdateStatusFailed = "failed" // check logs
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetUpdateStatus updates the system status with a new update status.
|
|
||||||
func SetUpdateStatus(newStatus string) {
|
|
||||||
status.Lock()
|
|
||||||
status.UpdateStatus = newStatus
|
|
||||||
status.Unlock()
|
|
||||||
|
|
||||||
go status.Save()
|
|
||||||
}
|
|
20
test
20
test
|
@ -12,9 +12,8 @@ function help {
|
||||||
echo ""
|
echo ""
|
||||||
echo "commands:"
|
echo "commands:"
|
||||||
echo " <none> run baseline tests"
|
echo " <none> run baseline tests"
|
||||||
echo " all run all tests"
|
echo " full run full tests (ie. not short)"
|
||||||
echo " install install deps for running baseline tests"
|
echo " install install deps for running tests"
|
||||||
echo " install all install deps for running all tests"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "options:"
|
echo "options:"
|
||||||
echo " --scripted dont jump console lines (still use colors)"
|
echo " --scripted dont jump console lines (still use colors)"
|
||||||
|
@ -97,7 +96,7 @@ while true; do
|
||||||
install=1
|
install=1
|
||||||
shift 1
|
shift 1
|
||||||
;;
|
;;
|
||||||
"all")
|
"full")
|
||||||
fullTestFlags=""
|
fullTestFlags=""
|
||||||
shift 1
|
shift 1
|
||||||
;;
|
;;
|
||||||
|
@ -117,8 +116,9 @@ if [[ $install -eq 1 ]]; then
|
||||||
echo "installing dependencies..."
|
echo "installing dependencies..."
|
||||||
echo "$ go get -u golang.org/x/lint/golint"
|
echo "$ go get -u golang.org/x/lint/golint"
|
||||||
go get -u golang.org/x/lint/golint
|
go get -u golang.org/x/lint/golint
|
||||||
echo "$ go get -u github.com/golangci/golangci-lint/cmd/golangci-lint"
|
# TODO: update golangci-lint version regularly
|
||||||
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
echo "$ curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0"
|
||||||
|
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.24.0
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -139,11 +139,11 @@ if [[ $(which golint) == "" ]]; then
|
||||||
fi
|
fi
|
||||||
if [[ $(which golangci-lint) == "" ]]; then
|
if [[ $(which golangci-lint) == "" ]]; then
|
||||||
echo "golangci-lint command not found"
|
echo "golangci-lint command not found"
|
||||||
echo "install locally with: go get -u github.com/golangci/golangci-lint/cmd/golangci-lint"
|
echo "install with: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin vX.Y.Z"
|
||||||
echo "or run: ./test install all"
|
|
||||||
echo ""
|
|
||||||
echo "hint: install for CI with: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin vX.Y.Z"
|
|
||||||
echo "don't forget to specify the version you want"
|
echo "don't forget to specify the version you want"
|
||||||
|
echo "or run: ./test install"
|
||||||
|
echo ""
|
||||||
|
echo "alternatively, install the current dev version with: go get -u github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
40
ui/module.go
40
ui/module.go
|
@ -1,13 +1,51 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
resources "github.com/cookieo9/resources-go"
|
||||||
|
"github.com/safing/portbase/log"
|
||||||
"github.com/safing/portbase/modules"
|
"github.com/safing/portbase/modules"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
eventReload = "reload"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
module *modules.Module
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
modules.Register("ui", prep, nil, nil, "api", "updates")
|
module = modules.Register("ui", prep, start, nil, "api", "updates")
|
||||||
}
|
}
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
|
module.RegisterEvent(eventReload)
|
||||||
|
|
||||||
return registerRoutes()
|
return registerRoutes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func start() error {
|
||||||
|
return module.RegisterEventHook("ui", eventReload, "reload assets", reloadUI)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reloadUI(ctx context.Context, _ interface{}) error {
|
||||||
|
log.Info("core: user/UI requested UI reload")
|
||||||
|
|
||||||
|
appsLock.Lock()
|
||||||
|
defer appsLock.Unlock()
|
||||||
|
|
||||||
|
// close all bundles
|
||||||
|
for id, bundle := range apps {
|
||||||
|
err := bundle.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Warningf("ui: failed to close bundle %s: %s", id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset index
|
||||||
|
apps = make(map[string]*resources.BundleSequence)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,14 +5,21 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/safing/portbase/config"
|
"github.com/safing/portbase/config"
|
||||||
|
"github.com/safing/portbase/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cfgDevModeKey = "core/devMode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
releaseChannel config.StringOption
|
releaseChannel config.StringOption
|
||||||
devMode config.BoolOption
|
devMode config.BoolOption
|
||||||
|
disableUpdates config.BoolOption
|
||||||
|
|
||||||
previousReleaseChannel string
|
previousReleaseChannel string
|
||||||
previousDevMode bool
|
updatesCurrentlyDisabled bool
|
||||||
|
previousDevMode bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerConfig() error {
|
func registerConfig() error {
|
||||||
|
@ -20,6 +27,7 @@ func registerConfig() error {
|
||||||
Name: "Release Channel",
|
Name: "Release Channel",
|
||||||
Key: releaseChannelKey,
|
Key: releaseChannelKey,
|
||||||
Description: "The Release Channel changes which updates are applied. When using beta, you will receive new features earlier and Portmaster will update more frequently. Some beta or experimental features are also available in the stable release channel.",
|
Description: "The Release Channel changes which updates are applied. When using beta, you will receive new features earlier and Portmaster will update more frequently. Some beta or experimental features are also available in the stable release channel.",
|
||||||
|
Order: 1,
|
||||||
OptType: config.OptTypeString,
|
OptType: config.OptTypeString,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelBeta,
|
ReleaseLevel: config.ReleaseLevelBeta,
|
||||||
|
@ -32,16 +40,40 @@ func registerConfig() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return module.RegisterEventHook("config", "config change", "update registry config", updateRegistryConfig)
|
err = config.Register(&config.Option{
|
||||||
|
Name: "Disable Updates",
|
||||||
|
Key: disableUpdatesKey,
|
||||||
|
Description: "Disable automatic updates.",
|
||||||
|
Order: 64,
|
||||||
|
OptType: config.OptTypeBool,
|
||||||
|
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
|
ReleaseLevel: config.ReleaseLevelStable,
|
||||||
|
RequiresRestart: false,
|
||||||
|
DefaultValue: false,
|
||||||
|
ExternalOptType: "disable updates",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConfig() {
|
func initConfig() {
|
||||||
releaseChannel = config.GetAsString(releaseChannelKey, releaseChannelStable)
|
releaseChannel = config.GetAsString(releaseChannelKey, releaseChannelStable)
|
||||||
devMode = config.GetAsBool("core/devMode", false)
|
previousReleaseChannel = releaseChannel()
|
||||||
|
|
||||||
|
disableUpdates = config.GetAsBool(disableUpdatesKey, false)
|
||||||
|
updatesCurrentlyDisabled = disableUpdates()
|
||||||
|
|
||||||
|
devMode = config.GetAsBool(cfgDevModeKey, false)
|
||||||
|
previousDevMode = devMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateRegistryConfig(_ context.Context, _ interface{}) error {
|
func updateRegistryConfig(_ context.Context, _ interface{}) error {
|
||||||
changed := false
|
changed := false
|
||||||
|
forceUpdate := false
|
||||||
|
|
||||||
if releaseChannel() != previousReleaseChannel {
|
if releaseChannel() != previousReleaseChannel {
|
||||||
registry.SetBeta(releaseChannel() == releaseChannelBeta)
|
registry.SetBeta(releaseChannel() == releaseChannelBeta)
|
||||||
previousReleaseChannel = releaseChannel()
|
previousReleaseChannel = releaseChannel()
|
||||||
|
@ -49,14 +81,29 @@ func updateRegistryConfig(_ context.Context, _ interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if devMode() != previousDevMode {
|
if devMode() != previousDevMode {
|
||||||
registry.SetBeta(devMode())
|
registry.SetDevMode(devMode())
|
||||||
previousDevMode = devMode()
|
previousDevMode = devMode()
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if disableUpdates() != updatesCurrentlyDisabled {
|
||||||
|
updatesCurrentlyDisabled = disableUpdates()
|
||||||
|
changed = true
|
||||||
|
forceUpdate = !updatesCurrentlyDisabled
|
||||||
|
}
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
registry.SelectVersions()
|
registry.SelectVersions()
|
||||||
module.TriggerEvent(VersionUpdateEvent, nil)
|
module.TriggerEvent(VersionUpdateEvent, nil)
|
||||||
|
|
||||||
|
if forceUpdate {
|
||||||
|
module.Resolve(updateFailed)
|
||||||
|
_ = TriggerUpdate()
|
||||||
|
log.Infof("updates: automatic updates enabled again.")
|
||||||
|
} else if updatesCurrentlyDisabled {
|
||||||
|
module.Warning(updateFailed, "Automatic updates are disabled! This also affects security updates and threat intelligence.")
|
||||||
|
log.Warningf("updates: automatic updates are now disabled.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -19,6 +19,8 @@ const (
|
||||||
releaseChannelStable = "stable"
|
releaseChannelStable = "stable"
|
||||||
releaseChannelBeta = "beta"
|
releaseChannelBeta = "beta"
|
||||||
|
|
||||||
|
disableUpdatesKey = "core/disableUpdates"
|
||||||
|
|
||||||
// ModuleName is the name of the update module
|
// ModuleName is the name of the update module
|
||||||
// and can be used when declaring module dependencies.
|
// and can be used when declaring module dependencies.
|
||||||
ModuleName = "updates"
|
ModuleName = "updates"
|
||||||
|
@ -36,6 +38,10 @@ const (
|
||||||
// to check if new versions of their resources are
|
// to check if new versions of their resources are
|
||||||
// available by checking File.UpgradeAvailable().
|
// available by checking File.UpgradeAvailable().
|
||||||
ResourceUpdateEvent = "resource update"
|
ResourceUpdateEvent = "resource update"
|
||||||
|
|
||||||
|
// TriggerUpdateEvent is the event that can be emitted
|
||||||
|
// by the updates module to trigger an update.
|
||||||
|
TriggerUpdateEvent = "trigger update"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -46,15 +52,51 @@ var (
|
||||||
disableTaskSchedule bool
|
disableTaskSchedule bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
updateInProgress = "update-in-progress"
|
||||||
|
updateInProcessDescr = "Portmaster is currently checking and downloading updates."
|
||||||
|
updateFailed = "update-failed"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
module = modules.Register(ModuleName, registerConfig, start, stop, "base")
|
module = modules.Register(ModuleName, prep, start, stop, "base")
|
||||||
module.RegisterEvent(VersionUpdateEvent)
|
module.RegisterEvent(VersionUpdateEvent)
|
||||||
module.RegisterEvent(ResourceUpdateEvent)
|
module.RegisterEvent(ResourceUpdateEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prep() error {
|
||||||
|
if err := registerConfig(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
module.RegisterEvent(TriggerUpdateEvent)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func start() error {
|
func start() error {
|
||||||
initConfig()
|
initConfig()
|
||||||
|
|
||||||
|
if err := module.RegisterEventHook(
|
||||||
|
"config",
|
||||||
|
"config change",
|
||||||
|
"update registry config",
|
||||||
|
updateRegistryConfig); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := module.RegisterEventHook(
|
||||||
|
module.Name,
|
||||||
|
TriggerUpdateEvent,
|
||||||
|
"Check for and download available updates",
|
||||||
|
func(context.Context, interface{}) error {
|
||||||
|
_ = TriggerUpdate()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
var mandatoryUpdates []string
|
var mandatoryUpdates []string
|
||||||
if onWindows {
|
if onWindows {
|
||||||
mandatoryUpdates = []string{
|
mandatoryUpdates = []string{
|
||||||
|
@ -115,8 +157,8 @@ func start() error {
|
||||||
|
|
||||||
if !disableTaskSchedule {
|
if !disableTaskSchedule {
|
||||||
updateTask.
|
updateTask.
|
||||||
Repeat(24 * time.Hour).
|
Repeat(1 * time.Hour).
|
||||||
MaxDelay(1 * time.Hour).
|
MaxDelay(30 * time.Minute).
|
||||||
Schedule(time.Now().Add(10 * time.Second))
|
Schedule(time.Now().Add(10 * time.Second))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +180,7 @@ func TriggerUpdate() error {
|
||||||
updateASAP = true
|
updateASAP = true
|
||||||
} else {
|
} else {
|
||||||
updateTask.StartASAP()
|
updateTask.StartASAP()
|
||||||
|
log.Debugf("updates: triggering update to run as soon as possible")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -156,14 +199,32 @@ func DisableUpdateSchedule() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkForUpdates(ctx context.Context) error {
|
func checkForUpdates(ctx context.Context) (err error) {
|
||||||
if err := registry.UpdateIndexes(); err != nil {
|
if updatesCurrentlyDisabled {
|
||||||
return fmt.Errorf("updates: failed to update indexes: %w", err)
|
log.Debugf("updates: automatic updates are disabled")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer log.Debugf("updates: finished checking for updates")
|
||||||
|
|
||||||
|
module.Hint(updateInProgress, updateInProcessDescr)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
module.Resolve(updateInProgress)
|
||||||
|
} else {
|
||||||
|
module.Warning(updateFailed, "Failed to check for updates: "+err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = registry.UpdateIndexes(); err != nil {
|
||||||
|
err = fmt.Errorf("failed to update indexes: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := registry.DownloadUpdates(ctx)
|
err = registry.DownloadUpdates(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("updates: failed to update: %w", err)
|
err = fmt.Errorf("failed to update: %w", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
registry.SelectVersions()
|
registry.SelectVersions()
|
||||||
|
|
Loading…
Add table
Reference in a new issue