mirror of
https://github.com/safing/portmaster
synced 2025-09-02 10:39:22 +00:00
Integrate SPN route manipulation settings
This commit is contained in:
parent
0bb5f2901e
commit
7d315e92be
6 changed files with 188 additions and 69 deletions
|
@ -25,9 +25,6 @@ import (
|
||||||
"github.com/safing/portmaster/network"
|
"github.com/safing/portmaster/network"
|
||||||
"github.com/safing/portmaster/network/netutils"
|
"github.com/safing/portmaster/network/netutils"
|
||||||
"github.com/safing/portmaster/network/packet"
|
"github.com/safing/portmaster/network/packet"
|
||||||
"github.com/safing/spn/captain"
|
|
||||||
"github.com/safing/spn/crew"
|
|
||||||
"github.com/safing/spn/sluice"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -335,9 +332,6 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
||||||
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
||||||
conn.Internal = true
|
conn.Internal = true
|
||||||
|
|
||||||
// Set tunnel options.
|
|
||||||
setCustomTunnelOptionsForPortmaster(conn)
|
|
||||||
|
|
||||||
// Redirect outbound DNS packests,
|
// Redirect outbound DNS packests,
|
||||||
case pkt.IsOutbound() &&
|
case pkt.IsOutbound() &&
|
||||||
pkt.Info().DstPort == 53 &&
|
pkt.Info().DstPort == 53 &&
|
||||||
|
@ -368,41 +362,6 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
||||||
conn.Accept("privacy filter disabled", noReasonOptionKey)
|
conn.Accept("privacy filter disabled", noReasonOptionKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tunnel, if enabled.
|
|
||||||
if pkt.IsOutbound() && conn.Entity.IPScope.IsGlobal() &&
|
|
||||||
tunnelEnabled() && conn.Verdict == network.VerdictAccept &&
|
|
||||||
conn.Process().Profile() != nil &&
|
|
||||||
conn.Process().Profile().UseSPN() {
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case captain.ClientBootstrapping() &&
|
|
||||||
conn.Process().Pid == ownPID:
|
|
||||||
// Exclude the Portmaster during SPN bootstrapping.
|
|
||||||
|
|
||||||
case captain.IsExcepted(conn.Entity.IP) &&
|
|
||||||
conn.Process().Pid == ownPID:
|
|
||||||
// Exclude requests of the SPN itself.
|
|
||||||
|
|
||||||
case captain.ClientReady():
|
|
||||||
// Queue request in sluice.
|
|
||||||
err := sluice.AwaitRequest(conn, crew.HandleSluiceRequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Tracer(pkt.Ctx()).Warningf("failed to rqeuest tunneling: %s", err)
|
|
||||||
conn.Failed("failed to request tunneling", "")
|
|
||||||
} else {
|
|
||||||
log.Tracer(pkt.Ctx()).Trace("filter: tunneling requested")
|
|
||||||
conn.Verdict = network.VerdictRerouteToTunnel
|
|
||||||
conn.Tunneled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Block connection as SPN is not ready yet.
|
|
||||||
log.Tracer(pkt.Ctx()).Trace("SPN not ready for tunneling")
|
|
||||||
conn.Failed("SPN not ready for tunneling", "")
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Enable inspection framework again.
|
// TODO: Enable inspection framework again.
|
||||||
conn.Inspecting = false
|
conn.Inspecting = false
|
||||||
|
|
||||||
|
@ -419,6 +378,9 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
||||||
conn.Encrypted = true
|
conn.Encrypted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if connection should be tunneled.
|
||||||
|
checkTunneling(pkt.Ctx(), conn, pkt)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case conn.Inspecting:
|
case conn.Inspecting:
|
||||||
log.Tracer(pkt.Ctx()).Trace("filter: start inspecting")
|
log.Tracer(pkt.Ctx()).Trace("filter: start inspecting")
|
||||||
|
|
|
@ -140,10 +140,6 @@ func checkPortmasterConnection(ctx context.Context, conn *network.Connection, _
|
||||||
log.Tracer(ctx).Infof("filter: granting own connection %s", conn)
|
log.Tracer(ctx).Infof("filter: granting own connection %s", conn)
|
||||||
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
||||||
conn.Internal = true
|
conn.Internal = true
|
||||||
|
|
||||||
// Set tunnel options.
|
|
||||||
setCustomTunnelOptionsForPortmaster(conn)
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,114 @@
|
||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/safing/portbase/log"
|
||||||
|
"github.com/safing/portmaster/network/packet"
|
||||||
|
"github.com/safing/portmaster/profile"
|
||||||
|
"github.com/safing/portmaster/profile/endpoints"
|
||||||
|
|
||||||
"github.com/safing/portmaster/network"
|
"github.com/safing/portmaster/network"
|
||||||
"github.com/safing/portmaster/resolver"
|
"github.com/safing/portmaster/resolver"
|
||||||
|
"github.com/safing/spn/captain"
|
||||||
|
"github.com/safing/spn/crew"
|
||||||
"github.com/safing/spn/navigator"
|
"github.com/safing/spn/navigator"
|
||||||
|
"github.com/safing/spn/sluice"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setCustomTunnelOptionsForPortmaster(conn *network.Connection) {
|
func checkTunneling(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
|
||||||
|
// Check if the connection should be tunneled at all.
|
||||||
switch {
|
switch {
|
||||||
case !tunnelEnabled():
|
case !tunnelEnabled():
|
||||||
// Ignore when tunneling is not enabled.
|
// Tunneling is disabled.
|
||||||
return
|
return
|
||||||
case !conn.Entity.IPScope.IsGlobal():
|
case !conn.Entity.IPScope.IsGlobal():
|
||||||
// Ignore if destination is not in global address space.
|
// Can't tunnel Local/LAN connections.
|
||||||
|
return
|
||||||
|
case conn.Inbound:
|
||||||
|
// Can't tunnel incoming connections.
|
||||||
|
return
|
||||||
|
case conn.Verdict != network.VerdictAccept:
|
||||||
|
// Connection will be blocked.
|
||||||
|
return
|
||||||
|
case conn.Process().Pid == ownPID:
|
||||||
|
// Bypass tunneling for certain own connections.
|
||||||
|
switch {
|
||||||
|
case captain.ClientBootstrapping():
|
||||||
|
return
|
||||||
|
case captain.IsExcepted(conn.Entity.IP):
|
||||||
return
|
return
|
||||||
case resolver.IsResolverAddress(conn.Entity.IP, conn.Entity.Port):
|
|
||||||
// Set custom tunnel options for DNS servers.
|
|
||||||
conn.TunnelOpts = &navigator.Options{
|
|
||||||
RoutingProfile: navigator.RoutingProfileHomeName,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get profile.
|
||||||
|
layeredProfile := conn.Process().Profile()
|
||||||
|
if layeredProfile == nil {
|
||||||
|
conn.Failed("no profile set", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update profile.
|
||||||
|
if layeredProfile.NeedsUpdate() {
|
||||||
|
// Update revision counter in connection.
|
||||||
|
conn.ProfileRevisionCounter = layeredProfile.Update()
|
||||||
|
conn.SaveWhenFinished()
|
||||||
|
} else {
|
||||||
|
// Check if the revision counter of the connection needs updating.
|
||||||
|
revCnt := layeredProfile.RevisionCnt()
|
||||||
|
if conn.ProfileRevisionCounter != revCnt {
|
||||||
|
conn.ProfileRevisionCounter = revCnt
|
||||||
|
conn.SaveWhenFinished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if tunneling is enabeld for app at all.
|
||||||
|
if !layeredProfile.UseSPN() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if tunneling is enabled for entity.
|
||||||
|
conn.Entity.FetchData(ctx)
|
||||||
|
result, _ := layeredProfile.MatchSPNUsagePolicy(ctx, conn.Entity)
|
||||||
|
switch result {
|
||||||
|
case endpoints.MatchError:
|
||||||
|
conn.Failed("failed to check SPN rules", profile.CfgOptionSPNUsagePolicyKey)
|
||||||
|
return
|
||||||
|
case endpoints.Denied:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tunnel all the things!
|
||||||
|
|
||||||
|
// Check if ready.
|
||||||
|
if !captain.ClientReady() {
|
||||||
|
// Block connection as SPN is not ready yet.
|
||||||
|
log.Tracer(pkt.Ctx()).Trace("SPN not ready for tunneling")
|
||||||
|
conn.Failed("SPN not ready for tunneling", "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set options.
|
||||||
|
conn.TunnelOpts = &navigator.Options{
|
||||||
|
HubPolicies: layeredProfile.StackedExitHubPolicies(),
|
||||||
|
CheckHubExitPolicyWith: conn.Entity,
|
||||||
|
RequireTrustedDestinationHubs: conn.Encrypted,
|
||||||
|
RoutingProfile: layeredProfile.SPNRoutingAlgorithm(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for the internal DNS resolver.
|
||||||
|
if conn.Process().Pid == ownPID && resolver.IsResolverAddress(conn.Entity.IP, conn.Entity.Port) {
|
||||||
|
conn.TunnelOpts.RoutingProfile = navigator.RoutingProfileHomeID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue request in sluice.
|
||||||
|
err := sluice.AwaitRequest(conn, crew.HandleSluiceRequest)
|
||||||
|
if err != nil {
|
||||||
|
log.Tracer(pkt.Ctx()).Warningf("failed to rqeuest tunneling: %s", err)
|
||||||
|
conn.Failed("failed to request tunneling", "")
|
||||||
|
} else {
|
||||||
|
log.Tracer(pkt.Ctx()).Trace("filter: tunneling requested")
|
||||||
|
conn.Verdict = network.VerdictRerouteToTunnel
|
||||||
|
conn.Tunneled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ var (
|
||||||
cfgDefaultAction uint8
|
cfgDefaultAction uint8
|
||||||
cfgEndpoints endpoints.Endpoints
|
cfgEndpoints endpoints.Endpoints
|
||||||
cfgServiceEndpoints endpoints.Endpoints
|
cfgServiceEndpoints endpoints.Endpoints
|
||||||
|
cfgSPNUsagePolicy endpoints.Endpoints
|
||||||
|
cfgSPNExitHubPolicy endpoints.Endpoints
|
||||||
cfgFilterLists []string
|
cfgFilterLists []string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,6 +77,20 @@ func updateGlobalConfigProfile(ctx context.Context, task *modules.Task) error {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list = cfgOptionSPNUsagePolicy()
|
||||||
|
cfgSPNUsagePolicy, err = endpoints.ParseEndpoints(list)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: module error?
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
|
||||||
|
list = cfgOptionExitHubPolicy()
|
||||||
|
cfgSPNExitHubPolicy, err = endpoints.ParseEndpoints(list)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: module error?
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
|
||||||
// build global profile for reference
|
// build global profile for reference
|
||||||
profile := New(SourceSpecial, "global-config", "", nil)
|
profile := New(SourceSpecial, "global-config", "", nil)
|
||||||
profile.Name = "Global Configuration"
|
profile.Name = "Global Configuration"
|
||||||
|
|
|
@ -48,6 +48,7 @@ type LayeredProfile struct {
|
||||||
PreventBypassing config.BoolOption `json:"-"`
|
PreventBypassing config.BoolOption `json:"-"`
|
||||||
DomainHeuristics config.BoolOption `json:"-"`
|
DomainHeuristics config.BoolOption `json:"-"`
|
||||||
UseSPN config.BoolOption `json:"-"`
|
UseSPN config.BoolOption `json:"-"`
|
||||||
|
SPNRoutingAlgorithm config.StringOption `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLayeredProfile returns a new layered profile based on the given local profile.
|
// NewLayeredProfile returns a new layered profile based on the given local profile.
|
||||||
|
@ -115,6 +116,10 @@ func NewLayeredProfile(localProfile *Profile) *LayeredProfile {
|
||||||
CfgOptionUseSPNKey,
|
CfgOptionUseSPNKey,
|
||||||
cfgOptionUseSPN,
|
cfgOptionUseSPN,
|
||||||
)
|
)
|
||||||
|
lp.SPNRoutingAlgorithm = lp.wrapStringOption(
|
||||||
|
CfgOptionRoutingAlgorithmKey,
|
||||||
|
cfgOptionRoutingAlgorithm,
|
||||||
|
)
|
||||||
|
|
||||||
lp.LayerIDs = append(lp.LayerIDs, localProfile.ScopedID())
|
lp.LayerIDs = append(lp.LayerIDs, localProfile.ScopedID())
|
||||||
lp.layers = append(lp.layers, localProfile)
|
lp.layers = append(lp.layers, localProfile)
|
||||||
|
@ -334,6 +339,39 @@ func (lp *LayeredProfile) MatchServiceEndpoint(ctx context.Context, entity *inte
|
||||||
return cfgServiceEndpoints.Match(ctx, entity)
|
return cfgServiceEndpoints.Match(ctx, entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchSPNUsagePolicy checks if the given endpoint matches an entry in any of the profiles. This functions requires the layered profile to be read locked.
|
||||||
|
func (lp *LayeredProfile) MatchSPNUsagePolicy(ctx context.Context, entity *intel.Entity) (endpoints.EPResult, endpoints.Reason) {
|
||||||
|
for _, layer := range lp.layers {
|
||||||
|
if layer.spnUsagePolicy.IsSet() {
|
||||||
|
result, reason := layer.spnUsagePolicy.Match(ctx, entity)
|
||||||
|
if endpoints.IsDecision(result) {
|
||||||
|
return result, reason
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgLock.RLock()
|
||||||
|
defer cfgLock.RUnlock()
|
||||||
|
return cfgSPNUsagePolicy.Match(ctx, entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackedExitHubPolicies returns all exit hub policies of the layered profile, including the global one.
|
||||||
|
func (lp *LayeredProfile) StackedExitHubPolicies() []endpoints.Endpoints {
|
||||||
|
policies := make([]endpoints.Endpoints, 0, len(lp.layers)+3) // +1 for global policy, +2 for intel policies
|
||||||
|
|
||||||
|
for _, layer := range lp.layers {
|
||||||
|
if layer.spnExitHubPolicy.IsSet() {
|
||||||
|
policies = append(policies, layer.spnExitHubPolicy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgLock.RLock()
|
||||||
|
defer cfgLock.RUnlock()
|
||||||
|
policies = append(policies, cfgSPNExitHubPolicy)
|
||||||
|
|
||||||
|
return policies
|
||||||
|
}
|
||||||
|
|
||||||
// MatchFilterLists matches the entity against the set of filter
|
// MatchFilterLists matches the entity against the set of filter
|
||||||
// lists. This functions requires the layered profile to be read locked.
|
// lists. This functions requires the layered profile to be read locked.
|
||||||
func (lp *LayeredProfile) MatchFilterLists(ctx context.Context, entity *intel.Entity) (endpoints.EPResult, endpoints.Reason) {
|
func (lp *LayeredProfile) MatchFilterLists(ctx context.Context, entity *intel.Entity) (endpoints.EPResult, endpoints.Reason) {
|
||||||
|
@ -451,9 +489,6 @@ func (lp *LayeredProfile) GetProfileSource(configKey string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
For later:
|
|
||||||
|
|
||||||
func (lp *LayeredProfile) wrapStringOption(configKey string, globalConfig config.StringOption) config.StringOption {
|
func (lp *LayeredProfile) wrapStringOption(configKey string, globalConfig config.StringOption) config.StringOption {
|
||||||
var revCnt uint64 = 0
|
var revCnt uint64 = 0
|
||||||
var value string
|
var value string
|
||||||
|
@ -485,7 +520,6 @@ func (lp *LayeredProfile) wrapStringOption(configKey string, globalConfig config
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
func max(a, b uint8) uint8 {
|
func max(a, b uint8) uint8 {
|
||||||
if a > b {
|
if a > b {
|
||||||
|
|
|
@ -128,6 +128,8 @@ type Profile struct { //nolint:maligned // not worth the effort
|
||||||
serviceEndpoints endpoints.Endpoints
|
serviceEndpoints endpoints.Endpoints
|
||||||
filterListsSet bool
|
filterListsSet bool
|
||||||
filterListIDs []string
|
filterListIDs []string
|
||||||
|
spnUsagePolicy endpoints.Endpoints
|
||||||
|
spnExitHubPolicy endpoints.Endpoints
|
||||||
|
|
||||||
// Lifecycle Management
|
// Lifecycle Management
|
||||||
outdated *abool.AtomicBool
|
outdated *abool.AtomicBool
|
||||||
|
@ -202,6 +204,24 @@ func (profile *Profile) parseConfig() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list, ok = profile.configPerspective.GetAsStringArray(CfgOptionSPNUsagePolicyKey)
|
||||||
|
profile.spnUsagePolicy = nil
|
||||||
|
if ok {
|
||||||
|
profile.spnUsagePolicy, err = endpoints.ParseEndpoints(list)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list, ok = profile.configPerspective.GetAsStringArray(CfgOptionExitHubPolicyKey)
|
||||||
|
profile.spnExitHubPolicy = nil
|
||||||
|
if ok {
|
||||||
|
profile.spnExitHubPolicy, err = endpoints.ParseEndpoints(list)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return lastErr
|
return lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue