Continue with the new profile integration

This commit is contained in:
Daniel 2020-04-01 17:15:33 +02:00
parent 5523fcf0bd
commit 200d9000f6
19 changed files with 509 additions and 557 deletions

View file

@ -9,15 +9,12 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/safing/portbase/utils" "github.com/safing/portbase/api"
"github.com/safing/portmaster/core/structure" "github.com/safing/portbase/dataroot"
"github.com/safing/portbase/log" "github.com/safing/portbase/log"
"github.com/safing/portbase/utils"
"github.com/safing/portmaster/network/packet" "github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/process" "github.com/safing/portmaster/process"
"github.com/safing/portbase/api"
) )
var ( var (
@ -28,7 +25,7 @@ var (
) )
func prepAPIAuth() error { func prepAPIAuth() error {
dataRoot = structure.Root() dataRoot = dataroot.Root()
return api.SetAuthenticator(apiAuthenticator) return api.SetAuthenticator(apiAuthenticator)
} }

View file

@ -4,12 +4,12 @@ import (
"fmt" "fmt"
"net" "net"
"github.com/safing/portmaster/intel"
"github.com/safing/portmaster/network/environment" "github.com/safing/portmaster/network/environment"
"github.com/safing/portmaster/resolver"
) )
func init() { func init() {
intel.SetLocalAddrFactory(PermittedAddr) resolver.SetLocalAddrFactory(PermittedAddr)
environment.SetLocalAddrFactory(PermittedAddr) environment.SetLocalAddrFactory(PermittedAddr)
} }

View file

@ -7,6 +7,9 @@ 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"
@ -41,11 +44,26 @@ var (
) )
func init() { func init() {
module = modules.Register("firewall", prep, start, stop, "core", "network", "nameserver", "profile", "updates") module = modules.Register("firewall", prep, start, stop, "core", "network", "resolver", "intel", "processes")
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,
},
)
} }
func prep() (err error) { func prep() (err error) {
err = registerConfig() err = registerConfig()
if err != nil { if err != nil {
return err return err
@ -188,22 +206,18 @@ func handlePacket(pkt packet.Packet) {
// associate packet to link and handle // associate packet to link and handle
link, created := network.GetOrCreateLinkByPacket(pkt) link, created := network.GetOrCreateLinkByPacket(pkt)
defer func() {
go link.SaveIfNeeded()
}()
if created { if created {
link.SetFirewallHandler(initialHandler) link.SetFirewallHandler(initialHandler)
link.HandlePacket(pkt)
return
} }
if link.FirewallHandlerIsSet() {
link.HandlePacket(pkt) link.HandlePacket(pkt)
return
}
issueVerdict(pkt, link, 0, true)
} }
func initialHandler(pkt packet.Packet, link *network.Link) { func initialHandler(pkt packet.Packet, link *network.Link) {
defer func() {
go link.SaveIfNeeded()
}()
log.Tracer(pkt.Ctx()).Trace("firewall: [initial handler]") log.Tracer(pkt.Ctx()).Trace("firewall: [initial handler]")
// check for internal firewall bypass // check for internal firewall bypass
@ -217,9 +231,6 @@ func initialHandler(pkt packet.Packet, link *network.Link) {
} else { } else {
comm.AddLink(link) comm.AddLink(link)
} }
defer func() {
go comm.SaveIfNeeded()
}()
// approve // approve
link.Accept("internally approved") link.Accept("internally approved")
@ -249,9 +260,6 @@ func initialHandler(pkt packet.Packet, link *network.Link) {
return return
} }
} }
defer func() {
go comm.SaveIfNeeded()
}()
// add new Link to Communication (and save both) // add new Link to Communication (and save both)
comm.AddLink(link) comm.AddLink(link)
@ -267,7 +275,8 @@ func initialHandler(pkt packet.Packet, link *network.Link) {
log.Tracer(pkt.Ctx()).Trace("firewall: starting decision process") log.Tracer(pkt.Ctx()).Trace("firewall: starting decision process")
DecideOnCommunication(comm, pkt) // TODO: filter lists may have IPs in the future!
DecideOnCommunication(comm)
DecideOnLink(comm, link, pkt) DecideOnLink(comm, link, pkt)
// TODO: link this to real status // TODO: link this to real status
@ -380,7 +389,7 @@ func issueVerdict(pkt packet.Packet, link *network.Link, verdict network.Verdict
func run() { func run() {
for { for {
select { select {
case <-modules.ShuttingDown(): case <-module.Stopping():
return return
case pkt := <-interception.Packets: case pkt := <-interception.Packets:
handlePacket(pkt) handlePacket(pkt)
@ -391,7 +400,7 @@ func run() {
func statLogger() { func statLogger() {
for { for {
select { select {
case <-modules.ShuttingDown(): case <-module.Stopping():
return return
case <-time.After(10 * time.Second): case <-time.After(10 * time.Second):
log.Tracef("firewall: packets accepted %d, blocked %d, dropped %d", atomic.LoadUint64(packetsAccepted), atomic.LoadUint64(packetsBlocked), atomic.LoadUint64(packetsDropped)) log.Tracef("firewall: packets accepted %d, blocked %d, dropped %d", atomic.LoadUint64(packetsAccepted), atomic.LoadUint64(packetsBlocked), atomic.LoadUint64(packetsDropped))

View file

@ -4,19 +4,20 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/miekg/dns"
"github.com/safing/portbase/log" "github.com/safing/portbase/log"
"github.com/safing/portmaster/intel"
"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/portmaster/process" "github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile" "github.com/safing/portmaster/profile"
"github.com/safing/portmaster/status" "github.com/safing/portmaster/profile/endpoints"
"github.com/safing/portmaster/resolver"
"github.com/agext/levenshtein" "github.com/agext/levenshtein"
"github.com/miekg/dns"
) )
// Call order: // Call order:
@ -30,11 +31,10 @@ import (
// 4. DecideOnLink // 4. DecideOnLink
// is called when when the first packet of a link arrives only if communication has verdict UNDECIDED or CANTSAY // is called when when the first packet of a link arrives only if communication has verdict UNDECIDED or CANTSAY
// DecideOnCommunicationBeforeIntel makes a decision about a communication before the dns query is resolved and intel is gathered. // DecideOnCommunicationBeforeDNS makes a decision about a communication before the dns query is resolved and intel is gathered.
func DecideOnCommunicationBeforeIntel(comm *network.Communication, fqdn string) { func DecideOnCommunicationBeforeDNS(comm *network.Communication) {
// update profiles and check if communication needs reevaluation
// check if communication needs reevaluation if comm.UpdateAndCheck() {
if comm.NeedsReevaluation() {
log.Infof("firewall: re-evaluating verdict on %s", comm) log.Infof("firewall: re-evaluating verdict on %s", comm)
comm.ResetVerdict() comm.ResetVerdict()
} }
@ -51,116 +51,74 @@ func DecideOnCommunicationBeforeIntel(comm *network.Communication, fqdn string)
return return
} }
// get and check profile set // get profile
profileSet := comm.Process().ProfileSet() p := comm.Process().Profile()
if profileSet == nil {
log.Errorf("firewall: denying communication %s, no Profile Set", comm)
comm.Deny("no Profile Set")
return
}
profileSet.Update(status.ActiveSecurityLevel())
// check for any network access // check for any network access
if !profileSet.CheckFlag(profile.Internet) && !profileSet.CheckFlag(profile.LAN) { if p.BlockScopeInternet() && p.BlockScopeLAN() {
log.Infof("firewall: denying communication %s, accessing Internet or LAN not permitted", comm) log.Infof("firewall: denying communication %s, accessing Internet or LAN not permitted", comm)
comm.Deny("accessing Internet or LAN not permitted") comm.Deny("accessing Internet or LAN not permitted")
return return
} }
// continueing with access to either Internet or LAN
// check endpoint list // check endpoint list
result, reason := profileSet.CheckEndpointDomain(fqdn) // FIXME: comm.Entity.Lock()
result, reason := p.MatchEndpoint(comm.Entity)
// FIXME: comm.Entity.Unlock()
switch result { switch result {
case profile.NoMatch: case endpoints.Undeterminable:
comm.UpdateVerdict(network.VerdictUndecided)
if profileSet.GetProfileMode() == profile.Whitelist {
log.Infof("firewall: denying communication %s, domain is not whitelisted", comm)
comm.Deny("domain is not whitelisted")
}
case profile.Undeterminable:
comm.UpdateVerdict(network.VerdictUndeterminable) comm.UpdateVerdict(network.VerdictUndeterminable)
case profile.Denied: return
log.Infof("firewall: denying communication %s, endpoint is blacklisted: %s", comm, reason) case endpoints.Denied:
comm.Deny(fmt.Sprintf("endpoint is blacklisted: %s", reason)) log.Infof("firewall: denying communication %s, domain is blacklisted: %s", comm, reason)
case profile.Permitted: comm.Deny(fmt.Sprintf("domain is blacklisted: %s", reason))
log.Infof("firewall: permitting communication %s, endpoint is whitelisted: %s", comm, reason) return
comm.Accept(fmt.Sprintf("endpoint is whitelisted: %s", reason)) case endpoints.Permitted:
} log.Infof("firewall: permitting communication %s, domain is whitelisted: %s", comm, reason)
} comm.Accept(fmt.Sprintf("domain is whitelisted: %s", reason))
// DecideOnCommunicationAfterIntel makes a decision about a communication after the dns query is resolved and intel is gathered.
func DecideOnCommunicationAfterIntel(comm *network.Communication, fqdn string, rrCache *intel.RRCache) {
// rrCache may be nil, when function is called for re-evaluation by DecideOnCommunication
// check if need to run
if comm.GetVerdict() != network.VerdictUndecided {
return return
} }
// continueing with result == NoMatch
// grant self - should not get here // check default action
if comm.Process().Pid == os.Getpid() { if p.DefaultAction() == profile.DefaultActionPermit {
log.Infof("firewall: granting own communication %s", comm) log.Infof("firewall: permitting communication %s, domain is not blacklisted (default=permit)", comm)
comm.Accept("") comm.Accept("domain is not blacklisted (default=permit)")
return return
} }
// check if there is a profile
profileSet := comm.Process().ProfileSet()
if profileSet == nil {
log.Errorf("firewall: denying communication %s, no Profile Set", comm)
comm.Deny("no Profile Set")
return
}
profileSet.Update(status.ActiveSecurityLevel())
// TODO: Stamp integration
switch profileSet.GetProfileMode() {
case profile.Whitelist:
log.Infof("firewall: denying communication %s, domain is not whitelisted", comm)
comm.Deny("domain is not whitelisted")
return
case profile.Blacklist:
log.Infof("firewall: permitting communication %s, domain is not blacklisted", comm)
comm.Accept("domain is not blacklisted")
return
}
// ProfileMode == Prompt
// check relation // check relation
if profileSet.CheckFlag(profile.Related) { if !p.DisableAutoPermit() {
if checkRelation(comm, fqdn) { if checkRelation(comm) {
return return
} }
} }
// prompt // prompt
prompt(comm, nil, nil, fqdn) if p.DefaultAction() == profile.DefaultActionAsk {
prompt(comm, nil, nil)
return
}
// DefaultAction == DefaultActionBlock
log.Infof("firewall: denying communication %s, domain is not whitelisted (default=block)", comm)
comm.Deny("domain is not whitelisted (default=block)")
return
} }
// FilterDNSResponse filters a dns response according to the application profile and settings. // FilterDNSResponse filters a dns response according to the application profile and settings.
//nolint:gocognit // FIXME func FilterDNSResponse(comm *network.Communication, q *resolver.Query, rrCache *resolver.RRCache) *resolver.RRCache { //nolint:gocognit // TODO
func FilterDNSResponse(comm *network.Communication, q *intel.Query, rrCache *intel.RRCache) *intel.RRCache {
// do not modify own queries - this should not happen anyway // do not modify own queries - this should not happen anyway
if comm.Process().Pid == os.Getpid() { if comm.Process().Pid == os.Getpid() {
return rrCache return rrCache
} }
// check if there is a profile // get profile
profileSet := comm.Process().ProfileSet() p := comm.Process().Profile()
if profileSet == nil {
log.Infof("firewall: blocking dns query of communication %s, no Profile Set", comm)
return nil
}
profileSet.Update(status.ActiveSecurityLevel())
// save config for consistency during function call
secLevel := profileSet.SecurityLevel()
filterByScope := filterDNSByScope(secLevel)
filterByProfile := filterDNSByProfile(secLevel)
// check if DNS response filtering is completely turned off // check if DNS response filtering is completely turned off
if !filterByScope && !filterByProfile { if !p.RemoveOutOfScopeDNS() && !p.RemoveBlockedDNS() {
return rrCache return rrCache
} }
@ -175,7 +133,6 @@ func FilterDNSResponse(comm *network.Communication, q *intel.Query, rrCache *int
// loop vars // loop vars
var classification int8 var classification int8
var ip net.IP var ip net.IP
var result profile.EPResult
// filter function // filter function
filterEntries := func(entries []dns.RR) (goodEntries []dns.RR) { filterEntries := func(entries []dns.RR) (goodEntries []dns.RR) {
@ -196,7 +153,7 @@ func FilterDNSResponse(comm *network.Communication, q *intel.Query, rrCache *int
} }
classification = netutils.ClassifyIP(ip) classification = netutils.ClassifyIP(ip)
if filterByScope { if p.RemoveOutOfScopeDNS() {
switch { switch {
case classification == netutils.HostLocal: case classification == netutils.HostLocal:
// No DNS should return localhost addresses // No DNS should return localhost addresses
@ -211,30 +168,24 @@ func FilterDNSResponse(comm *network.Communication, q *intel.Query, rrCache *int
} }
} }
if filterByProfile { if p.RemoveBlockedDNS() {
// filter by flags // filter by flags
switch { switch {
case !profileSet.CheckFlag(profile.Internet) && classification == netutils.Global: case p.BlockScopeInternet() && classification == netutils.Global:
addressesRemoved++ addressesRemoved++
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String()) rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
continue continue
case !profileSet.CheckFlag(profile.LAN) && (classification == netutils.SiteLocal || classification == netutils.LinkLocal): case p.BlockScopeLAN() && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
addressesRemoved++ addressesRemoved++
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String()) rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
continue continue
case !profileSet.CheckFlag(profile.Localhost) && classification == netutils.HostLocal: case p.BlockScopeLocal() && classification == netutils.HostLocal:
addressesRemoved++ addressesRemoved++
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String()) rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
continue continue
} }
// filter by endpoints // TODO: filter by endpoint list (IP only)
result, _ = profileSet.CheckEndpointIP(q.FQDN, ip, 0, 0, false)
if result == profile.Denied {
addressesRemoved++
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
continue
}
} }
// if survived, add to good entries // if survived, add to good entries
@ -267,17 +218,15 @@ func FilterDNSResponse(comm *network.Communication, q *intel.Query, rrCache *int
} }
// DecideOnCommunication makes a decision about a communication with its first packet. // DecideOnCommunication makes a decision about a communication with its first packet.
func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) { func DecideOnCommunication(comm *network.Communication) {
// update profiles and check if communication needs reevaluation
// check if communication needs reevaluation, if it's not with a domain if comm.UpdateAndCheck() {
if comm.NeedsReevaluation() {
log.Infof("firewall: re-evaluating verdict on %s", comm) log.Infof("firewall: re-evaluating verdict on %s", comm)
comm.ResetVerdict() comm.ResetVerdict()
// if communicating with a domain entity, re-evaluate with Before/AfterIntel // if communicating with a domain entity, re-evaluate with BeforeDNS
if strings.HasSuffix(comm.Domain, ".") { if strings.HasSuffix(comm.Scope, ".") {
DecideOnCommunicationBeforeIntel(comm, comm.Domain) DecideOnCommunicationBeforeDNS(comm)
DecideOnCommunicationAfterIntel(comm, comm.Domain, nil)
} }
} }
@ -293,29 +242,24 @@ func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) {
return return
} }
// check if there is a profile // get profile
profileSet := comm.Process().ProfileSet() p := comm.Process().Profile()
if profileSet == nil {
log.Errorf("firewall: denying communication %s, no Profile Set", comm)
comm.Deny("no Profile Set")
return
}
profileSet.Update(status.ActiveSecurityLevel())
// check comm type // check comm type
switch comm.Domain { switch comm.Scope {
case network.IncomingHost, network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid: case network.IncomingHost, network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid:
if !profileSet.CheckFlag(profile.Service) { if p.BlockInbound() {
log.Infof("firewall: denying communication %s, not a service", comm) log.Infof("firewall: denying communication %s, not a service", comm)
if comm.Domain == network.IncomingHost { if comm.Scope == network.IncomingHost {
comm.Block("not a service") comm.Block("not a service")
} else { } else {
comm.Deny("not a service") comm.Deny("not a service")
} }
return return
} }
case network.PeerLAN, network.PeerInternet, network.PeerInvalid: // Important: PeerHost is and should be missing! case network.PeerLAN, network.PeerInternet, network.PeerInvalid:
if !profileSet.CheckFlag(profile.PeerToPeer) { // Important: PeerHost is and should be missing!
if p.BlockP2P() {
log.Infof("firewall: denying communication %s, peer to peer comms (to an IP) not allowed", comm) log.Infof("firewall: denying communication %s, peer to peer comms (to an IP) not allowed", comm)
comm.Deny("peer to peer comms (to an IP) not allowed") comm.Deny("peer to peer comms (to an IP) not allowed")
return return
@ -323,21 +267,21 @@ func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) {
} }
// check network scope // check network scope
switch comm.Domain { switch comm.Scope {
case network.IncomingHost: case network.IncomingHost:
if !profileSet.CheckFlag(profile.Localhost) { if p.BlockScopeLocal() {
log.Infof("firewall: denying communication %s, serving localhost not allowed", comm) log.Infof("firewall: denying communication %s, serving localhost not allowed", comm)
comm.Block("serving localhost not allowed") comm.Block("serving localhost not allowed")
return return
} }
case network.IncomingLAN: case network.IncomingLAN:
if !profileSet.CheckFlag(profile.LAN) { if p.BlockScopeLAN() {
log.Infof("firewall: denying communication %s, serving LAN not allowed", comm) log.Infof("firewall: denying communication %s, serving LAN not allowed", comm)
comm.Deny("serving LAN not allowed") comm.Deny("serving LAN not allowed")
return return
} }
case network.IncomingInternet: case network.IncomingInternet:
if !profileSet.CheckFlag(profile.Internet) { if p.BlockScopeInternet() {
log.Infof("firewall: denying communication %s, serving Internet not allowed", comm) log.Infof("firewall: denying communication %s, serving Internet not allowed", comm)
comm.Deny("serving Internet not allowed") comm.Deny("serving Internet not allowed")
return return
@ -347,19 +291,19 @@ func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) {
comm.Drop("invalid IP address") comm.Drop("invalid IP address")
return return
case network.PeerHost: case network.PeerHost:
if !profileSet.CheckFlag(profile.Localhost) { if p.BlockScopeLocal() {
log.Infof("firewall: denying communication %s, accessing localhost not allowed", comm) log.Infof("firewall: denying communication %s, accessing localhost not allowed", comm)
comm.Block("accessing localhost not allowed") comm.Block("accessing localhost not allowed")
return return
} }
case network.PeerLAN: case network.PeerLAN:
if !profileSet.CheckFlag(profile.LAN) { if p.BlockScopeLAN() {
log.Infof("firewall: denying communication %s, accessing the LAN not allowed", comm) log.Infof("firewall: denying communication %s, accessing the LAN not allowed", comm)
comm.Deny("accessing the LAN not allowed") comm.Deny("accessing the LAN not allowed")
return return
} }
case network.PeerInternet: case network.PeerInternet:
if !profileSet.CheckFlag(profile.Internet) { if p.BlockScopeInternet() {
log.Infof("firewall: denying communication %s, accessing the Internet not allowed", comm) log.Infof("firewall: denying communication %s, accessing the Internet not allowed", comm)
comm.Deny("accessing the Internet not allowed") comm.Deny("accessing the Internet not allowed")
return return
@ -384,7 +328,7 @@ func DecideOnLink(comm *network.Communication, link *network.Link, pkt packet.Pa
return return
} }
// check if communicating with self // check if process is communicating with itself
if comm.Process().Pid >= 0 && pkt.Info().Src.Equal(pkt.Info().Dst) { if comm.Process().Pid >= 0 && pkt.Info().Src.Equal(pkt.Info().Dst) {
// get PID // get PID
otherPid, _, err := process.GetPidByEndpoints( otherPid, _, err := process.GetPidByEndpoints(
@ -424,86 +368,80 @@ func DecideOnLink(comm *network.Communication, link *network.Link, pkt packet.Pa
return return
} }
// check if there is a profile // get profile
profileSet := comm.Process().ProfileSet() p := comm.Process().Profile()
if profileSet == nil {
log.Infof("firewall: no Profile Set, denying %s", link)
link.Deny("no Profile Set")
return
}
profileSet.Update(status.ActiveSecurityLevel())
// get domain
var fqdn string
if strings.HasSuffix(comm.Domain, ".") {
fqdn = comm.Domain
}
// remoteIP
var remoteIP net.IP
if comm.Direction {
remoteIP = pkt.Info().Src
} else {
remoteIP = pkt.Info().Dst
}
// protocol and destination port
protocol := uint8(pkt.Info().Protocol)
dstPort := pkt.Info().DstPort
// check endpoints list // check endpoints list
result, reason := profileSet.CheckEndpointIP(fqdn, remoteIP, protocol, dstPort, comm.Direction) var result endpoints.EPResult
var reason string
// FIXME: link.Entity.Lock()
if comm.Direction {
result, reason = p.MatchServiceEndpoint(link.Entity)
} else {
result, reason = p.MatchEndpoint(link.Entity)
}
// FIXME: link.Entity.Unlock()
switch result { switch result {
case profile.Denied: case endpoints.Denied:
log.Infof("firewall: denying link %s, endpoint is blacklisted: %s", link, reason) log.Infof("firewall: denying link %s, endpoint is blacklisted: %s", link, reason)
link.Deny(fmt.Sprintf("endpoint is blacklisted: %s", reason)) link.Deny(fmt.Sprintf("endpoint is blacklisted: %s", reason))
return return
case profile.Permitted: case endpoints.Permitted:
log.Infof("firewall: permitting link %s, endpoint is whitelisted: %s", link, reason) log.Infof("firewall: permitting link %s, endpoint is whitelisted: %s", link, reason)
link.Accept(fmt.Sprintf("endpoint is whitelisted: %s", reason)) link.Accept(fmt.Sprintf("endpoint is whitelisted: %s", reason))
return return
} }
// continueing with result == NoMatch
// TODO: Stamp integration // implicit default=block for incoming
if comm.Direction {
switch profileSet.GetProfileMode() { log.Infof("firewall: denying link %s: endpoint is not whitelisted (incoming is always default=block)", link)
case profile.Whitelist: link.Deny("endpoint is not whitelisted (incoming is always default=block)")
log.Infof("firewall: denying link %s: endpoint is not whitelisted", link)
link.Deny("endpoint is not whitelisted")
return
case profile.Blacklist:
log.Infof("firewall: permitting link %s: endpoint is not blacklisted", link)
link.Accept("endpoint is not blacklisted")
return return
} }
// ProfileMode == Prompt // check default action
if p.DefaultAction() == profile.DefaultActionPermit {
log.Infof("firewall: permitting link %s: endpoint is not blacklisted (default=permit)", link)
link.Accept("endpoint is not blacklisted (default=permit)")
return
}
// check relation // check relation
if fqdn != "" && profileSet.CheckFlag(profile.Related) { if !p.DisableAutoPermit() {
if checkRelation(comm, fqdn) { if checkRelation(comm) {
return return
} }
} }
// prompt // prompt
prompt(comm, link, pkt, fqdn) if p.DefaultAction() == profile.DefaultActionAsk {
} prompt(comm, link, pkt)
func checkRelation(comm *network.Communication, fqdn string) (related bool) {
profileSet := comm.Process().ProfileSet()
if profileSet == nil {
return return
} }
// TODO: add #AI // DefaultAction == DefaultActionBlock
log.Infof("firewall: denying link %s: endpoint is not whitelisted (default=block)", link)
link.Deny("endpoint is not whitelisted (default=block)")
return
}
pathElements := strings.Split(comm.Process().Path, "/") // FIXME: path separator // checkRelation tries to find a relation between a process and a communication. This is for better out of the box experience and is _not_ meant to thwart intentional malware.
func checkRelation(comm *network.Communication) (related bool) {
if comm.Entity.Domain != "" {
return false
}
// don't check for unknown processes
if comm.Process().Pid < 0 {
return false
}
pathElements := strings.Split(comm.Process().Path, string(filepath.Separator))
// only look at the last two path segments // only look at the last two path segments
if len(pathElements) > 2 { if len(pathElements) > 2 {
pathElements = pathElements[len(pathElements)-2:] pathElements = pathElements[len(pathElements)-2:]
} }
domainElements := strings.Split(fqdn, ".") domainElements := strings.Split(comm.Entity.Domain, ".")
var domainElement string var domainElement string
var processElement string var processElement string
@ -517,11 +455,6 @@ matchLoop:
break matchLoop break matchLoop
} }
} }
if levenshtein.Match(domainElement, profileSet.UserProfile().Name, nil) > 0.5 {
related = true
processElement = profileSet.UserProfile().Name
break matchLoop
}
if levenshtein.Match(domainElement, comm.Process().Name, nil) > 0.5 { if levenshtein.Match(domainElement, comm.Process().Name, nil) > 0.5 {
related = true related = true
processElement = comm.Process().Name processElement = comm.Process().Name

View file

@ -71,9 +71,13 @@ func GetPermittedPort() uint16 {
func portsInUseCleaner() { func portsInUseCleaner() {
for { for {
time.Sleep(cleanerTickDuration) select {
case <-module.Stopping():
return
case <-time.After(cleanerTickDuration):
cleanPortsInUse() cleanPortsInUse()
} }
}
} }
func cleanPortsInUse() { func cleanPortsInUse() {

View file

@ -1,15 +1,15 @@
package firewall package firewall
import ( import (
"context"
"fmt" "fmt"
"time" "time"
"github.com/safing/portmaster/profile/endpoints"
"github.com/safing/portbase/log" "github.com/safing/portbase/log"
"github.com/safing/portbase/notifications" "github.com/safing/portbase/notifications"
"github.com/safing/portmaster/network" "github.com/safing/portmaster/network"
"github.com/safing/portmaster/network/packet" "github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/profile"
) )
const ( const (
@ -30,14 +30,14 @@ var (
) )
//nolint:gocognit // FIXME //nolint:gocognit // FIXME
func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet, fqdn string) { func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet) {
nTTL := time.Duration(promptTimeout()) * time.Second nTTL := time.Duration(promptTimeout()) * 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 comm.Direction, fqdn == "": // connection to/from IP case comm.Direction, comm.Entity.Domain == "": // connection to/from IP
if pkt == nil { if pkt == nil {
log.Error("firewall: could not prompt for incoming/direct connection: missing pkt") log.Error("firewall: could not prompt for incoming/direct connection: missing pkt")
if link != nil { if link != nil {
@ -47,9 +47,9 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
} }
return return
} }
nID = fmt.Sprintf("firewall-prompt-%d-%s-%s", comm.Process().Pid, comm.Domain, pkt.Info().RemoteIP()) nID = fmt.Sprintf("firewall-prompt-%d-%s-%s", comm.Process().Pid, comm.Scope, pkt.Info().RemoteIP())
default: // connection to domain default: // connection to domain
nID = fmt.Sprintf("firewall-prompt-%d-%s", comm.Process().Pid, comm.Domain) nID = fmt.Sprintf("firewall-prompt-%d-%s", comm.Process().Pid, comm.Scope)
} }
n := notifications.Get(nID) n := notifications.Get(nID)
saveResponse := true saveResponse := true
@ -70,7 +70,7 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
// add message and actions // add message and actions
switch { switch {
case comm.Direction: // incoming case comm.Direction: // incoming
n.Message = fmt.Sprintf("Application %s wants to accept connections from %s (on %d/%d)", comm.Process(), pkt.Info().RemoteIP(), pkt.Info().Protocol, pkt.Info().LocalPort()) n.Message = fmt.Sprintf("Application %s wants to accept connections from %s (%d/%d)", comm.Process(), link.Entity.IP.String(), link.Entity.Protocol, link.Entity.Port)
n.AvailableActions = []*notifications.Action{ n.AvailableActions = []*notifications.Action{
{ {
ID: permitServingIP, ID: permitServingIP,
@ -81,8 +81,8 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
Text: "Deny", Text: "Deny",
}, },
} }
case fqdn == "": // direct connection case comm.Entity.Domain == "": // direct connection
n.Message = fmt.Sprintf("Application %s wants to connect to %s (on %d/%d)", comm.Process(), pkt.Info().RemoteIP(), pkt.Info().Protocol, pkt.Info().RemotePort()) n.Message = fmt.Sprintf("Application %s wants to connect to %s (%d/%d)", comm.Process(), link.Entity.IP.String(), link.Entity.Protocol, link.Entity.Port)
n.AvailableActions = []*notifications.Action{ n.AvailableActions = []*notifications.Action{
{ {
ID: permitIP, ID: permitIP,
@ -94,10 +94,10 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
}, },
} }
default: // connection to domain default: // connection to domain
if pkt != nil { if link != nil {
n.Message = fmt.Sprintf("Application %s wants to connect to %s (%s %d/%d)", comm.Process(), comm.Domain, pkt.Info().RemoteIP(), pkt.Info().Protocol, pkt.Info().RemotePort()) n.Message = fmt.Sprintf("Application %s wants to connect to %s (%s %d/%d)", comm.Process(), comm.Entity.Domain, link.Entity.IP.String(), link.Entity.Protocol, link.Entity.Port)
} else { } else {
n.Message = fmt.Sprintf("Application %s wants to connect to %s", comm.Process(), comm.Domain) n.Message = fmt.Sprintf("Application %s wants to connect to %s", comm.Process(), comm.Entity.Domain)
} }
n.AvailableActions = []*notifications.Action{ n.AvailableActions = []*notifications.Action{
{ {
@ -141,62 +141,57 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
return return
} }
new := &profile.EndpointPermission{ // get profile
Type: profile.EptDomain, p := comm.Process().Profile()
Value: comm.Domain,
Permit: false,
Created: time.Now().Unix(),
}
// permission type var ep endpoints.Endpoint
switch promptResponse { switch promptResponse {
case permitDomainAll, denyDomainAll: case permitDomainAll:
new.Value = "." + new.Value ep = &endpoints.EndpointDomain{
case permitIP, permitServingIP, denyIP, denyServingIP: EndpointBase: endpoints.EndpointBase{Permitted: true},
if pkt == nil { Domain: "." + comm.Entity.Domain,
log.Warningf("firewall: received invalid prompt response: %s for %s", promptResponse, comm.Domain)
return
} }
if pkt.Info().Version == packet.IPv4 { case permitDomainDistinct:
new.Type = profile.EptIPv4 ep = &endpoints.EndpointDomain{
} else { EndpointBase: endpoints.EndpointBase{Permitted: true},
new.Type = profile.EptIPv6 Domain: comm.Entity.Domain,
} }
new.Value = pkt.Info().RemoteIP().String() case denyDomainAll:
ep = &endpoints.EndpointDomain{
EndpointBase: endpoints.EndpointBase{Permitted: false},
Domain: "." + comm.Entity.Domain,
}
case denyDomainDistinct:
ep = &endpoints.EndpointDomain{
EndpointBase: endpoints.EndpointBase{Permitted: false},
Domain: comm.Entity.Domain,
}
case permitIP, permitServingIP:
ep = &endpoints.EndpointIP{
EndpointBase: endpoints.EndpointBase{Permitted: true},
IP: comm.Entity.IP,
}
case denyIP, denyServingIP:
ep = &endpoints.EndpointIP{
EndpointBase: endpoints.EndpointBase{Permitted: false},
IP: comm.Entity.IP,
}
default:
log.Warningf("filter: unknown prompt response: %s", promptResponse)
} }
// permission verdict
switch promptResponse {
case permitDomainAll, permitDomainDistinct, permitIP, permitServingIP:
new.Permit = false
}
// get user profile
profileSet := comm.Process().ProfileSet()
profileSet.Lock()
defer profileSet.Unlock()
userProfile := profileSet.UserProfile()
userProfile.Lock()
defer userProfile.Unlock()
// add to correct list
switch promptResponse { switch promptResponse {
case permitServingIP, denyServingIP: case permitServingIP, denyServingIP:
userProfile.ServiceEndpoints = append(userProfile.ServiceEndpoints, new) p.AddServiceEndpoint(ep.String())
default: default:
userProfile.Endpoints = append(userProfile.Endpoints, new) p.AddEndpoint(ep.String())
} }
// save!
module.StartMicroTask(&mtSaveProfile, func(ctx context.Context) error {
return userProfile.Save("")
})
case <-n.Expired(): case <-n.Expired():
if link != nil { if link != nil {
link.Accept("no response to prompt") link.Deny("no response to prompt")
} else { } else {
comm.Accept("no response to prompt") comm.Deny("no response to prompt")
} }
} }
} }

View file

@ -40,9 +40,9 @@ func cleanLinks() (activeComms map[string]struct{}) {
for key, link := range links { for key, link := range links {
// delete dead links // delete dead links
link.Lock() link.lock.Lock()
deleteThis := link.Ended > 0 && link.Ended < deleteOlderThan deleteThis := link.Ended > 0 && link.Ended < deleteOlderThan
link.Unlock() link.lock.Unlock()
if deleteThis { if deleteThis {
log.Tracef("network.clean: deleted %s (ended at %d)", link.DatabaseKey(), link.Ended) log.Tracef("network.clean: deleted %s (ended at %d)", link.DatabaseKey(), link.Ended)
go link.Delete() go link.Delete()
@ -51,9 +51,9 @@ func cleanLinks() (activeComms map[string]struct{}) {
// not yet deleted, so its still a valid link regarding link count // not yet deleted, so its still a valid link regarding link count
comm := link.Communication() comm := link.Communication()
comm.Lock() comm.lock.Lock()
markActive(activeComms, comm.DatabaseKey()) markActive(activeComms, comm.DatabaseKey())
comm.Unlock() comm.lock.Unlock()
// check if link is dead // check if link is dead
found = false found = false
@ -66,9 +66,9 @@ func cleanLinks() (activeComms map[string]struct{}) {
if !found { if !found {
// mark end time // mark end time
link.Lock() link.lock.Lock()
link.Ended = now link.Ended = now
link.Unlock() link.lock.Unlock()
log.Tracef("network.clean: marked %s as ended", link.DatabaseKey()) log.Tracef("network.clean: marked %s as ended", link.DatabaseKey())
// save // save
linkToSave := link linkToSave := link
@ -95,9 +95,9 @@ func cleanComms(activeLinks map[string]struct{}) (activeComms map[string]struct{
_, hasLinks := activeLinks[comm.DatabaseKey()] _, hasLinks := activeLinks[comm.DatabaseKey()]
// comm created // comm created
comm.Lock() comm.lock.Lock()
created := comm.Meta().Created created := comm.Meta().Created
comm.Unlock() comm.lock.Unlock()
if !hasLinks && created < threshold { if !hasLinks && created < threshold {
log.Tracef("network.clean: deleted %s", comm.DatabaseKey()) log.Tracef("network.clean: deleted %s", comm.DatabaseKey())

View file

@ -8,48 +8,63 @@ import (
"sync" "sync"
"time" "time"
"github.com/safing/portmaster/resolver"
"github.com/safing/portbase/database/record" "github.com/safing/portbase/database/record"
"github.com/safing/portbase/log" "github.com/safing/portbase/log"
"github.com/safing/portmaster/intel" "github.com/safing/portmaster/intel"
"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/portmaster/process" "github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile"
) )
// Communication describes a logical connection between a process and a domain. // Communication describes a logical connection between a process and a domain.
//nolint:maligned // TODO: fix alignment //nolint:maligned // TODO: fix alignment
type Communication struct { type Communication struct {
record.Base record.Base
sync.Mutex lock sync.Mutex
Domain string Scope string
Entity *intel.Entity
Direction bool Direction bool
Intel *intel.Intel
process *process.Process
Verdict Verdict Verdict Verdict
Reason string Reason string
ReasonID string // format source[:id[:id]]
Inspect bool Inspect bool
process *process.Process
profileRevisionCounter uint64
FirstLinkEstablished int64 FirstLinkEstablished int64
LastLinkEstablished int64 LastLinkEstablished int64
profileUpdateVersion uint32
saveWhenFinished bool saveWhenFinished bool
} }
// Lock locks the communication and the communication's Entity.
func (comm *Communication) Lock() {
comm.lock.Lock()
comm.Entity.Lock()
}
// Lock unlocks the communication and the communication's Entity.
func (comm *Communication) Unlock() {
comm.Entity.Unlock()
comm.lock.Unlock()
}
// Process returns the process that owns the connection. // Process returns the process that owns the connection.
func (comm *Communication) Process() *process.Process { func (comm *Communication) Process() *process.Process {
comm.Lock() comm.lock.Lock()
defer comm.Unlock() defer comm.lock.Unlock()
return comm.process return comm.process
} }
// ResetVerdict resets the verdict to VerdictUndecided. // ResetVerdict resets the verdict to VerdictUndecided.
func (comm *Communication) ResetVerdict() { func (comm *Communication) ResetVerdict() {
comm.Lock() comm.lock.Lock()
defer comm.Unlock() defer comm.lock.Unlock()
comm.Verdict = VerdictUndecided comm.Verdict = VerdictUndecided
comm.Reason = "" comm.Reason = ""
@ -58,8 +73,8 @@ func (comm *Communication) ResetVerdict() {
// GetVerdict returns the current verdict. // GetVerdict returns the current verdict.
func (comm *Communication) GetVerdict() Verdict { func (comm *Communication) GetVerdict() Verdict {
comm.Lock() comm.lock.Lock()
defer comm.Unlock() defer comm.lock.Unlock()
return comm.Verdict return comm.Verdict
} }
@ -93,8 +108,8 @@ func (comm *Communication) Drop(reason string) {
// UpdateVerdict sets a new verdict for this link, making sure it does not interfere with previous verdicts. // UpdateVerdict sets a new verdict for this link, making sure it does not interfere with previous verdicts.
func (comm *Communication) UpdateVerdict(newVerdict Verdict) { func (comm *Communication) UpdateVerdict(newVerdict Verdict) {
comm.Lock() comm.lock.Lock()
defer comm.Unlock() defer comm.lock.Unlock()
if newVerdict > comm.Verdict { if newVerdict > comm.Verdict {
comm.Verdict = newVerdict comm.Verdict = newVerdict
@ -108,8 +123,8 @@ func (comm *Communication) SetReason(reason string) {
return return
} }
comm.Lock() comm.lock.Lock()
defer comm.Unlock() defer comm.lock.Unlock()
comm.Reason = reason comm.Reason = reason
comm.saveWhenFinished = true comm.saveWhenFinished = true
} }
@ -120,8 +135,8 @@ func (comm *Communication) AddReason(reason string) {
return return
} }
comm.Lock() comm.lock.Lock()
defer comm.Unlock() defer comm.lock.Unlock()
if comm.Reason != "" { if comm.Reason != "" {
comm.Reason += " | " comm.Reason += " | "
@ -129,21 +144,18 @@ func (comm *Communication) AddReason(reason string) {
comm.Reason += reason comm.Reason += reason
} }
// NeedsReevaluation returns whether the decision on this communication should be re-evaluated. // UpdateAndCheck updates profiles and checks whether a reevaluation is needed.
func (comm *Communication) NeedsReevaluation() bool { func (comm *Communication) UpdateAndCheck() (needsReevaluation bool) {
comm.Lock() revCnt := comm.Process().Profile().Update()
defer comm.Unlock()
oldVersion := comm.profileUpdateVersion comm.lock.Lock()
comm.profileUpdateVersion = profile.GetUpdateVersion() defer comm.lock.Unlock()
if comm.profileRevisionCounter != revCnt {
comm.profileRevisionCounter = revCnt
needsReevaluation = true
}
if oldVersion == 0 { return
return false
}
if oldVersion != comm.profileUpdateVersion {
return true
}
return false
} }
// GetCommunicationByFirstPacket returns the matching communication from the internal storage. // GetCommunicationByFirstPacket returns the matching communication from the internal storage.
@ -153,25 +165,26 @@ func GetCommunicationByFirstPacket(pkt packet.Packet) (*Communication, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var domain string var scope string
// Incoming // Incoming
if direction { if direction {
switch netutils.ClassifyIP(pkt.Info().Src) { switch netutils.ClassifyIP(pkt.Info().Src) {
case netutils.HostLocal: case netutils.HostLocal:
domain = IncomingHost scope = IncomingHost
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast: case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
domain = IncomingLAN scope = IncomingLAN
case netutils.Global, netutils.GlobalMulticast: case netutils.Global, netutils.GlobalMulticast:
domain = IncomingInternet scope = IncomingInternet
case netutils.Invalid: case netutils.Invalid:
domain = IncomingInvalid scope = IncomingInvalid
} }
communication, ok := GetCommunication(proc.Pid, domain) communication, ok := GetCommunication(proc.Pid, scope)
if !ok { if !ok {
communication = &Communication{ communication = &Communication{
Domain: domain, Scope: scope,
Entity: (&intel.Entity{}).Init(),
Direction: Inbound, Direction: Inbound,
process: proc, process: proc,
Inspect: true, Inspect: true,
@ -184,7 +197,7 @@ func GetCommunicationByFirstPacket(pkt packet.Packet) (*Communication, error) {
} }
// get domain // get domain
ipinfo, err := intel.GetIPInfo(pkt.FmtRemoteIP()) ipinfo, err := resolver.GetIPInfo(pkt.FmtRemoteIP())
// PeerToPeer // PeerToPeer
if err != nil { if err != nil {
@ -192,19 +205,20 @@ func GetCommunicationByFirstPacket(pkt packet.Packet) (*Communication, error) {
switch netutils.ClassifyIP(pkt.Info().Dst) { switch netutils.ClassifyIP(pkt.Info().Dst) {
case netutils.HostLocal: case netutils.HostLocal:
domain = PeerHost scope = PeerHost
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast: case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
domain = PeerLAN scope = PeerLAN
case netutils.Global, netutils.GlobalMulticast: case netutils.Global, netutils.GlobalMulticast:
domain = PeerInternet scope = PeerInternet
case netutils.Invalid: case netutils.Invalid:
domain = PeerInvalid scope = PeerInvalid
} }
communication, ok := GetCommunication(proc.Pid, domain) communication, ok := GetCommunication(proc.Pid, scope)
if !ok { if !ok {
communication = &Communication{ communication = &Communication{
Domain: domain, Scope: scope,
Entity: (&intel.Entity{}).Init(),
Direction: Outbound, Direction: Outbound,
process: proc, process: proc,
Inspect: true, Inspect: true,
@ -221,7 +235,10 @@ func GetCommunicationByFirstPacket(pkt packet.Packet) (*Communication, error) {
communication, ok := GetCommunication(proc.Pid, ipinfo.Domains[0]) communication, ok := GetCommunication(proc.Pid, ipinfo.Domains[0])
if !ok { if !ok {
communication = &Communication{ communication = &Communication{
Scope: ipinfo.Domains[0],
Entity: (&intel.Entity{
Domain: ipinfo.Domains[0], Domain: ipinfo.Domains[0],
}).Init(),
Direction: Outbound, Direction: Outbound,
process: proc, process: proc,
Inspect: true, Inspect: true,
@ -251,7 +268,10 @@ func GetCommunicationByDNSRequest(ctx context.Context, ip net.IP, port uint16, f
communication, ok := GetCommunication(proc.Pid, fqdn) communication, ok := GetCommunication(proc.Pid, fqdn)
if !ok { if !ok {
communication = &Communication{ communication = &Communication{
Scope: fqdn,
Entity: (&intel.Entity{
Domain: fqdn, Domain: fqdn,
}).Init(),
process: proc, process: proc,
Inspect: true, Inspect: true,
saveWhenFinished: true, saveWhenFinished: true,
@ -271,7 +291,7 @@ func GetCommunication(pid int, domain string) (comm *Communication, ok bool) {
} }
func (comm *Communication) makeKey() string { func (comm *Communication) makeKey() string {
return fmt.Sprintf("%d/%s", comm.process.Pid, comm.Domain) return fmt.Sprintf("%d/%s", comm.process.Pid, comm.Scope)
} }
// SaveWhenFinished marks the Connection for saving after all current actions are finished. // SaveWhenFinished marks the Connection for saving after all current actions are finished.
@ -281,12 +301,12 @@ func (comm *Communication) SaveWhenFinished() {
// SaveIfNeeded saves the Connection if it is marked for saving when finished. // SaveIfNeeded saves the Connection if it is marked for saving when finished.
func (comm *Communication) SaveIfNeeded() { func (comm *Communication) SaveIfNeeded() {
comm.Lock() comm.lock.Lock()
save := comm.saveWhenFinished save := comm.saveWhenFinished
if save { if save {
comm.saveWhenFinished = false comm.saveWhenFinished = false
} }
comm.Unlock() comm.lock.Unlock()
if save { if save {
err := comm.save() err := comm.save()
@ -299,14 +319,14 @@ func (comm *Communication) SaveIfNeeded() {
// Save saves the Connection object in the storage and propagates the change. // Save saves the Connection object in the storage and propagates the change.
func (comm *Communication) save() error { func (comm *Communication) save() error {
// update comm // update comm
comm.Lock() comm.lock.Lock()
if comm.process == nil { if comm.process == nil {
comm.Unlock() comm.lock.Unlock()
return errors.New("cannot save connection without process") return errors.New("cannot save connection without process")
} }
if !comm.KeyIsSet() { if !comm.KeyIsSet() {
comm.SetKey(fmt.Sprintf("network:tree/%d/%s", comm.process.Pid, comm.Domain)) comm.SetKey(fmt.Sprintf("network:tree/%d/%s", comm.process.Pid, comm.Scope))
comm.UpdateMeta() comm.UpdateMeta()
} }
if comm.Meta().Deleted > 0 { if comm.Meta().Deleted > 0 {
@ -315,7 +335,7 @@ func (comm *Communication) save() error {
} }
key := comm.makeKey() key := comm.makeKey()
comm.saveWhenFinished = false comm.saveWhenFinished = false
comm.Unlock() comm.lock.Unlock()
// save comm // save comm
commsLock.RLock() commsLock.RLock()
@ -336,8 +356,8 @@ func (comm *Communication) save() error {
func (comm *Communication) Delete() { func (comm *Communication) Delete() {
commsLock.Lock() commsLock.Lock()
defer commsLock.Unlock() defer commsLock.Unlock()
comm.Lock() comm.lock.Lock()
defer comm.Unlock() defer comm.lock.Unlock()
delete(comms, comm.makeKey()) delete(comms, comm.makeKey())
@ -347,16 +367,18 @@ func (comm *Communication) Delete() {
// AddLink applies the Communication to the Link and sets timestamps. // AddLink applies the Communication to the Link and sets timestamps.
func (comm *Communication) AddLink(link *Link) { func (comm *Communication) AddLink(link *Link) {
comm.lock.Lock()
defer comm.lock.Unlock()
// apply comm to link // apply comm to link
link.Lock() link.lock.Lock()
link.comm = comm link.comm = comm
link.Verdict = comm.Verdict link.Verdict = comm.Verdict
link.Inspect = comm.Inspect link.Inspect = comm.Inspect
// FIXME: use new copy methods
link.Entity.Domain = comm.Entity.Domain
link.saveWhenFinished = true link.saveWhenFinished = true
link.Unlock() link.lock.Unlock()
// update comm LastLinkEstablished
comm.Lock()
// check if we should save // check if we should save
if comm.LastLinkEstablished < time.Now().Add(-3*time.Second).Unix() { if comm.LastLinkEstablished < time.Now().Add(-3*time.Second).Unix() {
@ -368,8 +390,6 @@ func (comm *Communication) AddLink(link *Link) {
if comm.FirstLinkEstablished == 0 { if comm.FirstLinkEstablished == 0 {
comm.FirstLinkEstablished = comm.LastLinkEstablished comm.FirstLinkEstablished = comm.LastLinkEstablished
} }
comm.Unlock()
} }
// String returns a string representation of Communication. // String returns a string representation of Communication.
@ -377,7 +397,7 @@ func (comm *Communication) String() string {
comm.Lock() comm.Lock()
defer comm.Unlock() defer comm.Unlock()
switch comm.Domain { switch comm.Scope {
case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid: case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid:
if comm.process == nil { if comm.process == nil {
return "? <- *" return "? <- *"
@ -390,8 +410,8 @@ func (comm *Communication) String() string {
return fmt.Sprintf("%s -> *", comm.process.String()) return fmt.Sprintf("%s -> *", comm.process.String())
default: default:
if comm.process == nil { if comm.process == nil {
return fmt.Sprintf("? -> %s", comm.Domain) return fmt.Sprintf("? -> %s", comm.Scope)
} }
return fmt.Sprintf("%s -> %s", comm.process.String(), comm.Domain) return fmt.Sprintf("%s -> %s", comm.process.String(), comm.Scope)
} }
} }

View file

@ -7,6 +7,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/safing/portmaster/intel"
"github.com/safing/portbase/database/record" "github.com/safing/portbase/database/record"
"github.com/safing/portbase/log" "github.com/safing/portbase/log"
"github.com/safing/portmaster/network/packet" "github.com/safing/portmaster/network/packet"
@ -16,21 +18,22 @@ import (
type FirewallHandler func(pkt packet.Packet, link *Link) type FirewallHandler func(pkt packet.Packet, link *Link)
// Link describes a distinct physical connection (e.g. TCP connection) - like an instance - of a Connection. // Link describes a distinct physical connection (e.g. TCP connection) - like an instance - of a Connection.
//nolint:maligned // TODO: fix alignment type Link struct { //nolint:maligned // TODO: fix alignment
type Link struct {
record.Base record.Base
sync.Mutex lock sync.Mutex
ID string ID string
Entity *intel.Entity
Direction bool
Verdict Verdict Verdict Verdict
Reason string Reason string
ReasonID string // format source[:id[:id]]
Tunneled bool Tunneled bool
VerdictPermanent bool VerdictPermanent bool
Inspect bool Inspect bool
Started int64 Started int64
Ended int64 Ended int64
RemoteAddress string
pktQueue chan packet.Packet pktQueue chan packet.Packet
firewallHandler FirewallHandler firewallHandler FirewallHandler
@ -41,70 +44,82 @@ type Link struct {
saveWhenFinished bool saveWhenFinished bool
} }
// Lock locks the link and the link's Entity.
func (link *Link) Lock() {
link.lock.Lock()
link.Entity.Lock()
}
// Lock unlocks the link and the link's Entity.
func (link *Link) Unlock() {
link.Entity.Unlock()
link.lock.Unlock()
}
// Communication returns the Communication the Link is part of // Communication returns the Communication the Link is part of
func (link *Link) Communication() *Communication { func (link *Link) Communication() *Communication {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
return link.comm return link.comm
} }
// GetVerdict returns the current verdict. // GetVerdict returns the current verdict.
func (link *Link) GetVerdict() Verdict { func (link *Link) GetVerdict() Verdict {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
return link.Verdict return link.Verdict
} }
// FirewallHandlerIsSet returns whether a firewall handler is set or not // FirewallHandlerIsSet returns whether a firewall handler is set or not
func (link *Link) FirewallHandlerIsSet() bool { func (link *Link) FirewallHandlerIsSet() bool {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
return link.firewallHandler != nil return link.firewallHandler != nil
} }
// SetFirewallHandler sets the firewall handler for this link // SetFirewallHandler sets the firewall handler for this link
func (link *Link) SetFirewallHandler(handler FirewallHandler) { func (link *Link) SetFirewallHandler(handler FirewallHandler) {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
if link.firewallHandler == nil { if link.firewallHandler == nil {
link.firewallHandler = handler
link.pktQueue = make(chan packet.Packet, 1000) link.pktQueue = make(chan packet.Packet, 1000)
// start handling // start handling
module.StartWorker("", func(ctx context.Context) error { module.StartWorker("packet handler", func(ctx context.Context) error {
link.packetHandler() link.packetHandler()
return nil return nil
}) })
return
} }
link.firewallHandler = handler link.firewallHandler = handler
} }
// StopFirewallHandler unsets the firewall handler // StopFirewallHandler unsets the firewall handler
func (link *Link) StopFirewallHandler() { func (link *Link) StopFirewallHandler() {
link.Lock() link.lock.Lock()
link.firewallHandler = nil link.firewallHandler = nil
link.Unlock() link.lock.Unlock()
link.pktQueue <- nil link.pktQueue <- nil
} }
// HandlePacket queues packet of Link for handling // HandlePacket queues packet of Link for handling
func (link *Link) HandlePacket(pkt packet.Packet) { func (link *Link) HandlePacket(pkt packet.Packet) {
link.Lock() // get handler
defer link.Unlock() link.lock.Lock()
handler := link.firewallHandler
link.lock.Unlock()
if link.firewallHandler != nil { // send to queue
if handler != nil {
link.pktQueue <- pkt link.pktQueue <- pkt
return return
} }
// no handler!
log.Warningf("network: link %s does not have a firewallHandler, dropping packet", link) log.Warningf("network: link %s does not have a firewallHandler, dropping packet", link)
err := pkt.Drop() err := pkt.Drop()
if err != nil { if err != nil {
log.Warningf("network: failed to drop packet %s: %s", pkt, err) log.Warningf("network: failed to drop packet %s: %s", pkt, err)
@ -119,7 +134,7 @@ func (link *Link) Accept(reason string) {
// Deny blocks or drops the link depending on the connection direction and adds the given reason. // Deny blocks or drops the link depending on the connection direction and adds the given reason.
func (link *Link) Deny(reason string) { func (link *Link) Deny(reason string) {
if link.comm != nil && link.comm.Direction { if link.Direction {
link.Drop(reason) link.Drop(reason)
} else { } else {
link.Block(reason) link.Block(reason)
@ -151,8 +166,8 @@ func (link *Link) RerouteToTunnel(reason string) {
// UpdateVerdict sets a new verdict for this link, making sure it does not interfere with previous verdicts // UpdateVerdict sets a new verdict for this link, making sure it does not interfere with previous verdicts
func (link *Link) UpdateVerdict(newVerdict Verdict) { func (link *Link) UpdateVerdict(newVerdict Verdict) {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
if newVerdict > link.Verdict { if newVerdict > link.Verdict {
link.Verdict = newVerdict link.Verdict = newVerdict
@ -166,8 +181,8 @@ func (link *Link) AddReason(reason string) {
return return
} }
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
if link.Reason != "" { if link.Reason != "" {
link.Reason += " | " link.Reason += " | "
@ -185,9 +200,9 @@ func (link *Link) packetHandler() {
return return
} }
// get handler // get handler
link.Lock() link.lock.Lock()
handler := link.firewallHandler handler := link.firewallHandler
link.Unlock() link.lock.Unlock()
// execute handler or verdict // execute handler or verdict
if handler != nil { if handler != nil {
handler(pkt, link) handler(pkt, link)
@ -201,8 +216,8 @@ func (link *Link) packetHandler() {
// ApplyVerdict appies the link verdict to a packet. // ApplyVerdict appies the link verdict to a packet.
func (link *Link) ApplyVerdict(pkt packet.Packet) { func (link *Link) ApplyVerdict(pkt packet.Packet) {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
var err error var err error
@ -251,12 +266,12 @@ func (link *Link) SaveWhenFinished() {
// SaveIfNeeded saves the Link if it is marked for saving when finished. // SaveIfNeeded saves the Link if it is marked for saving when finished.
func (link *Link) SaveIfNeeded() { func (link *Link) SaveIfNeeded() {
link.Lock() link.lock.Lock()
save := link.saveWhenFinished save := link.saveWhenFinished
if save { if save {
link.saveWhenFinished = false link.saveWhenFinished = false
} }
link.Unlock() link.lock.Unlock()
if save { if save {
link.saveAndLog() link.saveAndLog()
@ -274,18 +289,18 @@ func (link *Link) saveAndLog() {
// save saves the link object in the storage and propagates the change. // save saves the link object in the storage and propagates the change.
func (link *Link) save() error { func (link *Link) save() error {
// update link // update link
link.Lock() link.lock.Lock()
if link.comm == nil { if link.comm == nil {
link.Unlock() link.lock.Unlock()
return errors.New("cannot save link without comms") return errors.New("cannot save link without comms")
} }
if !link.KeyIsSet() { if !link.KeyIsSet() {
link.SetKey(fmt.Sprintf("network:tree/%d/%s/%s", link.comm.Process().Pid, link.comm.Domain, link.ID)) link.SetKey(fmt.Sprintf("network:tree/%d/%s/%s", link.comm.Process().Pid, link.comm.Scope, link.ID))
link.UpdateMeta() link.UpdateMeta()
} }
link.saveWhenFinished = false link.saveWhenFinished = false
link.Unlock() link.lock.Unlock()
// save link // save link
linksLock.RLock() linksLock.RLock()
@ -306,8 +321,8 @@ func (link *Link) save() error {
func (link *Link) Delete() { func (link *Link) Delete() {
linksLock.Lock() linksLock.Lock()
defer linksLock.Unlock() defer linksLock.Unlock()
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
delete(links, link.ID) delete(links, link.ID)
@ -340,9 +355,14 @@ func GetOrCreateLinkByPacket(pkt packet.Packet) (*Link, bool) {
func CreateLinkFromPacket(pkt packet.Packet) *Link { func CreateLinkFromPacket(pkt packet.Packet) *Link {
link := &Link{ link := &Link{
ID: pkt.GetLinkID(), ID: pkt.GetLinkID(),
Entity: (&intel.Entity{
IP: pkt.Info().RemoteIP(),
Protocol: uint8(pkt.Info().Protocol),
Port: pkt.Info().RemotePort(),
}).Init(),
Direction: pkt.IsInbound(),
Verdict: VerdictUndecided, Verdict: VerdictUndecided,
Started: time.Now().Unix(), Started: time.Now().Unix(),
RemoteAddress: pkt.FmtRemoteAddress(),
saveWhenFinished: true, saveWhenFinished: true,
} }
return link return link
@ -350,59 +370,59 @@ func CreateLinkFromPacket(pkt packet.Packet) *Link {
// GetActiveInspectors returns the list of active inspectors. // GetActiveInspectors returns the list of active inspectors.
func (link *Link) GetActiveInspectors() []bool { func (link *Link) GetActiveInspectors() []bool {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
return link.activeInspectors return link.activeInspectors
} }
// SetActiveInspectors sets the list of active inspectors. // SetActiveInspectors sets the list of active inspectors.
func (link *Link) SetActiveInspectors(new []bool) { func (link *Link) SetActiveInspectors(new []bool) {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
link.activeInspectors = new link.activeInspectors = new
} }
// GetInspectorData returns the list of inspector data. // GetInspectorData returns the list of inspector data.
func (link *Link) GetInspectorData() map[uint8]interface{} { func (link *Link) GetInspectorData() map[uint8]interface{} {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
return link.inspectorData return link.inspectorData
} }
// SetInspectorData set the list of inspector data. // SetInspectorData set the list of inspector data.
func (link *Link) SetInspectorData(new map[uint8]interface{}) { func (link *Link) SetInspectorData(new map[uint8]interface{}) {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
link.inspectorData = new link.inspectorData = new
} }
// String returns a string representation of Link. // String returns a string representation of Link.
func (link *Link) String() string { func (link *Link) String() string {
link.Lock() link.lock.Lock()
defer link.Unlock() defer link.lock.Unlock()
if link.comm == nil { if link.comm == nil {
return fmt.Sprintf("? <-> %s", link.RemoteAddress) return fmt.Sprintf("? <-> %s", link.Entity.IP.String())
} }
switch link.comm.Domain { switch link.comm.Scope {
case "I": case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid:
if link.comm.process == nil { if link.comm.process == nil {
return fmt.Sprintf("? <- %s", link.RemoteAddress) return fmt.Sprintf("? <- %s", link.Entity.IP.String())
} }
return fmt.Sprintf("%s <- %s", link.comm.process.String(), link.RemoteAddress) return fmt.Sprintf("%s <- %s", link.comm.process.String(), link.Entity.IP.String())
case "D": case PeerHost, PeerLAN, PeerInternet, PeerInvalid:
if link.comm.process == nil { if link.comm.process == nil {
return fmt.Sprintf("? -> %s", link.RemoteAddress) return fmt.Sprintf("? -> %s", link.Entity.IP.String())
} }
return fmt.Sprintf("%s -> %s", link.comm.process.String(), link.RemoteAddress) return fmt.Sprintf("%s -> %s", link.comm.process.String(), link.Entity.IP.String())
default: default:
if link.comm.process == nil { if link.comm.process == nil {
return fmt.Sprintf("? -> %s (%s)", link.comm.Domain, link.RemoteAddress) return fmt.Sprintf("? -> %s (%s)", link.comm.Scope, link.Entity.IP.String())
} }
return fmt.Sprintf("%s to %s (%s)", link.comm.process.String(), link.comm.Domain, link.RemoteAddress) return fmt.Sprintf("%s to %s (%s)", link.comm.process.String(), link.comm.Scope, link.Entity.IP.String())
} }
} }

View file

@ -5,36 +5,38 @@ import (
"os" "os"
"time" "time"
"github.com/safing/portmaster/intel"
"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/portmaster/process" "github.com/safing/portmaster/process"
) )
// GetOwnComm returns the communication for the given packet, that originates from // GetOwnComm returns the communication for the given packet, that originates from the Portmaster itself.
func GetOwnComm(pkt packet.Packet) (*Communication, error) { func GetOwnComm(pkt packet.Packet) (*Communication, error) {
var domain string var scope string
// Incoming // Incoming
if pkt.IsInbound() { if pkt.IsInbound() {
switch netutils.ClassifyIP(pkt.Info().RemoteIP()) { switch netutils.ClassifyIP(pkt.Info().RemoteIP()) {
case netutils.HostLocal: case netutils.HostLocal:
domain = IncomingHost scope = IncomingHost
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast: case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
domain = IncomingLAN scope = IncomingLAN
case netutils.Global, netutils.GlobalMulticast: case netutils.Global, netutils.GlobalMulticast:
domain = IncomingInternet scope = IncomingInternet
case netutils.Invalid: case netutils.Invalid:
domain = IncomingInvalid scope = IncomingInvalid
} }
communication, ok := GetCommunication(os.Getpid(), domain) communication, ok := GetCommunication(os.Getpid(), scope)
if !ok { if !ok {
proc, err := process.GetOrFindProcess(pkt.Ctx(), os.Getpid()) proc, err := process.GetOrFindProcess(pkt.Ctx(), os.Getpid())
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get own process") return nil, fmt.Errorf("could not get own process")
} }
communication = &Communication{ communication = &Communication{
Domain: domain, Scope: scope,
Entity: (&intel.Entity{}).Init(),
Direction: Inbound, Direction: Inbound,
process: proc, process: proc,
Inspect: true, Inspect: true,
@ -48,23 +50,24 @@ func GetOwnComm(pkt packet.Packet) (*Communication, error) {
// PeerToPeer // PeerToPeer
switch netutils.ClassifyIP(pkt.Info().RemoteIP()) { switch netutils.ClassifyIP(pkt.Info().RemoteIP()) {
case netutils.HostLocal: case netutils.HostLocal:
domain = PeerHost scope = PeerHost
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast: case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
domain = PeerLAN scope = PeerLAN
case netutils.Global, netutils.GlobalMulticast: case netutils.Global, netutils.GlobalMulticast:
domain = PeerInternet scope = PeerInternet
case netutils.Invalid: case netutils.Invalid:
domain = PeerInvalid scope = PeerInvalid
} }
communication, ok := GetCommunication(os.Getpid(), domain) communication, ok := GetCommunication(os.Getpid(), scope)
if !ok { if !ok {
proc, err := process.GetOrFindProcess(pkt.Ctx(), os.Getpid()) proc, err := process.GetOrFindProcess(pkt.Ctx(), os.Getpid())
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get own process") return nil, fmt.Errorf("could not get own process")
} }
communication = &Communication{ communication = &Communication{
Domain: domain, Scope: scope,
Entity: (&intel.Entity{}).Init(),
Direction: Outbound, Direction: Outbound,
process: proc, process: proc,
Inspect: true, Inspect: true,

View file

@ -42,7 +42,7 @@ const (
Outbound = false Outbound = false
) )
// Non-Domain Connections // Non-Domain Scopes
const ( const (
IncomingHost = "IH" IncomingHost = "IH"
IncomingLAN = "IL" IncomingLAN = "IL"

View file

@ -3,6 +3,7 @@ package network
import ( import (
"time" "time"
"github.com/safing/portmaster/intel"
"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/portmaster/process" "github.com/safing/portmaster/process"
@ -43,11 +44,12 @@ func GetUnknownCommunication(pkt packet.Packet) (*Communication, error) {
return getOrCreateUnknownCommunication(pkt, PeerInvalid) return getOrCreateUnknownCommunication(pkt, PeerInvalid)
} }
func getOrCreateUnknownCommunication(pkt packet.Packet, connClass string) (*Communication, error) { func getOrCreateUnknownCommunication(pkt packet.Packet, connScope string) (*Communication, error) {
connection, ok := GetCommunication(process.UnknownProcess.Pid, connClass) connection, ok := GetCommunication(process.UnknownProcess.Pid, connScope)
if !ok { if !ok {
connection = &Communication{ connection = &Communication{
Domain: connClass, Scope: connScope,
Entity: (&intel.Entity{}).Init(),
Direction: pkt.IsInbound(), Direction: pkt.IsInbound(),
Verdict: VerdictDrop, Verdict: VerdictDrop,
Reason: ReasonUnknownProcess, Reason: ReasonUnknownProcess,

29
process/config.go Normal file
View file

@ -0,0 +1,29 @@
package process
import (
"github.com/safing/portbase/config"
)
var (
CfgOptionEnableProcessDetectionKey = "core/enableProcessDetection"
enableProcessDetection config.BoolOption
)
func registerConfiguration() error {
// Enable Process Detection
// This should be always enabled. Provided as an option to disable in case there are severe problems on a system, or for debugging.
err := config.Register(&config.Option{
Name: "Enable Process Detection",
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.",
OptType: config.OptTypeBool,
ExpertiseLevel: config.ExpertiseLevelDeveloper,
DefaultValue: true,
})
if err != nil {
return err
}
enableProcessDetection = config.Concurrent.GetAsBool(CfgOptionEnableProcessDetectionKey, true)
return nil
}

View file

@ -9,7 +9,6 @@ import (
"github.com/safing/portbase/database" "github.com/safing/portbase/database"
"github.com/safing/portbase/log" "github.com/safing/portbase/log"
"github.com/safing/portmaster/profile"
"github.com/tevino/abool" "github.com/tevino/abool"
) )
@ -90,11 +89,7 @@ func (p *Process) Delete() {
go dbController.PushUpdate(p) go dbController.PushUpdate(p)
} }
// deactivate profile // TODO: maybe mark the assigned profiles as no longer needed?
// TODO: check if there is another process using the same profile set
if p.profileSet != nil {
profile.DeactivateProfileSet(p.profileSet)
}
} }
// CleanProcessStorage cleans the storage from old processes. // CleanProcessStorage cleans the storage from old processes.

View file

@ -56,6 +56,11 @@ func GetPidByPacket(pkt packet.Packet) (pid int, direction bool, err error) {
// GetProcessByPacket returns the process that owns the given packet. // GetProcessByPacket returns the process that owns the given packet.
func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, err error) { func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, err error) {
if !enableProcessDetection() {
log.Tracer(pkt.Ctx()).Tracef("process: process detection disabled")
return UnknownProcess, direction, nil
}
log.Tracer(pkt.Ctx()).Tracef("process: getting process and profile by packet") log.Tracer(pkt.Ctx()).Tracef("process: getting process and profile by packet")
var pid int var pid int
@ -75,10 +80,9 @@ func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, er
return nil, direction, err return nil, direction, err
} }
err = process.FindProfiles(pkt.Ctx()) err = process.GetProfile(pkt.Ctx())
if err != nil { if err != nil {
log.Tracer(pkt.Ctx()).Errorf("process: failed to find profiles for process %s: %s", process, err) log.Tracer(pkt.Ctx()).Errorf("process: failed to get profile for process %s: %s", process, err)
log.Errorf("failed to find profiles for process %s: %s", process, err)
} }
return process, direction, nil return process, direction, nil
@ -110,6 +114,11 @@ func GetPidByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remote
// GetProcessByEndpoints returns the process that owns the described link. // GetProcessByEndpoints returns the process that owns the described link.
func GetProcessByEndpoints(ctx context.Context, localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (process *Process, err error) { func GetProcessByEndpoints(ctx context.Context, localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (process *Process, err error) {
if !enableProcessDetection() {
log.Tracer(ctx).Tracef("process: process detection disabled")
return UnknownProcess, nil
}
log.Tracer(ctx).Tracef("process: getting process and profile by endpoints") log.Tracer(ctx).Tracef("process: getting process and profile by endpoints")
var pid int var pid int
@ -129,10 +138,9 @@ func GetProcessByEndpoints(ctx context.Context, localIP net.IP, localPort uint16
return nil, err return nil, err
} }
err = process.FindProfiles(ctx) err = process.GetProfile(ctx)
if err != nil { if err != nil {
log.Tracer(ctx).Errorf("process: failed to find profiles for process %s: %s", process, err) log.Tracer(ctx).Errorf("process: failed to get profile for process %s: %s", process, err)
log.Errorf("process: failed to find profiles for process %s: %s", process, err)
} }
return process, nil return process, nil

View file

@ -1,108 +0,0 @@
package process
import (
"context"
"fmt"
"github.com/safing/portbase/database"
"github.com/safing/portbase/database/query"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/profile"
)
var (
profileDB = database.NewInterface(nil)
)
// FindProfiles finds and assigns a profile set to the process.
func (p *Process) FindProfiles(ctx context.Context) error {
log.Tracer(ctx).Trace("process: loading profile set")
p.Lock()
defer p.Unlock()
// only find profiles if not already done.
if p.profileSet != nil {
return nil
}
// User Profile
it, err := profileDB.Query(query.New(profile.MakeProfileKey(profile.UserNamespace, "")).Where(query.Where("LinkedPath", query.SameAs, p.Path)))
if err != nil {
return err
}
var userProfile *profile.Profile
// get first result
r := <-it.Next
// cancel immediately
it.Cancel()
// ensure its a profile
userProfile, err = profile.EnsureProfile(r)
if err != nil {
return err
}
// create new profile if it does not exist.
if userProfile == nil {
// create new profile
userProfile = profile.New()
userProfile.Name = p.ExecName
userProfile.LinkedPath = p.Path
}
if userProfile.MarkUsed() {
_ = userProfile.Save(profile.UserNamespace)
}
// Stamp
// Find/Re-evaluate Stamp profile
// 1. check linked stamp profile
// 2. if last check is was more than a week ago, fetch from stamp:
// 3. send path identifier to stamp
// 4. evaluate all returned profiles
// 5. select best
// 6. link stamp profile to user profile
// FIXME: implement!
p.UserProfileKey = userProfile.Key()
p.profileSet = profile.NewSet(ctx, fmt.Sprintf("%d-%s", p.Pid, p.Path), userProfile, nil)
go p.Save()
return nil
}
//nolint:deadcode,unused // FIXME
func matchProfile(p *Process, prof *profile.Profile) (score int) {
for _, fp := range prof.Fingerprints {
score += matchFingerprint(p, fp)
}
return
}
//nolint:deadcode,unused // FIXME
func matchFingerprint(p *Process, fp *profile.Fingerprint) (score int) {
if !fp.MatchesOS() {
return 0
}
switch fp.Type {
case "full_path":
if p.Path == fp.Value {
return profile.GetFingerprintWeight(fp.Type)
}
case "partial_path":
// FIXME: if full_path matches, do not match partial paths
return profile.GetFingerprintWeight(fp.Type)
case "md5_sum", "sha1_sum", "sha256_sum":
// FIXME: one sum is enough, check sums in a grouped form, start with the best
sum, err := p.GetExecHash(fp.Type)
if err != nil {
log.Errorf("process: failed to get hash of executable: %s", err)
} else if sum == fp.Value {
return profile.GetFingerprintWeight(fp.Type)
}
}
return 0
}

View file

@ -17,7 +17,7 @@ var (
) )
// GetPidOfInode returns the pid of the given uid and socket inode. // GetPidOfInode returns the pid of the given uid and socket inode.
func GetPidOfInode(uid, inode int) (int, bool) { func GetPidOfInode(uid, inode int) (int, bool) { //nolint:gocognit // TODO
pidsByUserLock.Lock() pidsByUserLock.Lock()
defer pidsByUserLock.Unlock() defer pidsByUserLock.Unlock()

View file

@ -40,8 +40,8 @@ type Process struct {
// ExecOwner ... // ExecOwner ...
// ExecSignature ... // ExecSignature ...
UserProfileKey string LocalProfileKey string
profileSet *profile.Set profile *profile.LayeredProfile
Name string Name string
Icon string Icon string
// Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for database cache path or "c:"/"a:" for a the icon key to fetch it from a company / authoritative node and cache it in its own cache. // Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for database cache path or "c:"/"a:" for a the icon key to fetch it from a company / authoritative node and cache it in its own cache.
@ -53,12 +53,12 @@ type Process struct {
Error string // If this is set, the process is invalid. This is used to cache failing or inexistent processes. Error string // If this is set, the process is invalid. This is used to cache failing or inexistent processes.
} }
// ProfileSet returns the assigned profile set. // Profile returns the assigned layered profile.
func (p *Process) ProfileSet() *profile.Set { func (p *Process) Profile() *profile.LayeredProfile {
p.Lock() p.Lock()
defer p.Unlock() defer p.Unlock()
return p.profileSet return p.profile
} }
// Strings returns a string representation of process. // Strings returns a string representation of process.
@ -208,13 +208,14 @@ func GetOrFindProcess(ctx context.Context, pid int) (*Process, error) {
func deduplicateRequest(ctx context.Context, pid int) (finishRequest func()) { func deduplicateRequest(ctx context.Context, pid int) (finishRequest func()) {
dupReqLock.Lock() dupReqLock.Lock()
defer dupReqLock.Unlock()
// get duplicate request waitgroup // get duplicate request waitgroup
wg, requestActive := dupReqMap[pid] wg, requestActive := dupReqMap[pid]
// someone else is already on it! // someone else is already on it!
if requestActive { if requestActive {
dupReqLock.Unlock()
// log that we are waiting // log that we are waiting
log.Tracer(ctx).Tracef("intel: waiting for duplicate request for PID %d to complete", pid) log.Tracer(ctx).Tracef("intel: waiting for duplicate request for PID %d to complete", pid)
// wait // wait
@ -232,6 +233,8 @@ func deduplicateRequest(ctx context.Context, pid int) (finishRequest func()) {
// add to registry // add to registry
dupReqMap[pid] = wg dupReqMap[pid] = wg
dupReqLock.Unlock()
// return function to mark request as finished // return function to mark request as finished
return func() { return func() {
dupReqLock.Lock() dupReqLock.Lock()

42
process/profile.go Normal file
View file

@ -0,0 +1,42 @@
package process
import (
"context"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/profile"
)
// GetProfile finds and assigns a profile set to the process.
func (p *Process) GetProfile(ctx context.Context) error {
p.Lock()
defer p.Unlock()
// only find profiles if not already done.
if p.profile != nil {
log.Tracer(ctx).Trace("process: profile already loaded")
return nil
}
log.Tracer(ctx).Trace("process: loading profile")
// get profile
localProfile, new, err := profile.FindOrCreateLocalProfileByPath(p.Path)
if err != nil {
return err
}
// add more information if new
if new {
localProfile.Name = p.ExecName
}
// mark as used and save
if localProfile.MarkUsed() {
_ = localProfile.Save()
}
p.LocalProfileKey = localProfile.Key()
p.profile = profile.NewLayeredProfile(localProfile)
go p.Save()
return nil
}