mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
Continue with the new profile integration
This commit is contained in:
parent
5523fcf0bd
commit
200d9000f6
19 changed files with 509 additions and 557 deletions
|
@ -9,15 +9,12 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portbase/utils"
|
||||
"github.com/safing/portmaster/core/structure"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/dataroot"
|
||||
"github.com/safing/portbase/log"
|
||||
|
||||
"github.com/safing/portbase/utils"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/process"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -28,7 +25,7 @@ var (
|
|||
)
|
||||
|
||||
func prepAPIAuth() error {
|
||||
dataRoot = structure.Root()
|
||||
dataRoot = dataroot.Root()
|
||||
return api.SetAuthenticator(apiAuthenticator)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/safing/portmaster/intel"
|
||||
"github.com/safing/portmaster/network/environment"
|
||||
"github.com/safing/portmaster/resolver"
|
||||
)
|
||||
|
||||
func init() {
|
||||
intel.SetLocalAddrFactory(PermittedAddr)
|
||||
resolver.SetLocalAddrFactory(PermittedAddr)
|
||||
environment.SetLocalAddrFactory(PermittedAddr)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/config"
|
||||
"github.com/safing/portbase/modules/subsystems"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portmaster/firewall/inspection"
|
||||
|
@ -41,11 +44,26 @@ var (
|
|||
)
|
||||
|
||||
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) {
|
||||
|
||||
err = registerConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -188,22 +206,18 @@ func handlePacket(pkt packet.Packet) {
|
|||
|
||||
// associate packet to link and handle
|
||||
link, created := network.GetOrCreateLinkByPacket(pkt)
|
||||
defer func() {
|
||||
go link.SaveIfNeeded()
|
||||
}()
|
||||
if created {
|
||||
link.SetFirewallHandler(initialHandler)
|
||||
link.HandlePacket(pkt)
|
||||
return
|
||||
}
|
||||
if link.FirewallHandlerIsSet() {
|
||||
link.HandlePacket(pkt)
|
||||
return
|
||||
}
|
||||
issueVerdict(pkt, link, 0, true)
|
||||
|
||||
link.HandlePacket(pkt)
|
||||
}
|
||||
|
||||
func initialHandler(pkt packet.Packet, link *network.Link) {
|
||||
defer func() {
|
||||
go link.SaveIfNeeded()
|
||||
}()
|
||||
|
||||
log.Tracer(pkt.Ctx()).Trace("firewall: [initial handler]")
|
||||
|
||||
// check for internal firewall bypass
|
||||
|
@ -217,9 +231,6 @@ func initialHandler(pkt packet.Packet, link *network.Link) {
|
|||
} else {
|
||||
comm.AddLink(link)
|
||||
}
|
||||
defer func() {
|
||||
go comm.SaveIfNeeded()
|
||||
}()
|
||||
|
||||
// approve
|
||||
link.Accept("internally approved")
|
||||
|
@ -249,9 +260,6 @@ func initialHandler(pkt packet.Packet, link *network.Link) {
|
|||
return
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
go comm.SaveIfNeeded()
|
||||
}()
|
||||
|
||||
// add new Link to Communication (and save both)
|
||||
comm.AddLink(link)
|
||||
|
@ -267,7 +275,8 @@ func initialHandler(pkt packet.Packet, link *network.Link) {
|
|||
|
||||
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)
|
||||
|
||||
// TODO: link this to real status
|
||||
|
@ -380,7 +389,7 @@ func issueVerdict(pkt packet.Packet, link *network.Link, verdict network.Verdict
|
|||
func run() {
|
||||
for {
|
||||
select {
|
||||
case <-modules.ShuttingDown():
|
||||
case <-module.Stopping():
|
||||
return
|
||||
case pkt := <-interception.Packets:
|
||||
handlePacket(pkt)
|
||||
|
@ -391,7 +400,7 @@ func run() {
|
|||
func statLogger() {
|
||||
for {
|
||||
select {
|
||||
case <-modules.ShuttingDown():
|
||||
case <-module.Stopping():
|
||||
return
|
||||
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))
|
||||
|
|
|
@ -4,19 +4,20 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/intel"
|
||||
"github.com/safing/portmaster/network"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/process"
|
||||
"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/miekg/dns"
|
||||
)
|
||||
|
||||
// Call order:
|
||||
|
@ -30,11 +31,10 @@ import (
|
|||
// 4. DecideOnLink
|
||||
// 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.
|
||||
func DecideOnCommunicationBeforeIntel(comm *network.Communication, fqdn string) {
|
||||
|
||||
// check if communication needs reevaluation
|
||||
if comm.NeedsReevaluation() {
|
||||
// DecideOnCommunicationBeforeDNS makes a decision about a communication before the dns query is resolved and intel is gathered.
|
||||
func DecideOnCommunicationBeforeDNS(comm *network.Communication) {
|
||||
// update profiles and check if communication needs reevaluation
|
||||
if comm.UpdateAndCheck() {
|
||||
log.Infof("firewall: re-evaluating verdict on %s", comm)
|
||||
comm.ResetVerdict()
|
||||
}
|
||||
|
@ -51,116 +51,74 @@ func DecideOnCommunicationBeforeIntel(comm *network.Communication, fqdn string)
|
|||
return
|
||||
}
|
||||
|
||||
// get and check profile set
|
||||
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())
|
||||
// get profile
|
||||
p := comm.Process().Profile()
|
||||
|
||||
// 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)
|
||||
comm.Deny("accessing Internet or LAN not permitted")
|
||||
return
|
||||
}
|
||||
// continueing with access to either Internet or LAN
|
||||
|
||||
// check endpoint list
|
||||
result, reason := profileSet.CheckEndpointDomain(fqdn)
|
||||
// FIXME: comm.Entity.Lock()
|
||||
result, reason := p.MatchEndpoint(comm.Entity)
|
||||
// FIXME: comm.Entity.Unlock()
|
||||
switch result {
|
||||
case profile.NoMatch:
|
||||
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:
|
||||
case endpoints.Undeterminable:
|
||||
comm.UpdateVerdict(network.VerdictUndeterminable)
|
||||
case profile.Denied:
|
||||
log.Infof("firewall: denying communication %s, endpoint is blacklisted: %s", comm, reason)
|
||||
comm.Deny(fmt.Sprintf("endpoint is blacklisted: %s", reason))
|
||||
case profile.Permitted:
|
||||
log.Infof("firewall: permitting communication %s, endpoint is whitelisted: %s", comm, reason)
|
||||
comm.Accept(fmt.Sprintf("endpoint 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
|
||||
case endpoints.Denied:
|
||||
log.Infof("firewall: denying communication %s, domain is blacklisted: %s", comm, reason)
|
||||
comm.Deny(fmt.Sprintf("domain is blacklisted: %s", reason))
|
||||
return
|
||||
case endpoints.Permitted:
|
||||
log.Infof("firewall: permitting communication %s, domain is whitelisted: %s", comm, reason)
|
||||
comm.Accept(fmt.Sprintf("domain is whitelisted: %s", reason))
|
||||
return
|
||||
}
|
||||
// continueing with result == NoMatch
|
||||
|
||||
// grant self - should not get here
|
||||
if comm.Process().Pid == os.Getpid() {
|
||||
log.Infof("firewall: granting own communication %s", comm)
|
||||
comm.Accept("")
|
||||
// check default action
|
||||
if p.DefaultAction() == profile.DefaultActionPermit {
|
||||
log.Infof("firewall: permitting communication %s, domain is not blacklisted (default=permit)", comm)
|
||||
comm.Accept("domain is not blacklisted (default=permit)")
|
||||
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
|
||||
if profileSet.CheckFlag(profile.Related) {
|
||||
if checkRelation(comm, fqdn) {
|
||||
if !p.DisableAutoPermit() {
|
||||
if checkRelation(comm) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
//nolint:gocognit // FIXME
|
||||
func FilterDNSResponse(comm *network.Communication, q *intel.Query, rrCache *intel.RRCache) *intel.RRCache {
|
||||
func FilterDNSResponse(comm *network.Communication, q *resolver.Query, rrCache *resolver.RRCache) *resolver.RRCache { //nolint:gocognit // TODO
|
||||
// do not modify own queries - this should not happen anyway
|
||||
if comm.Process().Pid == os.Getpid() {
|
||||
return rrCache
|
||||
}
|
||||
|
||||
// check if there is a profile
|
||||
profileSet := comm.Process().ProfileSet()
|
||||
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)
|
||||
// get profile
|
||||
p := comm.Process().Profile()
|
||||
|
||||
// check if DNS response filtering is completely turned off
|
||||
if !filterByScope && !filterByProfile {
|
||||
if !p.RemoveOutOfScopeDNS() && !p.RemoveBlockedDNS() {
|
||||
return rrCache
|
||||
}
|
||||
|
||||
|
@ -175,7 +133,6 @@ func FilterDNSResponse(comm *network.Communication, q *intel.Query, rrCache *int
|
|||
// loop vars
|
||||
var classification int8
|
||||
var ip net.IP
|
||||
var result profile.EPResult
|
||||
|
||||
// filter function
|
||||
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)
|
||||
|
||||
if filterByScope {
|
||||
if p.RemoveOutOfScopeDNS() {
|
||||
switch {
|
||||
case classification == netutils.HostLocal:
|
||||
// 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
|
||||
switch {
|
||||
case !profileSet.CheckFlag(profile.Internet) && classification == netutils.Global:
|
||||
case p.BlockScopeInternet() && classification == netutils.Global:
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
case !profileSet.CheckFlag(profile.LAN) && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
|
||||
case p.BlockScopeLAN() && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
case !profileSet.CheckFlag(profile.Localhost) && classification == netutils.HostLocal:
|
||||
case p.BlockScopeLocal() && classification == netutils.HostLocal:
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
}
|
||||
|
||||
// filter by endpoints
|
||||
result, _ = profileSet.CheckEndpointIP(q.FQDN, ip, 0, 0, false)
|
||||
if result == profile.Denied {
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
}
|
||||
// TODO: filter by endpoint list (IP only)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) {
|
||||
|
||||
// check if communication needs reevaluation, if it's not with a domain
|
||||
if comm.NeedsReevaluation() {
|
||||
func DecideOnCommunication(comm *network.Communication) {
|
||||
// update profiles and check if communication needs reevaluation
|
||||
if comm.UpdateAndCheck() {
|
||||
log.Infof("firewall: re-evaluating verdict on %s", comm)
|
||||
comm.ResetVerdict()
|
||||
|
||||
// if communicating with a domain entity, re-evaluate with Before/AfterIntel
|
||||
if strings.HasSuffix(comm.Domain, ".") {
|
||||
DecideOnCommunicationBeforeIntel(comm, comm.Domain)
|
||||
DecideOnCommunicationAfterIntel(comm, comm.Domain, nil)
|
||||
// if communicating with a domain entity, re-evaluate with BeforeDNS
|
||||
if strings.HasSuffix(comm.Scope, ".") {
|
||||
DecideOnCommunicationBeforeDNS(comm)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -293,29 +242,24 @@ func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) {
|
|||
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())
|
||||
// get profile
|
||||
p := comm.Process().Profile()
|
||||
|
||||
// check comm type
|
||||
switch comm.Domain {
|
||||
switch comm.Scope {
|
||||
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)
|
||||
if comm.Domain == network.IncomingHost {
|
||||
if comm.Scope == network.IncomingHost {
|
||||
comm.Block("not a service")
|
||||
} else {
|
||||
comm.Deny("not a service")
|
||||
}
|
||||
return
|
||||
}
|
||||
case network.PeerLAN, network.PeerInternet, network.PeerInvalid: // Important: PeerHost is and should be missing!
|
||||
if !profileSet.CheckFlag(profile.PeerToPeer) {
|
||||
case network.PeerLAN, network.PeerInternet, network.PeerInvalid:
|
||||
// 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)
|
||||
comm.Deny("peer to peer comms (to an IP) not allowed")
|
||||
return
|
||||
|
@ -323,21 +267,21 @@ func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) {
|
|||
}
|
||||
|
||||
// check network scope
|
||||
switch comm.Domain {
|
||||
switch comm.Scope {
|
||||
case network.IncomingHost:
|
||||
if !profileSet.CheckFlag(profile.Localhost) {
|
||||
if p.BlockScopeLocal() {
|
||||
log.Infof("firewall: denying communication %s, serving localhost not allowed", comm)
|
||||
comm.Block("serving localhost not allowed")
|
||||
return
|
||||
}
|
||||
case network.IncomingLAN:
|
||||
if !profileSet.CheckFlag(profile.LAN) {
|
||||
if p.BlockScopeLAN() {
|
||||
log.Infof("firewall: denying communication %s, serving LAN not allowed", comm)
|
||||
comm.Deny("serving LAN not allowed")
|
||||
return
|
||||
}
|
||||
case network.IncomingInternet:
|
||||
if !profileSet.CheckFlag(profile.Internet) {
|
||||
if p.BlockScopeInternet() {
|
||||
log.Infof("firewall: denying communication %s, serving Internet not allowed", comm)
|
||||
comm.Deny("serving Internet not allowed")
|
||||
return
|
||||
|
@ -347,19 +291,19 @@ func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) {
|
|||
comm.Drop("invalid IP address")
|
||||
return
|
||||
case network.PeerHost:
|
||||
if !profileSet.CheckFlag(profile.Localhost) {
|
||||
if p.BlockScopeLocal() {
|
||||
log.Infof("firewall: denying communication %s, accessing localhost not allowed", comm)
|
||||
comm.Block("accessing localhost not allowed")
|
||||
return
|
||||
}
|
||||
case network.PeerLAN:
|
||||
if !profileSet.CheckFlag(profile.LAN) {
|
||||
if p.BlockScopeLAN() {
|
||||
log.Infof("firewall: denying communication %s, accessing the LAN not allowed", comm)
|
||||
comm.Deny("accessing the LAN not allowed")
|
||||
return
|
||||
}
|
||||
case network.PeerInternet:
|
||||
if !profileSet.CheckFlag(profile.Internet) {
|
||||
if p.BlockScopeInternet() {
|
||||
log.Infof("firewall: denying communication %s, accessing the Internet not allowed", comm)
|
||||
comm.Deny("accessing the Internet not allowed")
|
||||
return
|
||||
|
@ -384,7 +328,7 @@ func DecideOnLink(comm *network.Communication, link *network.Link, pkt packet.Pa
|
|||
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) {
|
||||
// get PID
|
||||
otherPid, _, err := process.GetPidByEndpoints(
|
||||
|
@ -424,86 +368,80 @@ func DecideOnLink(comm *network.Communication, link *network.Link, pkt packet.Pa
|
|||
return
|
||||
}
|
||||
|
||||
// check if there is a profile
|
||||
profileSet := comm.Process().ProfileSet()
|
||||
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
|
||||
// get profile
|
||||
p := comm.Process().Profile()
|
||||
|
||||
// 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 {
|
||||
case profile.Denied:
|
||||
case endpoints.Denied:
|
||||
log.Infof("firewall: denying link %s, endpoint is blacklisted: %s", link, reason)
|
||||
link.Deny(fmt.Sprintf("endpoint is blacklisted: %s", reason))
|
||||
return
|
||||
case profile.Permitted:
|
||||
case endpoints.Permitted:
|
||||
log.Infof("firewall: permitting link %s, endpoint is whitelisted: %s", link, reason)
|
||||
link.Accept(fmt.Sprintf("endpoint is whitelisted: %s", reason))
|
||||
return
|
||||
}
|
||||
// continueing with result == NoMatch
|
||||
|
||||
// TODO: Stamp integration
|
||||
|
||||
switch profileSet.GetProfileMode() {
|
||||
case profile.Whitelist:
|
||||
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")
|
||||
// implicit default=block for incoming
|
||||
if comm.Direction {
|
||||
log.Infof("firewall: denying link %s: endpoint is not whitelisted (incoming is always default=block)", link)
|
||||
link.Deny("endpoint is not whitelisted (incoming is always default=block)")
|
||||
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
|
||||
if fqdn != "" && profileSet.CheckFlag(profile.Related) {
|
||||
if checkRelation(comm, fqdn) {
|
||||
if !p.DisableAutoPermit() {
|
||||
if checkRelation(comm) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// prompt
|
||||
prompt(comm, link, pkt, fqdn)
|
||||
}
|
||||
|
||||
func checkRelation(comm *network.Communication, fqdn string) (related bool) {
|
||||
profileSet := comm.Process().ProfileSet()
|
||||
if profileSet == nil {
|
||||
if p.DefaultAction() == profile.DefaultActionAsk {
|
||||
prompt(comm, link, pkt)
|
||||
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
|
||||
if len(pathElements) > 2 {
|
||||
pathElements = pathElements[len(pathElements)-2:]
|
||||
}
|
||||
domainElements := strings.Split(fqdn, ".")
|
||||
domainElements := strings.Split(comm.Entity.Domain, ".")
|
||||
|
||||
var domainElement string
|
||||
var processElement string
|
||||
|
@ -517,11 +455,6 @@ 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 {
|
||||
related = true
|
||||
processElement = comm.Process().Name
|
||||
|
|
|
@ -71,8 +71,12 @@ func GetPermittedPort() uint16 {
|
|||
|
||||
func portsInUseCleaner() {
|
||||
for {
|
||||
time.Sleep(cleanerTickDuration)
|
||||
cleanPortsInUse()
|
||||
select {
|
||||
case <-module.Stopping():
|
||||
return
|
||||
case <-time.After(cleanerTickDuration):
|
||||
cleanPortsInUse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portmaster/profile/endpoints"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/notifications"
|
||||
"github.com/safing/portmaster/network"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -30,14 +30,14 @@ var (
|
|||
)
|
||||
|
||||
//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
|
||||
|
||||
// first check if there is an existing notification for this.
|
||||
// build notification ID
|
||||
var nID string
|
||||
switch {
|
||||
case comm.Direction, fqdn == "": // connection to/from IP
|
||||
case comm.Direction, comm.Entity.Domain == "": // connection to/from IP
|
||||
if pkt == nil {
|
||||
log.Error("firewall: could not prompt for incoming/direct connection: missing pkt")
|
||||
if link != nil {
|
||||
|
@ -47,9 +47,9 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
|
|||
}
|
||||
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
|
||||
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)
|
||||
saveResponse := true
|
||||
|
@ -70,7 +70,7 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
|
|||
// add message and actions
|
||||
switch {
|
||||
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{
|
||||
{
|
||||
ID: permitServingIP,
|
||||
|
@ -81,8 +81,8 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
|
|||
Text: "Deny",
|
||||
},
|
||||
}
|
||||
case fqdn == "": // 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())
|
||||
case comm.Entity.Domain == "": // direct connection
|
||||
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{
|
||||
{
|
||||
ID: permitIP,
|
||||
|
@ -94,10 +94,10 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
|
|||
},
|
||||
}
|
||||
default: // connection to domain
|
||||
if pkt != 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())
|
||||
if link != nil {
|
||||
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 {
|
||||
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{
|
||||
{
|
||||
|
@ -141,62 +141,57 @@ func prompt(comm *network.Communication, link *network.Link, pkt packet.Packet,
|
|||
return
|
||||
}
|
||||
|
||||
new := &profile.EndpointPermission{
|
||||
Type: profile.EptDomain,
|
||||
Value: comm.Domain,
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
}
|
||||
// get profile
|
||||
p := comm.Process().Profile()
|
||||
|
||||
// permission type
|
||||
var ep endpoints.Endpoint
|
||||
switch promptResponse {
|
||||
case permitDomainAll, denyDomainAll:
|
||||
new.Value = "." + new.Value
|
||||
case permitIP, permitServingIP, denyIP, denyServingIP:
|
||||
if pkt == nil {
|
||||
log.Warningf("firewall: received invalid prompt response: %s for %s", promptResponse, comm.Domain)
|
||||
return
|
||||
case permitDomainAll:
|
||||
ep = &endpoints.EndpointDomain{
|
||||
EndpointBase: endpoints.EndpointBase{Permitted: true},
|
||||
Domain: "." + comm.Entity.Domain,
|
||||
}
|
||||
if pkt.Info().Version == packet.IPv4 {
|
||||
new.Type = profile.EptIPv4
|
||||
} else {
|
||||
new.Type = profile.EptIPv6
|
||||
case permitDomainDistinct:
|
||||
ep = &endpoints.EndpointDomain{
|
||||
EndpointBase: endpoints.EndpointBase{Permitted: true},
|
||||
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 {
|
||||
case permitServingIP, denyServingIP:
|
||||
userProfile.ServiceEndpoints = append(userProfile.ServiceEndpoints, new)
|
||||
p.AddServiceEndpoint(ep.String())
|
||||
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():
|
||||
if link != nil {
|
||||
link.Accept("no response to prompt")
|
||||
link.Deny("no response to prompt")
|
||||
} else {
|
||||
comm.Accept("no response to prompt")
|
||||
comm.Deny("no response to prompt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,9 +40,9 @@ func cleanLinks() (activeComms map[string]struct{}) {
|
|||
for key, link := range links {
|
||||
|
||||
// delete dead links
|
||||
link.Lock()
|
||||
link.lock.Lock()
|
||||
deleteThis := link.Ended > 0 && link.Ended < deleteOlderThan
|
||||
link.Unlock()
|
||||
link.lock.Unlock()
|
||||
if deleteThis {
|
||||
log.Tracef("network.clean: deleted %s (ended at %d)", link.DatabaseKey(), link.Ended)
|
||||
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
|
||||
comm := link.Communication()
|
||||
comm.Lock()
|
||||
comm.lock.Lock()
|
||||
markActive(activeComms, comm.DatabaseKey())
|
||||
comm.Unlock()
|
||||
comm.lock.Unlock()
|
||||
|
||||
// check if link is dead
|
||||
found = false
|
||||
|
@ -66,9 +66,9 @@ func cleanLinks() (activeComms map[string]struct{}) {
|
|||
|
||||
if !found {
|
||||
// mark end time
|
||||
link.Lock()
|
||||
link.lock.Lock()
|
||||
link.Ended = now
|
||||
link.Unlock()
|
||||
link.lock.Unlock()
|
||||
log.Tracef("network.clean: marked %s as ended", link.DatabaseKey())
|
||||
// save
|
||||
linkToSave := link
|
||||
|
@ -95,9 +95,9 @@ func cleanComms(activeLinks map[string]struct{}) (activeComms map[string]struct{
|
|||
_, hasLinks := activeLinks[comm.DatabaseKey()]
|
||||
|
||||
// comm created
|
||||
comm.Lock()
|
||||
comm.lock.Lock()
|
||||
created := comm.Meta().Created
|
||||
comm.Unlock()
|
||||
comm.lock.Unlock()
|
||||
|
||||
if !hasLinks && created < threshold {
|
||||
log.Tracef("network.clean: deleted %s", comm.DatabaseKey())
|
||||
|
|
|
@ -8,48 +8,63 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portmaster/resolver"
|
||||
|
||||
"github.com/safing/portbase/database/record"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/intel"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/process"
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
// Communication describes a logical connection between a process and a domain.
|
||||
//nolint:maligned // TODO: fix alignment
|
||||
type Communication struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
lock sync.Mutex
|
||||
|
||||
Domain string
|
||||
Scope string
|
||||
Entity *intel.Entity
|
||||
Direction bool
|
||||
Intel *intel.Intel
|
||||
process *process.Process
|
||||
Verdict Verdict
|
||||
Reason string
|
||||
Inspect bool
|
||||
|
||||
Verdict Verdict
|
||||
Reason string
|
||||
ReasonID string // format source[:id[:id]]
|
||||
Inspect bool
|
||||
process *process.Process
|
||||
profileRevisionCounter uint64
|
||||
|
||||
FirstLinkEstablished 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.
|
||||
func (comm *Communication) Process() *process.Process {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
comm.lock.Lock()
|
||||
defer comm.lock.Unlock()
|
||||
|
||||
return comm.process
|
||||
}
|
||||
|
||||
// ResetVerdict resets the verdict to VerdictUndecided.
|
||||
func (comm *Communication) ResetVerdict() {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
comm.lock.Lock()
|
||||
defer comm.lock.Unlock()
|
||||
|
||||
comm.Verdict = VerdictUndecided
|
||||
comm.Reason = ""
|
||||
|
@ -58,8 +73,8 @@ func (comm *Communication) ResetVerdict() {
|
|||
|
||||
// GetVerdict returns the current verdict.
|
||||
func (comm *Communication) GetVerdict() Verdict {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
comm.lock.Lock()
|
||||
defer comm.lock.Unlock()
|
||||
|
||||
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.
|
||||
func (comm *Communication) UpdateVerdict(newVerdict Verdict) {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
comm.lock.Lock()
|
||||
defer comm.lock.Unlock()
|
||||
|
||||
if newVerdict > comm.Verdict {
|
||||
comm.Verdict = newVerdict
|
||||
|
@ -108,8 +123,8 @@ func (comm *Communication) SetReason(reason string) {
|
|||
return
|
||||
}
|
||||
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
comm.lock.Lock()
|
||||
defer comm.lock.Unlock()
|
||||
comm.Reason = reason
|
||||
comm.saveWhenFinished = true
|
||||
}
|
||||
|
@ -120,8 +135,8 @@ func (comm *Communication) AddReason(reason string) {
|
|||
return
|
||||
}
|
||||
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
comm.lock.Lock()
|
||||
defer comm.lock.Unlock()
|
||||
|
||||
if comm.Reason != "" {
|
||||
comm.Reason += " | "
|
||||
|
@ -129,21 +144,18 @@ func (comm *Communication) AddReason(reason string) {
|
|||
comm.Reason += reason
|
||||
}
|
||||
|
||||
// NeedsReevaluation returns whether the decision on this communication should be re-evaluated.
|
||||
func (comm *Communication) NeedsReevaluation() bool {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
// UpdateAndCheck updates profiles and checks whether a reevaluation is needed.
|
||||
func (comm *Communication) UpdateAndCheck() (needsReevaluation bool) {
|
||||
revCnt := comm.Process().Profile().Update()
|
||||
|
||||
oldVersion := comm.profileUpdateVersion
|
||||
comm.profileUpdateVersion = profile.GetUpdateVersion()
|
||||
comm.lock.Lock()
|
||||
defer comm.lock.Unlock()
|
||||
if comm.profileRevisionCounter != revCnt {
|
||||
comm.profileRevisionCounter = revCnt
|
||||
needsReevaluation = true
|
||||
}
|
||||
|
||||
if oldVersion == 0 {
|
||||
return false
|
||||
}
|
||||
if oldVersion != comm.profileUpdateVersion {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return
|
||||
}
|
||||
|
||||
// GetCommunicationByFirstPacket returns the matching communication from the internal storage.
|
||||
|
@ -153,25 +165,26 @@ func GetCommunicationByFirstPacket(pkt packet.Packet) (*Communication, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var domain string
|
||||
var scope string
|
||||
|
||||
// Incoming
|
||||
if direction {
|
||||
switch netutils.ClassifyIP(pkt.Info().Src) {
|
||||
case netutils.HostLocal:
|
||||
domain = IncomingHost
|
||||
scope = IncomingHost
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
domain = IncomingLAN
|
||||
scope = IncomingLAN
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
domain = IncomingInternet
|
||||
scope = IncomingInternet
|
||||
case netutils.Invalid:
|
||||
domain = IncomingInvalid
|
||||
scope = IncomingInvalid
|
||||
}
|
||||
|
||||
communication, ok := GetCommunication(proc.Pid, domain)
|
||||
communication, ok := GetCommunication(proc.Pid, scope)
|
||||
if !ok {
|
||||
communication = &Communication{
|
||||
Domain: domain,
|
||||
Scope: scope,
|
||||
Entity: (&intel.Entity{}).Init(),
|
||||
Direction: Inbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
|
@ -184,7 +197,7 @@ func GetCommunicationByFirstPacket(pkt packet.Packet) (*Communication, error) {
|
|||
}
|
||||
|
||||
// get domain
|
||||
ipinfo, err := intel.GetIPInfo(pkt.FmtRemoteIP())
|
||||
ipinfo, err := resolver.GetIPInfo(pkt.FmtRemoteIP())
|
||||
|
||||
// PeerToPeer
|
||||
if err != nil {
|
||||
|
@ -192,19 +205,20 @@ func GetCommunicationByFirstPacket(pkt packet.Packet) (*Communication, error) {
|
|||
|
||||
switch netutils.ClassifyIP(pkt.Info().Dst) {
|
||||
case netutils.HostLocal:
|
||||
domain = PeerHost
|
||||
scope = PeerHost
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
domain = PeerLAN
|
||||
scope = PeerLAN
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
domain = PeerInternet
|
||||
scope = PeerInternet
|
||||
case netutils.Invalid:
|
||||
domain = PeerInvalid
|
||||
scope = PeerInvalid
|
||||
}
|
||||
|
||||
communication, ok := GetCommunication(proc.Pid, domain)
|
||||
communication, ok := GetCommunication(proc.Pid, scope)
|
||||
if !ok {
|
||||
communication = &Communication{
|
||||
Domain: domain,
|
||||
Scope: scope,
|
||||
Entity: (&intel.Entity{}).Init(),
|
||||
Direction: Outbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
|
@ -221,7 +235,10 @@ func GetCommunicationByFirstPacket(pkt packet.Packet) (*Communication, error) {
|
|||
communication, ok := GetCommunication(proc.Pid, ipinfo.Domains[0])
|
||||
if !ok {
|
||||
communication = &Communication{
|
||||
Domain: ipinfo.Domains[0],
|
||||
Scope: ipinfo.Domains[0],
|
||||
Entity: (&intel.Entity{
|
||||
Domain: ipinfo.Domains[0],
|
||||
}).Init(),
|
||||
Direction: Outbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
|
@ -251,7 +268,10 @@ func GetCommunicationByDNSRequest(ctx context.Context, ip net.IP, port uint16, f
|
|||
communication, ok := GetCommunication(proc.Pid, fqdn)
|
||||
if !ok {
|
||||
communication = &Communication{
|
||||
Domain: fqdn,
|
||||
Scope: fqdn,
|
||||
Entity: (&intel.Entity{
|
||||
Domain: fqdn,
|
||||
}).Init(),
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
saveWhenFinished: true,
|
||||
|
@ -271,7 +291,7 @@ func GetCommunication(pid int, domain string) (comm *Communication, ok bool) {
|
|||
}
|
||||
|
||||
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.
|
||||
|
@ -281,12 +301,12 @@ func (comm *Communication) SaveWhenFinished() {
|
|||
|
||||
// SaveIfNeeded saves the Connection if it is marked for saving when finished.
|
||||
func (comm *Communication) SaveIfNeeded() {
|
||||
comm.Lock()
|
||||
comm.lock.Lock()
|
||||
save := comm.saveWhenFinished
|
||||
if save {
|
||||
comm.saveWhenFinished = false
|
||||
}
|
||||
comm.Unlock()
|
||||
comm.lock.Unlock()
|
||||
|
||||
if save {
|
||||
err := comm.save()
|
||||
|
@ -299,14 +319,14 @@ func (comm *Communication) SaveIfNeeded() {
|
|||
// Save saves the Connection object in the storage and propagates the change.
|
||||
func (comm *Communication) save() error {
|
||||
// update comm
|
||||
comm.Lock()
|
||||
comm.lock.Lock()
|
||||
if comm.process == nil {
|
||||
comm.Unlock()
|
||||
comm.lock.Unlock()
|
||||
return errors.New("cannot save connection without process")
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
if comm.Meta().Deleted > 0 {
|
||||
|
@ -315,7 +335,7 @@ func (comm *Communication) save() error {
|
|||
}
|
||||
key := comm.makeKey()
|
||||
comm.saveWhenFinished = false
|
||||
comm.Unlock()
|
||||
comm.lock.Unlock()
|
||||
|
||||
// save comm
|
||||
commsLock.RLock()
|
||||
|
@ -336,8 +356,8 @@ func (comm *Communication) save() error {
|
|||
func (comm *Communication) Delete() {
|
||||
commsLock.Lock()
|
||||
defer commsLock.Unlock()
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
comm.lock.Lock()
|
||||
defer comm.lock.Unlock()
|
||||
|
||||
delete(comms, comm.makeKey())
|
||||
|
||||
|
@ -347,16 +367,18 @@ func (comm *Communication) Delete() {
|
|||
|
||||
// AddLink applies the Communication to the Link and sets timestamps.
|
||||
func (comm *Communication) AddLink(link *Link) {
|
||||
comm.lock.Lock()
|
||||
defer comm.lock.Unlock()
|
||||
|
||||
// apply comm to link
|
||||
link.Lock()
|
||||
link.lock.Lock()
|
||||
link.comm = comm
|
||||
link.Verdict = comm.Verdict
|
||||
link.Inspect = comm.Inspect
|
||||
// FIXME: use new copy methods
|
||||
link.Entity.Domain = comm.Entity.Domain
|
||||
link.saveWhenFinished = true
|
||||
link.Unlock()
|
||||
|
||||
// update comm LastLinkEstablished
|
||||
comm.Lock()
|
||||
link.lock.Unlock()
|
||||
|
||||
// check if we should save
|
||||
if comm.LastLinkEstablished < time.Now().Add(-3*time.Second).Unix() {
|
||||
|
@ -368,8 +390,6 @@ func (comm *Communication) AddLink(link *Link) {
|
|||
if comm.FirstLinkEstablished == 0 {
|
||||
comm.FirstLinkEstablished = comm.LastLinkEstablished
|
||||
}
|
||||
|
||||
comm.Unlock()
|
||||
}
|
||||
|
||||
// String returns a string representation of Communication.
|
||||
|
@ -377,7 +397,7 @@ func (comm *Communication) String() string {
|
|||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
|
||||
switch comm.Domain {
|
||||
switch comm.Scope {
|
||||
case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid:
|
||||
if comm.process == nil {
|
||||
return "? <- *"
|
||||
|
@ -390,8 +410,8 @@ func (comm *Communication) String() string {
|
|||
return fmt.Sprintf("%s -> *", comm.process.String())
|
||||
default:
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
144
network/link.go
144
network/link.go
|
@ -7,6 +7,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portmaster/intel"
|
||||
|
||||
"github.com/safing/portbase/database/record"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
|
@ -16,21 +18,22 @@ import (
|
|||
type FirewallHandler func(pkt packet.Packet, link *Link)
|
||||
|
||||
// Link describes a distinct physical connection (e.g. TCP connection) - like an instance - of a Connection.
|
||||
//nolint:maligned // TODO: fix alignment
|
||||
type Link struct {
|
||||
type Link struct { //nolint:maligned // TODO: fix alignment
|
||||
record.Base
|
||||
sync.Mutex
|
||||
lock sync.Mutex
|
||||
|
||||
ID string
|
||||
ID string
|
||||
Entity *intel.Entity
|
||||
Direction bool
|
||||
|
||||
Verdict Verdict
|
||||
Reason string
|
||||
ReasonID string // format source[:id[:id]]
|
||||
Tunneled bool
|
||||
VerdictPermanent bool
|
||||
Inspect bool
|
||||
Started int64
|
||||
Ended int64
|
||||
RemoteAddress string
|
||||
|
||||
pktQueue chan packet.Packet
|
||||
firewallHandler FirewallHandler
|
||||
|
@ -41,70 +44,82 @@ type Link struct {
|
|||
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
|
||||
func (link *Link) Communication() *Communication {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
return link.comm
|
||||
}
|
||||
|
||||
// GetVerdict returns the current verdict.
|
||||
func (link *Link) GetVerdict() Verdict {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
return link.Verdict
|
||||
}
|
||||
|
||||
// FirewallHandlerIsSet returns whether a firewall handler is set or not
|
||||
func (link *Link) FirewallHandlerIsSet() bool {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
return link.firewallHandler != nil
|
||||
}
|
||||
|
||||
// SetFirewallHandler sets the firewall handler for this link
|
||||
func (link *Link) SetFirewallHandler(handler FirewallHandler) {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
if link.firewallHandler == nil {
|
||||
link.firewallHandler = handler
|
||||
link.pktQueue = make(chan packet.Packet, 1000)
|
||||
|
||||
// start handling
|
||||
module.StartWorker("", func(ctx context.Context) error {
|
||||
module.StartWorker("packet handler", func(ctx context.Context) error {
|
||||
link.packetHandler()
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
link.firewallHandler = handler
|
||||
}
|
||||
|
||||
// StopFirewallHandler unsets the firewall handler
|
||||
func (link *Link) StopFirewallHandler() {
|
||||
link.Lock()
|
||||
link.lock.Lock()
|
||||
link.firewallHandler = nil
|
||||
link.Unlock()
|
||||
link.lock.Unlock()
|
||||
link.pktQueue <- nil
|
||||
}
|
||||
|
||||
// HandlePacket queues packet of Link for handling
|
||||
func (link *Link) HandlePacket(pkt packet.Packet) {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
// get handler
|
||||
link.lock.Lock()
|
||||
handler := link.firewallHandler
|
||||
link.lock.Unlock()
|
||||
|
||||
if link.firewallHandler != nil {
|
||||
// send to queue
|
||||
if handler != nil {
|
||||
link.pktQueue <- pkt
|
||||
return
|
||||
}
|
||||
|
||||
// no handler!
|
||||
log.Warningf("network: link %s does not have a firewallHandler, dropping packet", link)
|
||||
|
||||
err := pkt.Drop()
|
||||
if err != nil {
|
||||
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.
|
||||
func (link *Link) Deny(reason string) {
|
||||
if link.comm != nil && link.comm.Direction {
|
||||
if link.Direction {
|
||||
link.Drop(reason)
|
||||
} else {
|
||||
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
|
||||
func (link *Link) UpdateVerdict(newVerdict Verdict) {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
if newVerdict > link.Verdict {
|
||||
link.Verdict = newVerdict
|
||||
|
@ -166,8 +181,8 @@ func (link *Link) AddReason(reason string) {
|
|||
return
|
||||
}
|
||||
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
if link.Reason != "" {
|
||||
link.Reason += " | "
|
||||
|
@ -185,9 +200,9 @@ func (link *Link) packetHandler() {
|
|||
return
|
||||
}
|
||||
// get handler
|
||||
link.Lock()
|
||||
link.lock.Lock()
|
||||
handler := link.firewallHandler
|
||||
link.Unlock()
|
||||
link.lock.Unlock()
|
||||
// execute handler or verdict
|
||||
if handler != nil {
|
||||
handler(pkt, link)
|
||||
|
@ -201,8 +216,8 @@ func (link *Link) packetHandler() {
|
|||
|
||||
// ApplyVerdict appies the link verdict to a packet.
|
||||
func (link *Link) ApplyVerdict(pkt packet.Packet) {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
var err error
|
||||
|
||||
|
@ -251,12 +266,12 @@ func (link *Link) SaveWhenFinished() {
|
|||
|
||||
// SaveIfNeeded saves the Link if it is marked for saving when finished.
|
||||
func (link *Link) SaveIfNeeded() {
|
||||
link.Lock()
|
||||
link.lock.Lock()
|
||||
save := link.saveWhenFinished
|
||||
if save {
|
||||
link.saveWhenFinished = false
|
||||
}
|
||||
link.Unlock()
|
||||
link.lock.Unlock()
|
||||
|
||||
if save {
|
||||
link.saveAndLog()
|
||||
|
@ -274,18 +289,18 @@ func (link *Link) saveAndLog() {
|
|||
// save saves the link object in the storage and propagates the change.
|
||||
func (link *Link) save() error {
|
||||
// update link
|
||||
link.Lock()
|
||||
link.lock.Lock()
|
||||
if link.comm == nil {
|
||||
link.Unlock()
|
||||
link.lock.Unlock()
|
||||
return errors.New("cannot save link without comms")
|
||||
}
|
||||
|
||||
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.saveWhenFinished = false
|
||||
link.Unlock()
|
||||
link.lock.Unlock()
|
||||
|
||||
// save link
|
||||
linksLock.RLock()
|
||||
|
@ -306,8 +321,8 @@ func (link *Link) save() error {
|
|||
func (link *Link) Delete() {
|
||||
linksLock.Lock()
|
||||
defer linksLock.Unlock()
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
delete(links, link.ID)
|
||||
|
||||
|
@ -339,10 +354,15 @@ func GetOrCreateLinkByPacket(pkt packet.Packet) (*Link, bool) {
|
|||
// CreateLinkFromPacket creates a new Link based on Packet.
|
||||
func CreateLinkFromPacket(pkt packet.Packet) *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,
|
||||
Started: time.Now().Unix(),
|
||||
RemoteAddress: pkt.FmtRemoteAddress(),
|
||||
saveWhenFinished: true,
|
||||
}
|
||||
return link
|
||||
|
@ -350,59 +370,59 @@ func CreateLinkFromPacket(pkt packet.Packet) *Link {
|
|||
|
||||
// GetActiveInspectors returns the list of active inspectors.
|
||||
func (link *Link) GetActiveInspectors() []bool {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
return link.activeInspectors
|
||||
}
|
||||
|
||||
// SetActiveInspectors sets the list of active inspectors.
|
||||
func (link *Link) SetActiveInspectors(new []bool) {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
link.activeInspectors = new
|
||||
}
|
||||
|
||||
// GetInspectorData returns the list of inspector data.
|
||||
func (link *Link) GetInspectorData() map[uint8]interface{} {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
return link.inspectorData
|
||||
}
|
||||
|
||||
// SetInspectorData set the list of inspector data.
|
||||
func (link *Link) SetInspectorData(new map[uint8]interface{}) {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
link.inspectorData = new
|
||||
}
|
||||
|
||||
// String returns a string representation of Link.
|
||||
func (link *Link) String() string {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
link.lock.Lock()
|
||||
defer link.lock.Unlock()
|
||||
|
||||
if link.comm == nil {
|
||||
return fmt.Sprintf("? <-> %s", link.RemoteAddress)
|
||||
return fmt.Sprintf("? <-> %s", link.Entity.IP.String())
|
||||
}
|
||||
switch link.comm.Domain {
|
||||
case "I":
|
||||
switch link.comm.Scope {
|
||||
case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid:
|
||||
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)
|
||||
case "D":
|
||||
return fmt.Sprintf("%s <- %s", link.comm.process.String(), link.Entity.IP.String())
|
||||
case PeerHost, PeerLAN, PeerInternet, PeerInvalid:
|
||||
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:
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,36 +5,38 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portmaster/intel"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"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) {
|
||||
var domain string
|
||||
var scope string
|
||||
|
||||
// Incoming
|
||||
if pkt.IsInbound() {
|
||||
switch netutils.ClassifyIP(pkt.Info().RemoteIP()) {
|
||||
case netutils.HostLocal:
|
||||
domain = IncomingHost
|
||||
scope = IncomingHost
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
domain = IncomingLAN
|
||||
scope = IncomingLAN
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
domain = IncomingInternet
|
||||
scope = IncomingInternet
|
||||
case netutils.Invalid:
|
||||
domain = IncomingInvalid
|
||||
scope = IncomingInvalid
|
||||
}
|
||||
|
||||
communication, ok := GetCommunication(os.Getpid(), domain)
|
||||
communication, ok := GetCommunication(os.Getpid(), scope)
|
||||
if !ok {
|
||||
proc, err := process.GetOrFindProcess(pkt.Ctx(), os.Getpid())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get own process")
|
||||
}
|
||||
communication = &Communication{
|
||||
Domain: domain,
|
||||
Scope: scope,
|
||||
Entity: (&intel.Entity{}).Init(),
|
||||
Direction: Inbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
|
@ -48,23 +50,24 @@ func GetOwnComm(pkt packet.Packet) (*Communication, error) {
|
|||
// PeerToPeer
|
||||
switch netutils.ClassifyIP(pkt.Info().RemoteIP()) {
|
||||
case netutils.HostLocal:
|
||||
domain = PeerHost
|
||||
scope = PeerHost
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
domain = PeerLAN
|
||||
scope = PeerLAN
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
domain = PeerInternet
|
||||
scope = PeerInternet
|
||||
case netutils.Invalid:
|
||||
domain = PeerInvalid
|
||||
scope = PeerInvalid
|
||||
}
|
||||
|
||||
communication, ok := GetCommunication(os.Getpid(), domain)
|
||||
communication, ok := GetCommunication(os.Getpid(), scope)
|
||||
if !ok {
|
||||
proc, err := process.GetOrFindProcess(pkt.Ctx(), os.Getpid())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get own process")
|
||||
}
|
||||
communication = &Communication{
|
||||
Domain: domain,
|
||||
Scope: scope,
|
||||
Entity: (&intel.Entity{}).Init(),
|
||||
Direction: Outbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
|
|
|
@ -42,7 +42,7 @@ const (
|
|||
Outbound = false
|
||||
)
|
||||
|
||||
// Non-Domain Connections
|
||||
// Non-Domain Scopes
|
||||
const (
|
||||
IncomingHost = "IH"
|
||||
IncomingLAN = "IL"
|
||||
|
|
|
@ -3,6 +3,7 @@ package network
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/safing/portmaster/intel"
|
||||
"github.com/safing/portmaster/network/netutils"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/process"
|
||||
|
@ -43,11 +44,12 @@ func GetUnknownCommunication(pkt packet.Packet) (*Communication, error) {
|
|||
return getOrCreateUnknownCommunication(pkt, PeerInvalid)
|
||||
}
|
||||
|
||||
func getOrCreateUnknownCommunication(pkt packet.Packet, connClass string) (*Communication, error) {
|
||||
connection, ok := GetCommunication(process.UnknownProcess.Pid, connClass)
|
||||
func getOrCreateUnknownCommunication(pkt packet.Packet, connScope string) (*Communication, error) {
|
||||
connection, ok := GetCommunication(process.UnknownProcess.Pid, connScope)
|
||||
if !ok {
|
||||
connection = &Communication{
|
||||
Domain: connClass,
|
||||
Scope: connScope,
|
||||
Entity: (&intel.Entity{}).Init(),
|
||||
Direction: pkt.IsInbound(),
|
||||
Verdict: VerdictDrop,
|
||||
Reason: ReasonUnknownProcess,
|
||||
|
|
29
process/config.go
Normal file
29
process/config.go
Normal 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
|
||||
}
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/profile"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
||||
|
@ -90,11 +89,7 @@ func (p *Process) Delete() {
|
|||
go dbController.PushUpdate(p)
|
||||
}
|
||||
|
||||
// deactivate profile
|
||||
// TODO: check if there is another process using the same profile set
|
||||
if p.profileSet != nil {
|
||||
profile.DeactivateProfileSet(p.profileSet)
|
||||
}
|
||||
// TODO: maybe mark the assigned profiles as no longer needed?
|
||||
}
|
||||
|
||||
// CleanProcessStorage cleans the storage from old processes.
|
||||
|
|
|
@ -56,6 +56,11 @@ func GetPidByPacket(pkt packet.Packet) (pid int, direction bool, err error) {
|
|||
|
||||
// GetProcessByPacket returns the process that owns the given packet.
|
||||
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")
|
||||
|
||||
var pid int
|
||||
|
@ -75,10 +80,9 @@ func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, er
|
|||
return nil, direction, err
|
||||
}
|
||||
|
||||
err = process.FindProfiles(pkt.Ctx())
|
||||
err = process.GetProfile(pkt.Ctx())
|
||||
if err != nil {
|
||||
log.Tracer(pkt.Ctx()).Errorf("process: failed to find profiles for process %s: %s", process, err)
|
||||
log.Errorf("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)
|
||||
}
|
||||
|
||||
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.
|
||||
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")
|
||||
|
||||
var pid int
|
||||
|
@ -129,10 +138,9 @@ func GetProcessByEndpoints(ctx context.Context, localIP net.IP, localPort uint16
|
|||
return nil, err
|
||||
}
|
||||
|
||||
err = process.FindProfiles(ctx)
|
||||
err = process.GetProfile(ctx)
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Errorf("process: failed to find profiles for process %s: %s", process, err)
|
||||
log.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)
|
||||
}
|
||||
|
||||
return process, nil
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -17,7 +17,7 @@ var (
|
|||
)
|
||||
|
||||
// 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()
|
||||
defer pidsByUserLock.Unlock()
|
||||
|
||||
|
|
|
@ -40,10 +40,10 @@ type Process struct {
|
|||
// ExecOwner ...
|
||||
// ExecSignature ...
|
||||
|
||||
UserProfileKey string
|
||||
profileSet *profile.Set
|
||||
Name string
|
||||
Icon string
|
||||
LocalProfileKey string
|
||||
profile *profile.LayeredProfile
|
||||
Name 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.
|
||||
|
||||
FirstCommEstablished int64
|
||||
|
@ -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.
|
||||
}
|
||||
|
||||
// ProfileSet returns the assigned profile set.
|
||||
func (p *Process) ProfileSet() *profile.Set {
|
||||
// Profile returns the assigned layered profile.
|
||||
func (p *Process) Profile() *profile.LayeredProfile {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
return p.profileSet
|
||||
return p.profile
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
dupReqLock.Lock()
|
||||
defer dupReqLock.Unlock()
|
||||
|
||||
// get duplicate request waitgroup
|
||||
wg, requestActive := dupReqMap[pid]
|
||||
|
||||
// someone else is already on it!
|
||||
if requestActive {
|
||||
dupReqLock.Unlock()
|
||||
|
||||
// log that we are waiting
|
||||
log.Tracer(ctx).Tracef("intel: waiting for duplicate request for PID %d to complete", pid)
|
||||
// wait
|
||||
|
@ -232,6 +233,8 @@ func deduplicateRequest(ctx context.Context, pid int) (finishRequest func()) {
|
|||
// add to registry
|
||||
dupReqMap[pid] = wg
|
||||
|
||||
dupReqLock.Unlock()
|
||||
|
||||
// return function to mark request as finished
|
||||
return func() {
|
||||
dupReqLock.Lock()
|
||||
|
|
42
process/profile.go
Normal file
42
process/profile.go
Normal 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
|
||||
}
|
Loading…
Add table
Reference in a new issue