mirror of
https://github.com/safing/portmaster
synced 2025-04-25 05:19:09 +00:00
Reevaluate and update firewall core logic
This commit is contained in:
parent
d28ed664aa
commit
f7a07cbb2f
39 changed files with 1469 additions and 915 deletions
|
@ -1,4 +1,4 @@
|
|||
package global
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/Safing/portbase/database"
|
||||
|
@ -7,11 +7,10 @@ import (
|
|||
// module dependencies
|
||||
_ "github.com/Safing/portbase/database/dbmodule"
|
||||
_ "github.com/Safing/portbase/database/storage/badger"
|
||||
_ "github.com/Safing/portmaster/status"
|
||||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("global", nil, start, nil, "database", "status")
|
||||
modules.Register("core", nil, start, nil, "database")
|
||||
}
|
||||
|
||||
func start() error {
|
|
@ -2,10 +2,13 @@ package firewall
|
|||
|
||||
import (
|
||||
"github.com/Safing/portbase/config"
|
||||
"github.com/Safing/portmaster/status"
|
||||
)
|
||||
|
||||
var (
|
||||
permanentVerdicts config.BoolOption
|
||||
permanentVerdicts config.BoolOption
|
||||
filterDNSByScope status.SecurityLevelOption
|
||||
filterDNSByProfile status.SecurityLevelOption
|
||||
)
|
||||
|
||||
func registerConfig() error {
|
||||
|
@ -22,5 +25,35 @@ func registerConfig() error {
|
|||
}
|
||||
permanentVerdicts = config.Concurrent.GetAsBool("firewall/permanentVerdicts", true)
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Filter DNS Responses by Server Scope",
|
||||
Key: "firewall/filterDNSByScope",
|
||||
Description: "This option will filter out DNS answers that are outside of the scope of the server. A server on the public Internet may not respond with a private LAN address.",
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
OptType: config.OptTypeInt,
|
||||
ExternalOptType: "security level",
|
||||
DefaultValue: 7,
|
||||
ValidationRegex: "^(7|6|4)$",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filterDNSByScope = status.ConfigIsActiveConcurrent("firewall/filterDNSByScope")
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Filter DNS Responses by Application Profile",
|
||||
Key: "firewall/filterDNSByProfile",
|
||||
Description: "This option will filter out DNS answers that an application would not be allowed to connect, based on its profile.",
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
OptType: config.OptTypeInt,
|
||||
ExternalOptType: "security level",
|
||||
DefaultValue: 7,
|
||||
ValidationRegex: "^(7|6|4)$",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filterDNSByProfile = status.ConfigIsActiveConcurrent("firewall/filterDNSByProfile")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@ import (
|
|||
"github.com/Safing/portmaster/firewall/interception"
|
||||
"github.com/Safing/portmaster/network"
|
||||
"github.com/Safing/portmaster/network/packet"
|
||||
|
||||
// module dependencies
|
||||
_ "github.com/Safing/portmaster/core"
|
||||
_ "github.com/Safing/portmaster/profile"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -37,7 +41,7 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("firewall", prep, start, stop, "global", "network", "nameserver", "profile")
|
||||
modules.Register("firewall", prep, start, stop, "core", "network", "nameserver", "profile")
|
||||
}
|
||||
|
||||
func prep() (err error) {
|
||||
|
@ -111,7 +115,7 @@ func handlePacket(pkt packet.Packet) {
|
|||
return
|
||||
}
|
||||
|
||||
// log.Debugf("firewall: pkt %s has ID %s", pkt, pkt.GetConnectionID())
|
||||
// log.Debugf("firewall: pkt %s has ID %s", pkt, pkt.GetLinkID())
|
||||
|
||||
// use this to time how long it takes process packet
|
||||
// timed := time.Now()
|
||||
|
@ -146,43 +150,51 @@ func handlePacket(pkt packet.Packet) {
|
|||
|
||||
func initialHandler(pkt packet.Packet, link *network.Link) {
|
||||
|
||||
// get Connection
|
||||
connection, err := network.GetConnectionByFirstPacket(pkt)
|
||||
// get Communication
|
||||
comm, err := network.GetCommunicationByFirstPacket(pkt)
|
||||
if err != nil {
|
||||
// get "unknown" connection
|
||||
// get "unknown" comm
|
||||
link.Deny(fmt.Sprintf("could not get process: %s", err))
|
||||
connection, err = network.GetUnknownConnection(pkt)
|
||||
comm, err = network.GetUnknownCommunication(pkt)
|
||||
|
||||
if err != nil {
|
||||
// all failed
|
||||
log.Errorf("firewall: could not get unknown connection (dropping %s): %s", pkt.String(), err)
|
||||
link.UpdateVerdict(network.DROP)
|
||||
verdict(pkt, network.DROP)
|
||||
log.Errorf("firewall: could not get unknown comm (dropping %s): %s", pkt.String(), err)
|
||||
link.UpdateVerdict(network.VerdictDrop)
|
||||
verdict(pkt, network.VerdictDrop)
|
||||
link.StopFirewallHandler()
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// add new Link to Connection (and save both)
|
||||
connection.AddLink(link)
|
||||
// add new Link to Communication (and save both)
|
||||
comm.AddLink(link)
|
||||
|
||||
// reroute dns requests to nameserver
|
||||
if connection.Process().Pid != os.Getpid() && pkt.IsOutbound() && pkt.GetTCPUDPHeader() != nil && !pkt.GetIPHeader().Dst.Equal(localhost) && pkt.GetTCPUDPHeader().DstPort == 53 {
|
||||
if comm.Process().Pid != os.Getpid() && pkt.IsOutbound() && pkt.GetTCPUDPHeader() != nil && !pkt.GetIPHeader().Dst.Equal(localhost) && pkt.GetTCPUDPHeader().DstPort == 53 {
|
||||
link.RerouteToNameserver()
|
||||
verdict(pkt, link.GetVerdict())
|
||||
link.StopFirewallHandler()
|
||||
return
|
||||
}
|
||||
|
||||
// make a decision if not made already
|
||||
if connection.GetVerdict() == network.UNDECIDED {
|
||||
DecideOnConnection(connection, pkt)
|
||||
// check if communication needs reevaluation
|
||||
if comm.NeedsReevaluation() {
|
||||
comm.ResetVerdict()
|
||||
}
|
||||
if connection.GetVerdict() == network.ACCEPT {
|
||||
DecideOnLink(connection, link, pkt)
|
||||
} else {
|
||||
link.UpdateVerdict(connection.GetVerdict())
|
||||
|
||||
// make a decision if not made already
|
||||
switch comm.GetVerdict() {
|
||||
case network.VerdictUndecided, network.VerdictUndeterminable:
|
||||
DecideOnCommunication(comm, pkt)
|
||||
}
|
||||
|
||||
switch comm.GetVerdict() {
|
||||
case network.VerdictUndecided, network.VerdictUndeterminable, network.VerdictAccept:
|
||||
DecideOnLink(comm, link, pkt)
|
||||
default:
|
||||
link.UpdateVerdict(comm.GetVerdict())
|
||||
}
|
||||
|
||||
// log decision
|
||||
|
@ -201,7 +213,7 @@ func initialHandler(pkt packet.Packet, link *network.Link) {
|
|||
// // tunnel link, don't inspect
|
||||
// link.Tunneled = true
|
||||
// link.StopFirewallHandler()
|
||||
// permanentVerdict(pkt, network.ACCEPT)
|
||||
// permanentVerdict(pkt, network.VerdictAccept)
|
||||
case link.Inspect:
|
||||
link.SetFirewallHandler(inspectThenVerdict)
|
||||
inspectThenVerdict(pkt, link)
|
||||
|
@ -241,22 +253,22 @@ func inspectThenVerdict(pkt packet.Packet, link *network.Link) {
|
|||
|
||||
func permanentVerdict(pkt packet.Packet, action network.Verdict) {
|
||||
switch action {
|
||||
case network.ACCEPT:
|
||||
case network.VerdictAccept:
|
||||
atomic.AddUint64(packetsAccepted, 1)
|
||||
pkt.PermanentAccept()
|
||||
return
|
||||
case network.BLOCK:
|
||||
case network.VerdictBlock:
|
||||
atomic.AddUint64(packetsBlocked, 1)
|
||||
pkt.PermanentBlock()
|
||||
return
|
||||
case network.DROP:
|
||||
case network.VerdictDrop:
|
||||
atomic.AddUint64(packetsDropped, 1)
|
||||
pkt.PermanentDrop()
|
||||
return
|
||||
case network.RerouteToNameserver:
|
||||
case network.VerdictRerouteToNameserver:
|
||||
pkt.RerouteToNameserver()
|
||||
return
|
||||
case network.RerouteToTunnel:
|
||||
case network.VerdictRerouteToTunnel:
|
||||
pkt.RerouteToTunnel()
|
||||
return
|
||||
}
|
||||
|
@ -265,22 +277,22 @@ func permanentVerdict(pkt packet.Packet, action network.Verdict) {
|
|||
|
||||
func verdict(pkt packet.Packet, action network.Verdict) {
|
||||
switch action {
|
||||
case network.ACCEPT:
|
||||
case network.VerdictAccept:
|
||||
atomic.AddUint64(packetsAccepted, 1)
|
||||
pkt.Accept()
|
||||
return
|
||||
case network.BLOCK:
|
||||
case network.VerdictBlock:
|
||||
atomic.AddUint64(packetsBlocked, 1)
|
||||
pkt.Block()
|
||||
return
|
||||
case network.DROP:
|
||||
case network.VerdictDrop:
|
||||
atomic.AddUint64(packetsDropped, 1)
|
||||
pkt.Drop()
|
||||
return
|
||||
case network.RerouteToNameserver:
|
||||
case network.VerdictRerouteToNameserver:
|
||||
pkt.RerouteToNameserver()
|
||||
return
|
||||
case network.RerouteToTunnel:
|
||||
case network.VerdictRerouteToTunnel:
|
||||
pkt.RerouteToTunnel()
|
||||
return
|
||||
}
|
||||
|
@ -302,26 +314,26 @@ func verdict(pkt packet.Packet, action network.Verdict) {
|
|||
|
||||
func logInitialVerdict(link *network.Link) {
|
||||
// switch link.GetVerdict() {
|
||||
// case network.ACCEPT:
|
||||
// case network.VerdictAccept:
|
||||
// log.Infof("firewall: accepting new link: %s", link.String())
|
||||
// case network.BLOCK:
|
||||
// case network.VerdictBlock:
|
||||
// log.Infof("firewall: blocking new link: %s", link.String())
|
||||
// case network.DROP:
|
||||
// case network.VerdictDrop:
|
||||
// log.Infof("firewall: dropping new link: %s", link.String())
|
||||
// case network.RerouteToNameserver:
|
||||
// case network.VerdictRerouteToNameserver:
|
||||
// log.Infof("firewall: rerouting new link to nameserver: %s", link.String())
|
||||
// case network.RerouteToTunnel:
|
||||
// case network.VerdictRerouteToTunnel:
|
||||
// log.Infof("firewall: rerouting new link to tunnel: %s", link.String())
|
||||
// }
|
||||
}
|
||||
|
||||
func logChangedVerdict(link *network.Link) {
|
||||
// switch link.GetVerdict() {
|
||||
// case network.ACCEPT:
|
||||
// case network.VerdictAccept:
|
||||
// log.Infof("firewall: change! - now accepting link: %s", link.String())
|
||||
// case network.BLOCK:
|
||||
// case network.VerdictBlock:
|
||||
// log.Infof("firewall: change! - now blocking link: %s", link.String())
|
||||
// case network.DROP:
|
||||
// case network.VerdictDrop:
|
||||
// log.Infof("firewall: change! - now dropping link: %s", link.String())
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func RunInspectors(pkt packet.Packet, link *network.Link) (network.Verdict, bool
|
|||
}
|
||||
|
||||
continueInspection := false
|
||||
verdict := network.UNDECIDED
|
||||
verdict := network.VerdictUndecided
|
||||
|
||||
for key, skip := range activeInspectors {
|
||||
|
||||
|
@ -69,28 +69,28 @@ func RunInspectors(pkt packet.Packet, link *network.Link) (network.Verdict, bool
|
|||
action := inspectors[key](pkt, link)
|
||||
switch action {
|
||||
case DO_NOTHING:
|
||||
if verdict < network.ACCEPT {
|
||||
verdict = network.ACCEPT
|
||||
if verdict < network.VerdictAccept {
|
||||
verdict = network.VerdictAccept
|
||||
}
|
||||
continueInspection = true
|
||||
case BLOCK_PACKET:
|
||||
if verdict < network.BLOCK {
|
||||
verdict = network.BLOCK
|
||||
if verdict < network.VerdictBlock {
|
||||
verdict = network.VerdictBlock
|
||||
}
|
||||
continueInspection = true
|
||||
case DROP_PACKET:
|
||||
verdict = network.DROP
|
||||
verdict = network.VerdictDrop
|
||||
continueInspection = true
|
||||
case BLOCK_LINK:
|
||||
link.UpdateVerdict(network.BLOCK)
|
||||
link.UpdateVerdict(network.VerdictBlock)
|
||||
activeInspectors[key] = true
|
||||
if verdict < network.BLOCK {
|
||||
verdict = network.BLOCK
|
||||
if verdict < network.VerdictBlock {
|
||||
verdict = network.VerdictBlock
|
||||
}
|
||||
case DROP_LINK:
|
||||
link.UpdateVerdict(network.DROP)
|
||||
link.UpdateVerdict(network.VerdictDrop)
|
||||
activeInspectors[key] = true
|
||||
verdict = network.DROP
|
||||
verdict = network.VerdictDrop
|
||||
case STOP_INSPECTING:
|
||||
activeInspectors[key] = true
|
||||
}
|
||||
|
|
|
@ -2,85 +2,89 @@ package firewall
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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/profile"
|
||||
"github.com/Safing/portmaster/status"
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"github.com/agext/levenshtein"
|
||||
)
|
||||
|
||||
// Call order:
|
||||
//
|
||||
// 1. DecideOnConnectionBeforeIntel (if connecting to domain)
|
||||
// 1. DecideOnCommunicationBeforeIntel (if connecting to domain)
|
||||
// is called when a DNS query is made, before the query is resolved
|
||||
// 2. DecideOnConnectionAfterIntel (if connecting to domain)
|
||||
// 2. DecideOnCommunicationAfterIntel (if connecting to domain)
|
||||
// is called when a DNS query is made, after the query is resolved
|
||||
// 3. DecideOnConnection
|
||||
// is called when the first packet of the first link of the connection arrives
|
||||
// 3. DecideOnCommunication
|
||||
// is called when the first packet of the first link of the communication arrives
|
||||
// 4. DecideOnLink
|
||||
// is called when when the first packet of a link arrives only if connection has verdict UNDECIDED or CANTSAY
|
||||
// is called when when the first packet of a link arrives only if communication has verdict UNDECIDED or CANTSAY
|
||||
|
||||
// DecideOnConnectionBeforeIntel makes a decision about a connection before the dns query is resolved and intel is gathered.
|
||||
func DecideOnConnectionBeforeIntel(connection *network.Connection, fqdn string) {
|
||||
// check:
|
||||
// Profile.DomainWhitelist
|
||||
// Profile.Flags
|
||||
// - process specific: System, Admin, User
|
||||
// - network specific: Internet, LocalNet
|
||||
// DecideOnCommunicationBeforeIntel makes a decision about a communication before the dns query is resolved and intel is gathered.
|
||||
func DecideOnCommunicationBeforeIntel(comm *network.Communication, fqdn string) {
|
||||
|
||||
// grant self
|
||||
if connection.Process().Pid == os.Getpid() {
|
||||
log.Infof("firewall: granting own connection %s", connection)
|
||||
connection.Accept("")
|
||||
if comm.Process().Pid == os.Getpid() {
|
||||
log.Infof("firewall: granting own communication %s", comm)
|
||||
comm.Accept("")
|
||||
return
|
||||
}
|
||||
|
||||
// check if there is a profile
|
||||
profileSet := connection.Process().ProfileSet()
|
||||
// get and check profile set
|
||||
profileSet := comm.Process().ProfileSet()
|
||||
if profileSet == nil {
|
||||
log.Errorf("firewall: denying connection %s, no Profile Set", connection)
|
||||
connection.Deny("no Profile Set")
|
||||
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
|
||||
if !profileSet.CheckFlag(profile.Internet) && !profileSet.CheckFlag(profile.LAN) {
|
||||
log.Infof("firewall: denying connection %s, accessing Internet or LAN not allowed", connection)
|
||||
connection.Deny("accessing Internet or LAN not allowed")
|
||||
log.Infof("firewall: denying communication %s, accessing Internet or LAN not permitted", comm)
|
||||
comm.Deny("accessing Internet or LAN not permitted")
|
||||
return
|
||||
}
|
||||
|
||||
// check domain list
|
||||
permitted, reason, ok := profileSet.CheckEndpoint(fqdn, 0, 0, false)
|
||||
if ok {
|
||||
if permitted {
|
||||
log.Infof("firewall: accepting connection %s, endpoint is whitelisted: %s", connection, reason)
|
||||
connection.Accept(fmt.Sprintf("endpoint is whitelisted: %s", reason))
|
||||
} else {
|
||||
log.Infof("firewall: denying connection %s, endpoint is blacklisted: %s", connection, reason)
|
||||
connection.Deny(fmt.Sprintf("endpoint is blacklisted: %s", reason))
|
||||
}
|
||||
// check endpoint list
|
||||
result, reason := profileSet.CheckEndpointDomain(fqdn)
|
||||
switch result {
|
||||
case profile.NoMatch:
|
||||
comm.UpdateVerdict(network.VerdictUndecided)
|
||||
case profile.Undeterminable:
|
||||
comm.UpdateVerdict(network.VerdictUndeterminable)
|
||||
return
|
||||
case profile.Denied:
|
||||
log.Infof("firewall: denying communication %s, endpoint is blacklisted: %s", comm, reason)
|
||||
comm.Deny(fmt.Sprintf("endpoint is blacklisted: %s", reason))
|
||||
return
|
||||
case profile.Permitted:
|
||||
log.Infof("firewall: permitting communication %s, endpoint is whitelisted: %s", comm, reason)
|
||||
comm.Accept(fmt.Sprintf("endpoint is whitelisted: %s", reason))
|
||||
return
|
||||
}
|
||||
|
||||
switch profileSet.GetProfileMode() {
|
||||
case profile.Whitelist:
|
||||
log.Infof("firewall: denying connection %s, domain is not whitelisted", connection)
|
||||
connection.Deny("domain is not whitelisted")
|
||||
log.Infof("firewall: denying communication %s, domain is not whitelisted", comm)
|
||||
comm.Deny("domain is not whitelisted")
|
||||
return
|
||||
case profile.Prompt:
|
||||
|
||||
// check Related flag
|
||||
// TODO: improve this!
|
||||
if profileSet.CheckFlag(profile.Related) {
|
||||
matched := false
|
||||
pathElements := strings.Split(connection.Process().Path, "/") // FIXME: path seperator
|
||||
pathElements := strings.Split(comm.Process().Path, "/") // FIXME: path seperator
|
||||
// only look at the last two path segments
|
||||
if len(pathElements) > 2 {
|
||||
pathElements = pathElements[len(pathElements)-2:]
|
||||
|
@ -104,167 +108,285 @@ func DecideOnConnectionBeforeIntel(connection *network.Connection, fqdn string)
|
|||
processElement = profileSet.UserProfile().Name
|
||||
break matchLoop
|
||||
}
|
||||
if levenshtein.Match(domainElement, connection.Process().Name, nil) > 0.5 {
|
||||
if levenshtein.Match(domainElement, comm.Process().Name, nil) > 0.5 {
|
||||
matched = true
|
||||
processElement = connection.Process().Name
|
||||
processElement = comm.Process().Name
|
||||
break matchLoop
|
||||
}
|
||||
if levenshtein.Match(domainElement, connection.Process().ExecName, nil) > 0.5 {
|
||||
if levenshtein.Match(domainElement, comm.Process().ExecName, nil) > 0.5 {
|
||||
matched = true
|
||||
processElement = connection.Process().ExecName
|
||||
processElement = comm.Process().ExecName
|
||||
break matchLoop
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
log.Infof("firewall: accepting connection %s, match to domain was found: %s ~== %s", connection, domainElement, processElement)
|
||||
connection.Accept("domain is related to process")
|
||||
log.Infof("firewall: permitting communication %s, match to domain was found: %s ~== %s", comm, domainElement, processElement)
|
||||
comm.Accept("domain is related to process")
|
||||
}
|
||||
}
|
||||
|
||||
if connection.GetVerdict() != network.ACCEPT {
|
||||
if comm.GetVerdict() != network.VerdictAccept {
|
||||
// TODO
|
||||
log.Infof("firewall: accepting connection %s, domain permitted (prompting is not yet implemented)", connection)
|
||||
connection.Accept("domain permitted (prompting is not yet implemented)")
|
||||
log.Infof("firewall: permitting communication %s, domain permitted (prompting is not yet implemented)", comm)
|
||||
comm.Accept("domain permitted (prompting is not yet implemented)")
|
||||
}
|
||||
|
||||
return
|
||||
case profile.Blacklist:
|
||||
log.Infof("firewall: accepting connection %s, domain is not blacklisted", connection)
|
||||
connection.Accept("domain is not blacklisted")
|
||||
log.Infof("firewall: permitting communication %s, domain is not blacklisted", comm)
|
||||
comm.Accept("domain is not blacklisted")
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("firewall: denying communication %s, no profile mode set", comm)
|
||||
comm.Deny("no profile mode set")
|
||||
}
|
||||
|
||||
// DecideOnConnectionAfterIntel makes a decision about a connection after the dns query is resolved and intel is gathered.
|
||||
func DecideOnConnectionAfterIntel(connection *network.Connection, fqdn string, rrCache *intel.RRCache) *intel.RRCache {
|
||||
// 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) {
|
||||
|
||||
// grant self
|
||||
if connection.Process().Pid == os.Getpid() {
|
||||
log.Infof("firewall: granting own connection %s", connection)
|
||||
connection.Accept("")
|
||||
// SUSPENDED until Stamp integration is finished
|
||||
|
||||
// grant self - should not get here
|
||||
// if comm.Process().Pid == os.Getpid() {
|
||||
// log.Infof("firewall: granting own communication %s", comm)
|
||||
// comm.Accept("")
|
||||
// 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
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// FilterDNSResponse filters a dns response according to the application profile and settings.
|
||||
func FilterDNSResponse(comm *network.Communication, fqdn string, rrCache *intel.RRCache) *intel.RRCache {
|
||||
// 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 := connection.Process().ProfileSet()
|
||||
profileSet := comm.Process().ProfileSet()
|
||||
if profileSet == nil {
|
||||
log.Errorf("firewall: denying connection %s, no Profile Set", connection)
|
||||
connection.Deny("no Profile Set")
|
||||
return rrCache
|
||||
log.Infof("firewall: blocking dns query of communication %s, no Profile Set", comm)
|
||||
return nil
|
||||
}
|
||||
profileSet.Update(status.ActiveSecurityLevel())
|
||||
|
||||
// TODO: Stamp integration
|
||||
// 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
|
||||
if !filterByScope && !filterByProfile {
|
||||
return rrCache
|
||||
}
|
||||
|
||||
// duplicate entry
|
||||
rrCache = rrCache.ShallowCopy()
|
||||
rrCache.FilteredEntries = make([]string, 0)
|
||||
|
||||
// change information
|
||||
var addressesRemoved int
|
||||
var addressesOk int
|
||||
|
||||
// loop vars
|
||||
var classification int8
|
||||
var ip net.IP
|
||||
var result profile.EPResult
|
||||
|
||||
// filter function
|
||||
filterEntries := func(entries []dns.RR) (goodEntries []dns.RR) {
|
||||
goodEntries = make([]dns.RR, 0, len(entries))
|
||||
|
||||
for _, rr := range entries {
|
||||
|
||||
// get IP and classification
|
||||
switch v := rr.(type) {
|
||||
case *dns.A:
|
||||
ip = v.A
|
||||
case *dns.AAAA:
|
||||
ip = v.AAAA
|
||||
default:
|
||||
// add non A/AAAA entries
|
||||
goodEntries = append(goodEntries, rr)
|
||||
continue
|
||||
}
|
||||
classification = netutils.ClassifyIP(ip)
|
||||
|
||||
if filterByScope {
|
||||
switch {
|
||||
case classification == netutils.HostLocal:
|
||||
// No DNS should return localhost addresses
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
case rrCache.ServerScope == netutils.Global && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
|
||||
// No global DNS should return LAN addresses
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if filterByProfile {
|
||||
// filter by flags
|
||||
switch {
|
||||
case !profileSet.CheckFlag(profile.Internet) && classification == netutils.Global:
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
case !profileSet.CheckFlag(profile.LAN) && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
case !profileSet.CheckFlag(profile.Localhost) && classification == netutils.HostLocal:
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
}
|
||||
|
||||
// filter by endpoints
|
||||
result, _ = profileSet.CheckEndpointIP("", ip, 0, 0, false)
|
||||
if result == profile.Denied {
|
||||
addressesRemoved++
|
||||
rrCache.FilteredEntries = append(rrCache.FilteredEntries, rr.String())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// if survived, add to good entries
|
||||
addressesOk++
|
||||
goodEntries = append(goodEntries, rr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
rrCache.Answer = filterEntries(rrCache.Answer)
|
||||
rrCache.Extra = filterEntries(rrCache.Extra)
|
||||
|
||||
if addressesRemoved > 0 {
|
||||
rrCache.Filtered = true
|
||||
if addressesOk == 0 {
|
||||
comm.Deny("no addresses returned for this domain are permitted")
|
||||
log.Infof("firewall: fully dns responses for communication %s", comm)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if rrCache.Filtered {
|
||||
log.Infof("firewall: filtered DNS replies for %s: %s", comm, strings.Join(rrCache.FilteredEntries, ", "))
|
||||
}
|
||||
|
||||
// TODO: Gate17 integration
|
||||
// tunnelInfo, err := AssignTunnelIP(fqdn)
|
||||
|
||||
rrCache.Duplicate().FilterEntries(profileSet.CheckFlag(profile.Internet), profileSet.CheckFlag(profile.LAN), false)
|
||||
if len(rrCache.Answer) == 0 {
|
||||
if profileSet.CheckFlag(profile.Internet) {
|
||||
connection.Deny("server is located in the LAN, but LAN access is not permitted")
|
||||
} else {
|
||||
connection.Deny("server is located in the Internet, but Internet access is not permitted")
|
||||
}
|
||||
}
|
||||
|
||||
return rrCache
|
||||
}
|
||||
|
||||
// DeciceOnConnection makes a decision about a connection with its first packet.
|
||||
func DecideOnConnection(connection *network.Connection, pkt packet.Packet) {
|
||||
// DecideOnCommunication makes a decision about a communication with its first packet.
|
||||
func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) {
|
||||
|
||||
// grant self
|
||||
if connection.Process().Pid == os.Getpid() {
|
||||
log.Infof("firewall: granting own connection %s", connection)
|
||||
connection.Accept("")
|
||||
if comm.Process().Pid == os.Getpid() {
|
||||
log.Infof("firewall: granting own communication %s", comm)
|
||||
comm.Accept("")
|
||||
return
|
||||
}
|
||||
|
||||
// check if there is a profile
|
||||
profileSet := connection.Process().ProfileSet()
|
||||
profileSet := comm.Process().ProfileSet()
|
||||
if profileSet == nil {
|
||||
log.Errorf("firewall: denying connection %s, no Profile Set", connection)
|
||||
connection.Deny("no Profile Set")
|
||||
log.Errorf("firewall: denying communication %s, no Profile Set", comm)
|
||||
comm.Deny("no Profile Set")
|
||||
return
|
||||
}
|
||||
profileSet.Update(status.ActiveSecurityLevel())
|
||||
|
||||
// check connection type
|
||||
switch connection.Domain {
|
||||
// check comm type
|
||||
switch comm.Domain {
|
||||
case network.IncomingHost, network.IncomingLAN, network.IncomingInternet, network.IncomingInvalid:
|
||||
if !profileSet.CheckFlag(profile.Service) {
|
||||
log.Infof("firewall: denying connection %s, not a service", connection)
|
||||
if connection.Domain == network.IncomingHost {
|
||||
connection.Block("not a service")
|
||||
log.Infof("firewall: denying communication %s, not a service", comm)
|
||||
if comm.Domain == network.IncomingHost {
|
||||
comm.Block("not a service")
|
||||
} else {
|
||||
connection.Drop("not a service")
|
||||
comm.Drop("not a service")
|
||||
}
|
||||
return
|
||||
}
|
||||
case network.PeerLAN, network.PeerInternet, network.PeerInvalid: // Important: PeerHost is and should be missing!
|
||||
if !profileSet.CheckFlag(profile.PeerToPeer) {
|
||||
log.Infof("firewall: denying connection %s, peer to peer connections (to an IP) not allowed", connection)
|
||||
connection.Deny("peer to peer connections (to an IP) not allowed")
|
||||
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
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
// check network scope
|
||||
switch connection.Domain {
|
||||
switch comm.Domain {
|
||||
case network.IncomingHost:
|
||||
if !profileSet.CheckFlag(profile.Localhost) {
|
||||
log.Infof("firewall: denying connection %s, serving localhost not allowed", connection)
|
||||
connection.Block("serving localhost not allowed")
|
||||
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) {
|
||||
log.Infof("firewall: denying connection %s, serving LAN not allowed", connection)
|
||||
connection.Deny("serving LAN not allowed")
|
||||
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) {
|
||||
log.Infof("firewall: denying connection %s, serving Internet not allowed", connection)
|
||||
connection.Deny("serving Internet not allowed")
|
||||
log.Infof("firewall: denying communication %s, serving Internet not allowed", comm)
|
||||
comm.Deny("serving Internet not allowed")
|
||||
return
|
||||
}
|
||||
case network.IncomingInvalid:
|
||||
log.Infof("firewall: denying connection %s, invalid IP address", connection)
|
||||
connection.Drop("invalid IP address")
|
||||
log.Infof("firewall: denying communication %s, invalid IP address", comm)
|
||||
comm.Drop("invalid IP address")
|
||||
return
|
||||
case network.PeerHost:
|
||||
if !profileSet.CheckFlag(profile.Localhost) {
|
||||
log.Infof("firewall: denying connection %s, accessing localhost not allowed", connection)
|
||||
connection.Block("accessing localhost not allowed")
|
||||
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) {
|
||||
log.Infof("firewall: denying connection %s, accessing the LAN not allowed", connection)
|
||||
connection.Deny("accessing the LAN not allowed")
|
||||
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) {
|
||||
log.Infof("firewall: denying connection %s, accessing the Internet not allowed", connection)
|
||||
connection.Deny("accessing the Internet not allowed")
|
||||
log.Infof("firewall: denying communication %s, accessing the Internet not allowed", comm)
|
||||
comm.Deny("accessing the Internet not allowed")
|
||||
return
|
||||
}
|
||||
case network.PeerInvalid:
|
||||
log.Infof("firewall: denying connection %s, invalid IP address", connection)
|
||||
connection.Deny("invalid IP address")
|
||||
log.Infof("firewall: denying communication %s, invalid IP address", comm)
|
||||
comm.Deny("invalid IP address")
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("firewall: accepting connection %s", connection)
|
||||
connection.Accept("")
|
||||
log.Infof("firewall: undeterminable verdict for communication %s", comm)
|
||||
}
|
||||
|
||||
// DecideOnLink makes a decision about a link with the first packet.
|
||||
func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet.Packet) {
|
||||
func DecideOnLink(comm *network.Communication, link *network.Link, pkt packet.Packet) {
|
||||
// check:
|
||||
// Profile.Flags
|
||||
// - network specific: Internet, LocalNet
|
||||
|
@ -272,34 +394,37 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
|
|||
// Profile.ListenPorts
|
||||
|
||||
// grant self
|
||||
if connection.Process().Pid == os.Getpid() {
|
||||
log.Infof("firewall: granting own link %s", connection)
|
||||
connection.Accept("")
|
||||
if comm.Process().Pid == os.Getpid() {
|
||||
log.Infof("firewall: granting own link %s", comm)
|
||||
link.Accept("")
|
||||
return
|
||||
}
|
||||
|
||||
// check if there is a profile
|
||||
profileSet := connection.Process().ProfileSet()
|
||||
profileSet := comm.Process().ProfileSet()
|
||||
if profileSet == nil {
|
||||
log.Infof("firewall: no Profile Set, denying %s", link)
|
||||
link.Block("no Profile Set")
|
||||
link.Deny("no Profile Set")
|
||||
return
|
||||
}
|
||||
profileSet.Update(status.ActiveSecurityLevel())
|
||||
|
||||
// get host
|
||||
var domainOrIP string
|
||||
switch {
|
||||
case strings.HasSuffix(connection.Domain, "."):
|
||||
domainOrIP = connection.Domain
|
||||
case connection.Direction:
|
||||
domainOrIP = pkt.GetIPHeader().Src.String()
|
||||
default:
|
||||
domainOrIP = pkt.GetIPHeader().Dst.String()
|
||||
// get domain
|
||||
var domain string
|
||||
if strings.HasSuffix(comm.Domain, ".") {
|
||||
domain = comm.Domain
|
||||
}
|
||||
|
||||
// get protocol / destination port
|
||||
protocol := pkt.GetIPHeader().Protocol
|
||||
// remoteIP
|
||||
var remoteIP net.IP
|
||||
if comm.Direction {
|
||||
remoteIP = pkt.GetIPHeader().Src
|
||||
} else {
|
||||
remoteIP = pkt.GetIPHeader().Dst
|
||||
}
|
||||
|
||||
// protocol and destination port
|
||||
protocol := uint8(pkt.GetIPHeader().Protocol)
|
||||
var dstPort uint16
|
||||
tcpUDPHeader := pkt.GetTCPUDPHeader()
|
||||
if tcpUDPHeader != nil {
|
||||
|
@ -307,15 +432,17 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
|
|||
}
|
||||
|
||||
// check endpoints list
|
||||
permitted, reason, ok := profileSet.CheckEndpoint(domainOrIP, uint8(protocol), dstPort, connection.Direction)
|
||||
if ok {
|
||||
if permitted {
|
||||
log.Infof("firewall: accepting link %s, endpoint is whitelisted: %s", link, reason)
|
||||
link.Accept(fmt.Sprintf("port whitelisted: %s", reason))
|
||||
} else {
|
||||
log.Infof("firewall: denying link %s: endpoint is blacklisted: %s", link, reason)
|
||||
link.Deny("port blacklisted")
|
||||
}
|
||||
result, reason := profileSet.CheckEndpointIP(domain, remoteIP, protocol, dstPort, comm.Direction)
|
||||
switch result {
|
||||
// case profile.NoMatch, profile.Undeterminable:
|
||||
// continue
|
||||
case profile.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:
|
||||
log.Infof("firewall: permitting link %s, endpoint is whitelisted: %s", link, reason)
|
||||
link.Accept(fmt.Sprintf("endpoint is whitelisted: %s", reason))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -325,15 +452,15 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
|
|||
link.Deny("endpoint is not whitelisted")
|
||||
return
|
||||
case profile.Prompt:
|
||||
log.Infof("firewall: accepting link %s: endpoint is not blacklisted (prompting is not yet implemented)", link)
|
||||
log.Infof("firewall: permitting link %s: endpoint is not blacklisted (prompting is not yet implemented)", link)
|
||||
link.Accept("endpoint is not blacklisted (prompting is not yet implemented)")
|
||||
return
|
||||
case profile.Blacklist:
|
||||
log.Infof("firewall: accepting link %s: endpoint is not blacklisted", link)
|
||||
log.Infof("firewall: permitting link %s: endpoint is not blacklisted", link)
|
||||
link.Accept("endpoint is not blacklisted")
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("firewall: accepting link %s", link)
|
||||
link.Accept("")
|
||||
log.Infof("firewall: denying link %s, no profile mode set", link)
|
||||
link.Deny("no profile mode set")
|
||||
}
|
||||
|
|
|
@ -58,8 +58,8 @@ func prep() error {
|
|||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
OptType: config.OptTypeInt,
|
||||
ExternalOptType: "security level",
|
||||
DefaultValue: 3,
|
||||
ValidationRegex: "^(1|2|3)$",
|
||||
DefaultValue: 7,
|
||||
ValidationRegex: "^(7|6|4)$",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -73,8 +73,8 @@ func prep() error {
|
|||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
OptType: config.OptTypeInt,
|
||||
ExternalOptType: "security level",
|
||||
DefaultValue: 3,
|
||||
ValidationRegex: "^(1|2|3)$",
|
||||
DefaultValue: 7,
|
||||
ValidationRegex: "^(7|6|4)$",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -88,8 +88,8 @@ func prep() error {
|
|||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
OptType: config.OptTypeInt,
|
||||
ExternalOptType: "security level",
|
||||
DefaultValue: 3,
|
||||
ValidationRegex: "^(1|2|3)$",
|
||||
DefaultValue: 7,
|
||||
ValidationRegex: "^(7|6|4)$",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -7,11 +7,11 @@ import (
|
|||
"github.com/Safing/portbase/modules"
|
||||
|
||||
// module dependencies
|
||||
_ "github.com/Safing/portmaster/global"
|
||||
_ "github.com/Safing/portmaster/core"
|
||||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("intel", prep, start, nil, "global")
|
||||
modules.Register("intel", prep, start, nil, "core")
|
||||
}
|
||||
|
||||
func start() error {
|
||||
|
|
|
@ -27,7 +27,9 @@ type NameRecord struct {
|
|||
Ns []string
|
||||
Extra []string
|
||||
TTL int64
|
||||
Filtered bool
|
||||
|
||||
Server string
|
||||
ServerScope int8
|
||||
}
|
||||
|
||||
func makeNameRecordKey(domain string, question string) string {
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
|
||||
"github.com/Safing/portbase/database"
|
||||
"github.com/Safing/portbase/log"
|
||||
"github.com/Safing/portmaster/network/netutils"
|
||||
"github.com/Safing/portmaster/status"
|
||||
)
|
||||
|
||||
|
@ -304,13 +303,6 @@ func tryResolver(resolver *Resolver, lastFailBoundary int64, fqdn string, qtype
|
|||
}
|
||||
resolver.Initialized.SetToIf(false, true)
|
||||
|
||||
// remove localhost entries, remove LAN entries if server is in global IP space.
|
||||
if resolver.ServerIPScope == netutils.Global {
|
||||
rrCache.FilterEntries(true, false, false)
|
||||
} else {
|
||||
rrCache.FilterEntries(true, true, false)
|
||||
}
|
||||
|
||||
return rrCache, true
|
||||
}
|
||||
|
||||
|
@ -357,11 +349,13 @@ func query(resolver *Resolver, fqdn string, qtype dns.Type) (*RRCache, error) {
|
|||
}
|
||||
|
||||
new := &RRCache{
|
||||
Domain: fqdn,
|
||||
Question: qtype,
|
||||
Answer: reply.Answer,
|
||||
Ns: reply.Ns,
|
||||
Extra: reply.Extra,
|
||||
Domain: fqdn,
|
||||
Question: qtype,
|
||||
Answer: reply.Answer,
|
||||
Ns: reply.Ns,
|
||||
Extra: reply.Extra,
|
||||
Server: resolver.Server,
|
||||
ServerScope: resolver.ServerIPScope,
|
||||
}
|
||||
|
||||
// TODO: check if reply.Answer is valid
|
||||
|
|
104
intel/rrcache.go
104
intel/rrcache.go
|
@ -5,11 +5,8 @@ package intel
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Safing/portbase/log"
|
||||
"github.com/Safing/portmaster/network/netutils"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
|
@ -23,10 +20,14 @@ type RRCache struct {
|
|||
Extra []dns.RR
|
||||
TTL int64
|
||||
|
||||
Server string
|
||||
ServerScope int8
|
||||
|
||||
updated int64
|
||||
servedFromCache bool
|
||||
requestingNew bool
|
||||
Filtered bool
|
||||
FilteredEntries []string
|
||||
}
|
||||
|
||||
// Clean sets all TTLs to 17 and sets cache expiry with specified minimum.
|
||||
|
@ -79,10 +80,11 @@ func (m *RRCache) ExportAllARecords() (ips []net.IP) {
|
|||
// ToNameRecord converts the RRCache to a NameRecord for cleaner persistence.
|
||||
func (m *RRCache) ToNameRecord() *NameRecord {
|
||||
new := &NameRecord{
|
||||
Domain: m.Domain,
|
||||
Question: m.Question.String(),
|
||||
TTL: m.TTL,
|
||||
Filtered: m.Filtered,
|
||||
Domain: m.Domain,
|
||||
Question: m.Question.String(),
|
||||
TTL: m.TTL,
|
||||
Server: m.Server,
|
||||
ServerScope: m.ServerScope,
|
||||
}
|
||||
|
||||
// stringify RR entries
|
||||
|
@ -136,7 +138,8 @@ func GetRRCache(domain string, question dns.Type) (*RRCache, error) {
|
|||
}
|
||||
}
|
||||
|
||||
rrCache.Filtered = nameRecord.Filtered
|
||||
rrCache.Server = nameRecord.Server
|
||||
rrCache.ServerScope = nameRecord.ServerScope
|
||||
rrCache.servedFromCache = true
|
||||
return rrCache, nil
|
||||
}
|
||||
|
@ -175,82 +178,23 @@ func (m *RRCache) IsNXDomain() bool {
|
|||
return len(m.Answer) == 0
|
||||
}
|
||||
|
||||
// Duplicate returns a duplicate of the cache. slices are not copied, but referenced.
|
||||
func (m *RRCache) Duplicate() *RRCache {
|
||||
// ShallowCopy returns a shallow copy of the cache. slices are not copied, but referenced.
|
||||
func (m *RRCache) ShallowCopy() *RRCache {
|
||||
return &RRCache{
|
||||
Domain: m.Domain,
|
||||
Question: m.Question,
|
||||
Answer: m.Answer,
|
||||
Ns: m.Ns,
|
||||
Extra: m.Extra,
|
||||
TTL: m.TTL,
|
||||
Domain: m.Domain,
|
||||
Question: m.Question,
|
||||
Answer: m.Answer,
|
||||
Ns: m.Ns,
|
||||
Extra: m.Extra,
|
||||
TTL: m.TTL,
|
||||
|
||||
Server: m.Server,
|
||||
ServerScope: m.ServerScope,
|
||||
|
||||
updated: m.updated,
|
||||
servedFromCache: m.servedFromCache,
|
||||
requestingNew: m.requestingNew,
|
||||
Filtered: m.Filtered,
|
||||
FilteredEntries: m.FilteredEntries,
|
||||
}
|
||||
}
|
||||
|
||||
// FilterEntries filters resource records according to the given permission scope.
|
||||
func (m *RRCache) FilterEntries(internet, lan, host bool) {
|
||||
var filtered bool
|
||||
|
||||
m.Answer, filtered = filterEntries(m, m.Answer, internet, lan, host)
|
||||
if filtered {
|
||||
m.Filtered = true
|
||||
}
|
||||
m.Extra, filtered = filterEntries(m, m.Extra, internet, lan, host)
|
||||
if filtered {
|
||||
m.Filtered = true
|
||||
}
|
||||
}
|
||||
|
||||
func filterEntries(m *RRCache, entries []dns.RR, internet, lan, host bool) (filteredEntries []dns.RR, filtered bool) {
|
||||
filteredEntries = make([]dns.RR, 0, len(entries))
|
||||
var classification int8
|
||||
var deletedEntries []string
|
||||
|
||||
entryLoop:
|
||||
for _, rr := range entries {
|
||||
|
||||
classification = -1
|
||||
switch v := rr.(type) {
|
||||
case *dns.A:
|
||||
classification = netutils.ClassifyIP(v.A)
|
||||
case *dns.AAAA:
|
||||
classification = netutils.ClassifyIP(v.AAAA)
|
||||
}
|
||||
|
||||
if classification >= 0 {
|
||||
switch {
|
||||
case !internet && classification == netutils.Global:
|
||||
filtered = true
|
||||
deletedEntries = append(deletedEntries, rr.String())
|
||||
continue entryLoop
|
||||
case !lan && (classification == netutils.SiteLocal || classification == netutils.LinkLocal):
|
||||
filtered = true
|
||||
deletedEntries = append(deletedEntries, rr.String())
|
||||
continue entryLoop
|
||||
case !host && classification == netutils.HostLocal:
|
||||
filtered = true
|
||||
deletedEntries = append(deletedEntries, rr.String())
|
||||
continue entryLoop
|
||||
}
|
||||
}
|
||||
|
||||
filteredEntries = append(filteredEntries, rr)
|
||||
}
|
||||
|
||||
if len(deletedEntries) > 0 {
|
||||
log.Infof("intel: filtered DNS replies for %s%s: %s (Settings: Int=%v LAN=%v Host=%v)",
|
||||
m.Domain,
|
||||
m.Question.String(),
|
||||
strings.Join(deletedEntries, ", "),
|
||||
internet,
|
||||
lan,
|
||||
host,
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
5
main.go
5
main.go
|
@ -14,10 +14,7 @@ import (
|
|||
"github.com/Safing/portbase/modules"
|
||||
|
||||
// include packages here
|
||||
|
||||
_ "github.com/Safing/portbase/api"
|
||||
_ "github.com/Safing/portbase/database/dbmodule"
|
||||
_ "github.com/Safing/portbase/database/storage/badger"
|
||||
_ "github.com/Safing/portmaster/core"
|
||||
_ "github.com/Safing/portmaster/firewall"
|
||||
_ "github.com/Safing/portmaster/nameserver"
|
||||
_ "github.com/Safing/portmaster/ui"
|
||||
|
|
|
@ -126,8 +126,8 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
|||
|
||||
// get connection
|
||||
// start = time.Now()
|
||||
connection, err := network.GetConnectionByDNSRequest(rAddr.IP, uint16(rAddr.Port), fqdn)
|
||||
// log.Tracef("nameserver: took %s to get connection (and maybe process)", time.Since(start))
|
||||
comm, err := network.GetCommunicationByDNSRequest(rAddr.IP, uint16(rAddr.Port), fqdn)
|
||||
// log.Tracef("nameserver: took %s to get comms (and maybe process)", time.Since(start))
|
||||
if err != nil {
|
||||
log.Warningf("nameserver: someone is requesting %s, but could not identify process: %s, returning nxdomain", fqdn, err)
|
||||
nxDomain(w, query)
|
||||
|
@ -137,39 +137,51 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
|||
// [2/2] use this to time how long it takes to get process info
|
||||
// log.Tracef("nameserver: took %s to get connection/process of %s request", time.Now().Sub(timed).String(), fqdn)
|
||||
|
||||
// check if communication needs reevaluation
|
||||
if comm.NeedsReevaluation() {
|
||||
comm.ResetVerdict()
|
||||
}
|
||||
|
||||
// check profile before we even get intel and rr
|
||||
if connection.GetVerdict() == network.UNDECIDED {
|
||||
if comm.GetVerdict() == network.VerdictUndecided || comm.GetVerdict() == network.VerdictUndeterminable {
|
||||
// start = time.Now()
|
||||
firewall.DecideOnConnectionBeforeIntel(connection, fqdn)
|
||||
firewall.DecideOnCommunicationBeforeIntel(comm, fqdn)
|
||||
// log.Tracef("nameserver: took %s to make decision", time.Since(start))
|
||||
}
|
||||
if connection.GetVerdict() == network.BLOCK || connection.GetVerdict() == network.DROP {
|
||||
if comm.GetVerdict() == network.VerdictBlock || comm.GetVerdict() == network.VerdictDrop {
|
||||
nxDomain(w, query)
|
||||
return
|
||||
}
|
||||
|
||||
// get intel and RRs
|
||||
// start = time.Now()
|
||||
domainIntel, rrCache := intel.GetIntelAndRRs(fqdn, qtype, connection.Process().ProfileSet().SecurityLevel())
|
||||
domainIntel, rrCache := intel.GetIntelAndRRs(fqdn, qtype, comm.Process().ProfileSet().SecurityLevel())
|
||||
// log.Tracef("nameserver: took %s to get intel and RRs", time.Since(start))
|
||||
if rrCache == nil {
|
||||
// TODO: analyze nxdomain requests, malware could be trying DGA-domains
|
||||
log.Infof("nameserver: %s tried to query %s, but is nxdomain", connection.Process().String(), fqdn)
|
||||
log.Infof("nameserver: %s tried to query %s, but is nxdomain", comm.Process().String(), fqdn)
|
||||
nxDomain(w, query)
|
||||
return
|
||||
}
|
||||
|
||||
// set intel
|
||||
connection.Lock()
|
||||
connection.Intel = domainIntel
|
||||
connection.Unlock()
|
||||
connection.Save()
|
||||
comm.Lock()
|
||||
comm.Intel = domainIntel
|
||||
comm.Unlock()
|
||||
comm.Save()
|
||||
|
||||
// do a full check with intel
|
||||
if connection.GetVerdict() == network.UNDECIDED {
|
||||
rrCache = firewall.DecideOnConnectionAfterIntel(connection, fqdn, rrCache)
|
||||
if comm.GetVerdict() == network.VerdictUndecided || comm.GetVerdict() == network.VerdictUndeterminable {
|
||||
firewall.DecideOnCommunicationAfterIntel(comm, fqdn, rrCache)
|
||||
}
|
||||
if rrCache == nil || connection.GetVerdict() == network.BLOCK || connection.GetVerdict() == network.DROP {
|
||||
if comm.GetVerdict() == network.VerdictBlock || comm.GetVerdict() == network.VerdictDrop {
|
||||
nxDomain(w, query)
|
||||
return
|
||||
}
|
||||
|
||||
// filter DNS response
|
||||
rrCache = firewall.FilterDNSResponse(comm, fqdn, rrCache)
|
||||
if rrCache == nil {
|
||||
nxDomain(w, query)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ func cleaner() {
|
|||
|
||||
cleanLinks()
|
||||
time.Sleep(2 * time.Second)
|
||||
cleanConnections()
|
||||
cleanComms()
|
||||
time.Sleep(2 * time.Second)
|
||||
cleanProcesses()
|
||||
}
|
||||
|
@ -73,18 +73,18 @@ func cleanLinks() {
|
|||
}
|
||||
}
|
||||
|
||||
func cleanConnections() {
|
||||
connectionsLock.RLock()
|
||||
defer connectionsLock.RUnlock()
|
||||
func cleanComms() {
|
||||
commsLock.RLock()
|
||||
defer commsLock.RUnlock()
|
||||
|
||||
threshold := time.Now().Add(-thresholdDuration).Unix()
|
||||
for _, conn := range connections {
|
||||
conn.Lock()
|
||||
if conn.FirstLinkEstablished < threshold && conn.LinkCount == 0 {
|
||||
// log.Tracef("network.clean: deleted %s", conn.DatabaseKey())
|
||||
go conn.Delete()
|
||||
for _, comm := range comms {
|
||||
comm.Lock()
|
||||
if comm.FirstLinkEstablished < threshold && comm.LinkCount == 0 {
|
||||
// log.Tracef("network.clean: deleted %s", comm.DatabaseKey())
|
||||
go comm.Delete()
|
||||
}
|
||||
conn.Unlock()
|
||||
comm.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
349
network/communication.go
Normal file
349
network/communication.go
Normal file
|
@ -0,0 +1,349 @@
|
|||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Safing/portbase/database/record"
|
||||
"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.
|
||||
type Communication struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
Domain string
|
||||
Direction bool
|
||||
Intel *intel.Intel
|
||||
process *process.Process
|
||||
Verdict Verdict
|
||||
Reason string
|
||||
Inspect bool
|
||||
|
||||
FirstLinkEstablished int64
|
||||
LastLinkEstablished int64
|
||||
LinkCount uint
|
||||
|
||||
profileUpdateVersion uint32
|
||||
}
|
||||
|
||||
// Process returns the process that owns the connection.
|
||||
func (comm *Communication) Process() *process.Process {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
|
||||
return comm.process
|
||||
}
|
||||
|
||||
// ResetVerdict resets the verdict to VerdictUndecided.
|
||||
func (comm *Communication) ResetVerdict() {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
|
||||
comm.Verdict = VerdictUndecided
|
||||
}
|
||||
|
||||
// GetVerdict returns the current verdict.
|
||||
func (comm *Communication) GetVerdict() Verdict {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
|
||||
return comm.Verdict
|
||||
}
|
||||
|
||||
// Accept accepts the communication and adds the given reason.
|
||||
func (comm *Communication) Accept(reason string) {
|
||||
comm.AddReason(reason)
|
||||
comm.UpdateVerdict(VerdictAccept)
|
||||
}
|
||||
|
||||
// Deny blocks or drops the communication depending on the connection direction and adds the given reason.
|
||||
func (comm *Communication) Deny(reason string) {
|
||||
if comm.Direction {
|
||||
comm.Drop(reason)
|
||||
} else {
|
||||
comm.Block(reason)
|
||||
}
|
||||
}
|
||||
|
||||
// Block blocks the communication and adds the given reason.
|
||||
func (comm *Communication) Block(reason string) {
|
||||
comm.AddReason(reason)
|
||||
comm.UpdateVerdict(VerdictBlock)
|
||||
}
|
||||
|
||||
// Drop drops the communication and adds the given reason.
|
||||
func (comm *Communication) Drop(reason string) {
|
||||
comm.AddReason(reason)
|
||||
comm.UpdateVerdict(VerdictDrop)
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
if newVerdict > comm.Verdict {
|
||||
comm.Verdict = newVerdict
|
||||
go comm.Save()
|
||||
}
|
||||
}
|
||||
|
||||
// AddReason adds a human readable string as to why a certain verdict was set in regard to this communication.
|
||||
func (comm *Communication) AddReason(reason string) {
|
||||
if reason == "" {
|
||||
return
|
||||
}
|
||||
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
|
||||
if comm.Reason != "" {
|
||||
comm.Reason += " | "
|
||||
}
|
||||
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()
|
||||
|
||||
updateVersion := profile.GetUpdateVersion()
|
||||
if comm.profileUpdateVersion != updateVersion {
|
||||
comm.profileUpdateVersion = updateVersion
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetCommunicationByFirstPacket returns the matching communication from the internal storage.
|
||||
func GetCommunicationByFirstPacket(pkt packet.Packet) (*Communication, error) {
|
||||
// get Process
|
||||
proc, direction, err := process.GetProcessByPacket(pkt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var domain string
|
||||
|
||||
// Incoming
|
||||
if direction {
|
||||
switch netutils.ClassifyIP(pkt.GetIPHeader().Src) {
|
||||
case netutils.HostLocal:
|
||||
domain = IncomingHost
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
domain = IncomingLAN
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
domain = IncomingInternet
|
||||
case netutils.Invalid:
|
||||
domain = IncomingInvalid
|
||||
}
|
||||
|
||||
communication, ok := GetCommunication(proc.Pid, domain)
|
||||
if !ok {
|
||||
communication = &Communication{
|
||||
Domain: domain,
|
||||
Direction: Inbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
FirstLinkEstablished: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
communication.process.AddCommunication()
|
||||
return communication, nil
|
||||
}
|
||||
|
||||
// get domain
|
||||
ipinfo, err := intel.GetIPInfo(pkt.FmtRemoteIP())
|
||||
|
||||
// PeerToPeer
|
||||
if err != nil {
|
||||
// if no domain could be found, it must be a direct connection (ie. no DNS)
|
||||
|
||||
switch netutils.ClassifyIP(pkt.GetIPHeader().Dst) {
|
||||
case netutils.HostLocal:
|
||||
domain = PeerHost
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
domain = PeerLAN
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
domain = PeerInternet
|
||||
case netutils.Invalid:
|
||||
domain = PeerInvalid
|
||||
}
|
||||
|
||||
communication, ok := GetCommunication(proc.Pid, domain)
|
||||
if !ok {
|
||||
communication = &Communication{
|
||||
Domain: domain,
|
||||
Direction: Outbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
FirstLinkEstablished: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
communication.process.AddCommunication()
|
||||
return communication, nil
|
||||
}
|
||||
|
||||
// To Domain
|
||||
// FIXME: how to handle multiple possible domains?
|
||||
communication, ok := GetCommunication(proc.Pid, ipinfo.Domains[0])
|
||||
if !ok {
|
||||
communication = &Communication{
|
||||
Domain: ipinfo.Domains[0],
|
||||
Direction: Outbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
FirstLinkEstablished: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
communication.process.AddCommunication()
|
||||
return communication, nil
|
||||
}
|
||||
|
||||
// var localhost = net.IPv4(127, 0, 0, 1)
|
||||
|
||||
var (
|
||||
dnsAddress = net.IPv4(127, 0, 0, 1)
|
||||
dnsPort uint16 = 53
|
||||
)
|
||||
|
||||
// GetCommunicationByDNSRequest returns the matching communication from the internal storage.
|
||||
func GetCommunicationByDNSRequest(ip net.IP, port uint16, fqdn string) (*Communication, error) {
|
||||
// get Process
|
||||
proc, err := process.GetProcessByEndpoints(ip, port, dnsAddress, dnsPort, packet.UDP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
communication, ok := GetCommunication(proc.Pid, fqdn)
|
||||
if !ok {
|
||||
communication = &Communication{
|
||||
Domain: fqdn,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
}
|
||||
communication.process.AddCommunication()
|
||||
communication.Save()
|
||||
}
|
||||
return communication, nil
|
||||
}
|
||||
|
||||
// GetCommunication fetches a connection object from the internal storage.
|
||||
func GetCommunication(pid int, domain string) (comm *Communication, ok bool) {
|
||||
commsLock.RLock()
|
||||
defer commsLock.RUnlock()
|
||||
comm, ok = comms[fmt.Sprintf("%d/%s", pid, domain)]
|
||||
return
|
||||
}
|
||||
|
||||
func (comm *Communication) makeKey() string {
|
||||
return fmt.Sprintf("%d/%s", comm.process.Pid, comm.Domain)
|
||||
}
|
||||
|
||||
// Save saves the connection object in the storage and propagates the change.
|
||||
func (comm *Communication) Save() error {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
|
||||
if comm.process == nil {
|
||||
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.CreateMeta()
|
||||
}
|
||||
|
||||
key := comm.makeKey()
|
||||
commsLock.RLock()
|
||||
_, ok := comms[key]
|
||||
commsLock.RUnlock()
|
||||
|
||||
if !ok {
|
||||
commsLock.Lock()
|
||||
comms[key] = comm
|
||||
commsLock.Unlock()
|
||||
}
|
||||
|
||||
go dbController.PushUpdate(comm)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a connection from the storage and propagates the change.
|
||||
func (comm *Communication) Delete() {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
|
||||
commsLock.Lock()
|
||||
delete(comms, comm.makeKey())
|
||||
commsLock.Unlock()
|
||||
|
||||
comm.Meta().Delete()
|
||||
go dbController.PushUpdate(comm)
|
||||
comm.process.RemoveCommunication()
|
||||
go comm.process.Save()
|
||||
}
|
||||
|
||||
// AddLink applies the Communication to the Link and increases sets counter and timestamps.
|
||||
func (comm *Communication) AddLink(link *Link) {
|
||||
link.Lock()
|
||||
link.comm = comm
|
||||
link.Verdict = comm.Verdict
|
||||
link.Inspect = comm.Inspect
|
||||
link.Unlock()
|
||||
link.Save()
|
||||
|
||||
comm.Lock()
|
||||
comm.LinkCount++
|
||||
comm.LastLinkEstablished = time.Now().Unix()
|
||||
if comm.FirstLinkEstablished == 0 {
|
||||
comm.FirstLinkEstablished = comm.LastLinkEstablished
|
||||
}
|
||||
comm.Unlock()
|
||||
comm.Save()
|
||||
}
|
||||
|
||||
// RemoveLink lowers the link counter by one.
|
||||
func (comm *Communication) RemoveLink() {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
|
||||
if comm.LinkCount > 0 {
|
||||
comm.LinkCount--
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of Communication.
|
||||
func (comm *Communication) String() string {
|
||||
comm.Lock()
|
||||
defer comm.Unlock()
|
||||
|
||||
switch comm.Domain {
|
||||
case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid:
|
||||
if comm.process == nil {
|
||||
return "? <- *"
|
||||
}
|
||||
return fmt.Sprintf("%s <- *", comm.process.String())
|
||||
case PeerHost, PeerLAN, PeerInternet, PeerInvalid:
|
||||
if comm.process == nil {
|
||||
return "? -> *"
|
||||
}
|
||||
return fmt.Sprintf("%s -> *", comm.process.String())
|
||||
default:
|
||||
if comm.process == nil {
|
||||
return fmt.Sprintf("? -> %s", comm.Domain)
|
||||
}
|
||||
return fmt.Sprintf("%s -> %s", comm.process.String(), comm.Domain)
|
||||
}
|
||||
}
|
|
@ -1,325 +0,0 @@
|
|||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package network
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Safing/portbase/database/record"
|
||||
"github.com/Safing/portmaster/intel"
|
||||
"github.com/Safing/portmaster/network/netutils"
|
||||
"github.com/Safing/portmaster/network/packet"
|
||||
"github.com/Safing/portmaster/process"
|
||||
)
|
||||
|
||||
// Connection describes a connection between a process and a domain
|
||||
type Connection struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
Domain string
|
||||
Direction bool
|
||||
Intel *intel.Intel
|
||||
process *process.Process
|
||||
Verdict Verdict
|
||||
Reason string
|
||||
Inspect bool
|
||||
|
||||
FirstLinkEstablished int64
|
||||
LastLinkEstablished int64
|
||||
LinkCount uint
|
||||
}
|
||||
|
||||
// Process returns the process that owns the connection.
|
||||
func (conn *Connection) Process() *process.Process {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
return conn.process
|
||||
}
|
||||
|
||||
// GetVerdict returns the current verdict.
|
||||
func (conn *Connection) GetVerdict() Verdict {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
return conn.Verdict
|
||||
}
|
||||
|
||||
// Accept accepts the connection and adds the given reason.
|
||||
func (conn *Connection) Accept(reason string) {
|
||||
conn.AddReason(reason)
|
||||
conn.UpdateVerdict(ACCEPT)
|
||||
}
|
||||
|
||||
// Deny blocks or drops the connection depending on the connection direction and adds the given reason.
|
||||
func (conn *Connection) Deny(reason string) {
|
||||
if conn.Direction {
|
||||
conn.Drop(reason)
|
||||
} else {
|
||||
conn.Block(reason)
|
||||
}
|
||||
}
|
||||
|
||||
// Block blocks the connection and adds the given reason.
|
||||
func (conn *Connection) Block(reason string) {
|
||||
conn.AddReason(reason)
|
||||
conn.UpdateVerdict(BLOCK)
|
||||
}
|
||||
|
||||
// Drop drops the connection and adds the given reason.
|
||||
func (conn *Connection) Drop(reason string) {
|
||||
conn.AddReason(reason)
|
||||
conn.UpdateVerdict(DROP)
|
||||
}
|
||||
|
||||
// UpdateVerdict sets a new verdict for this link, making sure it does not interfere with previous verdicts
|
||||
func (conn *Connection) UpdateVerdict(newVerdict Verdict) {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
if newVerdict > conn.Verdict {
|
||||
conn.Verdict = newVerdict
|
||||
go conn.Save()
|
||||
}
|
||||
}
|
||||
|
||||
// AddReason adds a human readable string as to why a certain verdict was set in regard to this connection
|
||||
func (conn *Connection) AddReason(reason string) {
|
||||
if reason == "" {
|
||||
return
|
||||
}
|
||||
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
if conn.Reason != "" {
|
||||
conn.Reason += " | "
|
||||
}
|
||||
conn.Reason += reason
|
||||
}
|
||||
|
||||
// GetConnectionByFirstPacket returns the matching connection from the internal storage.
|
||||
func GetConnectionByFirstPacket(pkt packet.Packet) (*Connection, error) {
|
||||
// get Process
|
||||
proc, direction, err := process.GetProcessByPacket(pkt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var domain string
|
||||
|
||||
// Incoming
|
||||
if direction {
|
||||
switch netutils.ClassifyIP(pkt.GetIPHeader().Src) {
|
||||
case netutils.HostLocal:
|
||||
domain = IncomingHost
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
domain = IncomingLAN
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
domain = IncomingInternet
|
||||
case netutils.Invalid:
|
||||
domain = IncomingInvalid
|
||||
}
|
||||
|
||||
connection, ok := GetConnection(proc.Pid, domain)
|
||||
if !ok {
|
||||
connection = &Connection{
|
||||
Domain: domain,
|
||||
Direction: Inbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
FirstLinkEstablished: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
connection.process.AddConnection()
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
// get domain
|
||||
ipinfo, err := intel.GetIPInfo(pkt.FmtRemoteIP())
|
||||
|
||||
// PeerToPeer
|
||||
if err != nil {
|
||||
// if no domain could be found, it must be a direct connection
|
||||
|
||||
switch netutils.ClassifyIP(pkt.GetIPHeader().Dst) {
|
||||
case netutils.HostLocal:
|
||||
domain = PeerHost
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
domain = PeerLAN
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
domain = PeerInternet
|
||||
case netutils.Invalid:
|
||||
domain = PeerInvalid
|
||||
}
|
||||
|
||||
connection, ok := GetConnection(proc.Pid, domain)
|
||||
if !ok {
|
||||
connection = &Connection{
|
||||
Domain: domain,
|
||||
Direction: Outbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
FirstLinkEstablished: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
connection.process.AddConnection()
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
// To Domain
|
||||
// FIXME: how to handle multiple possible domains?
|
||||
connection, ok := GetConnection(proc.Pid, ipinfo.Domains[0])
|
||||
if !ok {
|
||||
connection = &Connection{
|
||||
Domain: ipinfo.Domains[0],
|
||||
Direction: Outbound,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
FirstLinkEstablished: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
connection.process.AddConnection()
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
// var localhost = net.IPv4(127, 0, 0, 1)
|
||||
|
||||
var (
|
||||
dnsAddress = net.IPv4(127, 0, 0, 1)
|
||||
dnsPort uint16 = 53
|
||||
)
|
||||
|
||||
// GetConnectionByDNSRequest returns the matching connection from the internal storage.
|
||||
func GetConnectionByDNSRequest(ip net.IP, port uint16, fqdn string) (*Connection, error) {
|
||||
// get Process
|
||||
proc, err := process.GetProcessByEndpoints(ip, port, dnsAddress, dnsPort, packet.UDP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connection, ok := GetConnection(proc.Pid, fqdn)
|
||||
if !ok {
|
||||
connection = &Connection{
|
||||
Domain: fqdn,
|
||||
process: proc,
|
||||
Inspect: true,
|
||||
}
|
||||
connection.process.AddConnection()
|
||||
connection.Save()
|
||||
}
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
// GetConnection fetches a connection object from the internal storage.
|
||||
func GetConnection(pid int, domain string) (conn *Connection, ok bool) {
|
||||
connectionsLock.RLock()
|
||||
defer connectionsLock.RUnlock()
|
||||
conn, ok = connections[fmt.Sprintf("%d/%s", pid, domain)]
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *Connection) makeKey() string {
|
||||
return fmt.Sprintf("%d/%s", conn.process.Pid, conn.Domain)
|
||||
}
|
||||
|
||||
// Save saves the connection object in the storage and propagates the change.
|
||||
func (conn *Connection) Save() error {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
if conn.process == nil {
|
||||
return errors.New("cannot save connection without process")
|
||||
}
|
||||
|
||||
if !conn.KeyIsSet() {
|
||||
conn.SetKey(fmt.Sprintf("network:tree/%d/%s", conn.process.Pid, conn.Domain))
|
||||
conn.CreateMeta()
|
||||
}
|
||||
|
||||
key := conn.makeKey()
|
||||
connectionsLock.RLock()
|
||||
_, ok := connections[key]
|
||||
connectionsLock.RUnlock()
|
||||
|
||||
if !ok {
|
||||
connectionsLock.Lock()
|
||||
connections[key] = conn
|
||||
connectionsLock.Unlock()
|
||||
}
|
||||
|
||||
go dbController.PushUpdate(conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a connection from the storage and propagates the change.
|
||||
func (conn *Connection) Delete() {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
connectionsLock.Lock()
|
||||
delete(connections, conn.makeKey())
|
||||
connectionsLock.Unlock()
|
||||
|
||||
conn.Meta().Delete()
|
||||
go dbController.PushUpdate(conn)
|
||||
conn.process.RemoveConnection()
|
||||
go conn.process.Save()
|
||||
}
|
||||
|
||||
// AddLink applies the connection to the link and increases sets counter and timestamps.
|
||||
func (conn *Connection) AddLink(link *Link) {
|
||||
link.Lock()
|
||||
link.connection = conn
|
||||
link.Verdict = conn.Verdict
|
||||
link.Inspect = conn.Inspect
|
||||
link.Unlock()
|
||||
link.Save()
|
||||
|
||||
conn.Lock()
|
||||
conn.LinkCount++
|
||||
conn.LastLinkEstablished = time.Now().Unix()
|
||||
if conn.FirstLinkEstablished == 0 {
|
||||
conn.FirstLinkEstablished = conn.LastLinkEstablished
|
||||
}
|
||||
conn.Unlock()
|
||||
conn.Save()
|
||||
}
|
||||
|
||||
// RemoveLink lowers the link counter by one.
|
||||
func (conn *Connection) RemoveLink() {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
if conn.LinkCount > 0 {
|
||||
conn.LinkCount--
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of Connection.
|
||||
func (conn *Connection) String() string {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
switch conn.Domain {
|
||||
case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid:
|
||||
if conn.process == nil {
|
||||
return "? <- *"
|
||||
}
|
||||
return fmt.Sprintf("%s <- *", conn.process.String())
|
||||
case PeerHost, PeerLAN, PeerInternet, PeerInvalid:
|
||||
if conn.process == nil {
|
||||
return "? -> *"
|
||||
}
|
||||
return fmt.Sprintf("%s -> *", conn.process.String())
|
||||
default:
|
||||
if conn.process == nil {
|
||||
return fmt.Sprintf("? -> %s", conn.Domain)
|
||||
}
|
||||
return fmt.Sprintf("%s -> %s", conn.process.String(), conn.Domain)
|
||||
}
|
||||
}
|
|
@ -14,10 +14,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
links = make(map[string]*Link)
|
||||
linksLock sync.RWMutex
|
||||
connections = make(map[string]*Connection)
|
||||
connectionsLock sync.RWMutex
|
||||
links = make(map[string]*Link)
|
||||
linksLock sync.RWMutex
|
||||
comms = make(map[string]*Communication)
|
||||
commsLock sync.RWMutex
|
||||
|
||||
dbController *database.Controller
|
||||
)
|
||||
|
@ -43,9 +43,9 @@ func (s *StorageInterface) Get(key string) (record.Record, error) {
|
|||
}
|
||||
}
|
||||
case 3:
|
||||
connectionsLock.RLock()
|
||||
defer connectionsLock.RUnlock()
|
||||
conn, ok := connections[splitted[2]]
|
||||
commsLock.RLock()
|
||||
defer commsLock.RUnlock()
|
||||
conn, ok := comms[splitted[2]]
|
||||
if ok {
|
||||
return conn, nil
|
||||
}
|
||||
|
@ -79,14 +79,14 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
|
|||
}
|
||||
}
|
||||
|
||||
// connections
|
||||
connectionsLock.RLock()
|
||||
for _, conn := range connections {
|
||||
// comms
|
||||
commsLock.RLock()
|
||||
for _, conn := range comms {
|
||||
if strings.HasPrefix(conn.DatabaseKey(), q.DatabaseKeyPrefix()) {
|
||||
it.Next <- conn
|
||||
}
|
||||
}
|
||||
connectionsLock.RUnlock()
|
||||
commsLock.RUnlock()
|
||||
|
||||
// links
|
||||
linksLock.RLock()
|
||||
|
|
|
@ -38,18 +38,18 @@ type Link struct {
|
|||
|
||||
pktQueue chan packet.Packet
|
||||
firewallHandler FirewallHandler
|
||||
connection *Connection
|
||||
comm *Communication
|
||||
|
||||
activeInspectors []bool
|
||||
inspectorData map[uint8]interface{}
|
||||
}
|
||||
|
||||
// Connection returns the Connection the Link is part of
|
||||
func (link *Link) Connection() *Connection {
|
||||
// Communication returns the Communication the Link is part of
|
||||
func (link *Link) Communication() *Communication {
|
||||
link.Lock()
|
||||
defer link.Unlock()
|
||||
|
||||
return link.connection
|
||||
return link.comm
|
||||
}
|
||||
|
||||
// GetVerdict returns the current verdict.
|
||||
|
@ -106,12 +106,12 @@ func (link *Link) HandlePacket(pkt packet.Packet) {
|
|||
// Accept accepts the link and adds the given reason.
|
||||
func (link *Link) Accept(reason string) {
|
||||
link.AddReason(reason)
|
||||
link.UpdateVerdict(ACCEPT)
|
||||
link.UpdateVerdict(VerdictAccept)
|
||||
}
|
||||
|
||||
// Deny blocks or drops the link depending on the connection direction and adds the given reason.
|
||||
func (link *Link) Deny(reason string) {
|
||||
if link.connection != nil && link.connection.Direction {
|
||||
if link.comm != nil && link.comm.Direction {
|
||||
link.Drop(reason)
|
||||
} else {
|
||||
link.Block(reason)
|
||||
|
@ -121,24 +121,24 @@ func (link *Link) Deny(reason string) {
|
|||
// Block blocks the link and adds the given reason.
|
||||
func (link *Link) Block(reason string) {
|
||||
link.AddReason(reason)
|
||||
link.UpdateVerdict(BLOCK)
|
||||
link.UpdateVerdict(VerdictBlock)
|
||||
}
|
||||
|
||||
// Drop drops the link and adds the given reason.
|
||||
func (link *Link) Drop(reason string) {
|
||||
link.AddReason(reason)
|
||||
link.UpdateVerdict(DROP)
|
||||
link.UpdateVerdict(VerdictDrop)
|
||||
}
|
||||
|
||||
// RerouteToNameserver reroutes the link to the portmaster nameserver.
|
||||
func (link *Link) RerouteToNameserver() {
|
||||
link.UpdateVerdict(RerouteToNameserver)
|
||||
link.UpdateVerdict(VerdictRerouteToNameserver)
|
||||
}
|
||||
|
||||
// RerouteToTunnel reroutes the link to the tunnel entrypoint and adds the given reason for accepting the connection.
|
||||
func (link *Link) RerouteToTunnel(reason string) {
|
||||
link.AddReason(reason)
|
||||
link.UpdateVerdict(RerouteToTunnel)
|
||||
link.UpdateVerdict(VerdictRerouteToTunnel)
|
||||
}
|
||||
|
||||
// UpdateVerdict sets a new verdict for this link, making sure it does not interfere with previous verdicts
|
||||
|
@ -192,30 +192,30 @@ func (link *Link) ApplyVerdict(pkt packet.Packet) {
|
|||
|
||||
if link.VerdictPermanent {
|
||||
switch link.Verdict {
|
||||
case ACCEPT:
|
||||
case VerdictAccept:
|
||||
pkt.PermanentAccept()
|
||||
case BLOCK:
|
||||
case VerdictBlock:
|
||||
pkt.PermanentBlock()
|
||||
case DROP:
|
||||
case VerdictDrop:
|
||||
pkt.PermanentDrop()
|
||||
case RerouteToNameserver:
|
||||
case VerdictRerouteToNameserver:
|
||||
pkt.RerouteToNameserver()
|
||||
case RerouteToTunnel:
|
||||
case VerdictRerouteToTunnel:
|
||||
pkt.RerouteToTunnel()
|
||||
default:
|
||||
pkt.Drop()
|
||||
}
|
||||
} else {
|
||||
switch link.Verdict {
|
||||
case ACCEPT:
|
||||
case VerdictAccept:
|
||||
pkt.Accept()
|
||||
case BLOCK:
|
||||
case VerdictBlock:
|
||||
pkt.Block()
|
||||
case DROP:
|
||||
case VerdictDrop:
|
||||
pkt.Drop()
|
||||
case RerouteToNameserver:
|
||||
case VerdictRerouteToNameserver:
|
||||
pkt.RerouteToNameserver()
|
||||
case RerouteToTunnel:
|
||||
case VerdictRerouteToTunnel:
|
||||
pkt.RerouteToTunnel()
|
||||
default:
|
||||
pkt.Drop()
|
||||
|
@ -228,12 +228,12 @@ func (link *Link) Save() error {
|
|||
link.Lock()
|
||||
defer link.Unlock()
|
||||
|
||||
if link.connection == nil {
|
||||
return errors.New("cannot save link without connection")
|
||||
if link.comm == nil {
|
||||
return errors.New("cannot save link without comms")
|
||||
}
|
||||
|
||||
if !link.KeyIsSet() {
|
||||
link.SetKey(fmt.Sprintf("network:tree/%d/%s/%s", link.connection.Process().Pid, link.connection.Domain, link.ID))
|
||||
link.SetKey(fmt.Sprintf("network:tree/%d/%s/%s", link.comm.Process().Pid, link.comm.Domain, link.ID))
|
||||
link.CreateMeta()
|
||||
}
|
||||
|
||||
|
@ -262,8 +262,8 @@ func (link *Link) Delete() {
|
|||
|
||||
link.Meta().Delete()
|
||||
go dbController.PushUpdate(link)
|
||||
link.connection.RemoveLink()
|
||||
go link.connection.Save()
|
||||
link.comm.RemoveLink()
|
||||
go link.comm.Save()
|
||||
}
|
||||
|
||||
// GetLink fetches a Link from the database from the default namespace for this object
|
||||
|
@ -288,7 +288,7 @@ func GetOrCreateLinkByPacket(pkt packet.Packet) (*Link, bool) {
|
|||
func CreateLinkFromPacket(pkt packet.Packet) *Link {
|
||||
link := &Link{
|
||||
ID: pkt.GetLinkID(),
|
||||
Verdict: UNDECIDED,
|
||||
Verdict: VerdictUndecided,
|
||||
Started: time.Now().Unix(),
|
||||
RemoteAddress: pkt.FmtRemoteAddress(),
|
||||
}
|
||||
|
@ -332,24 +332,24 @@ func (link *Link) String() string {
|
|||
link.Lock()
|
||||
defer link.Unlock()
|
||||
|
||||
if link.connection == nil {
|
||||
if link.comm == nil {
|
||||
return fmt.Sprintf("? <-> %s", link.RemoteAddress)
|
||||
}
|
||||
switch link.connection.Domain {
|
||||
switch link.comm.Domain {
|
||||
case "I":
|
||||
if link.connection.process == nil {
|
||||
if link.comm.process == nil {
|
||||
return fmt.Sprintf("? <- %s", link.RemoteAddress)
|
||||
}
|
||||
return fmt.Sprintf("%s <- %s", link.connection.process.String(), link.RemoteAddress)
|
||||
return fmt.Sprintf("%s <- %s", link.comm.process.String(), link.RemoteAddress)
|
||||
case "D":
|
||||
if link.connection.process == nil {
|
||||
if link.comm.process == nil {
|
||||
return fmt.Sprintf("? -> %s", link.RemoteAddress)
|
||||
}
|
||||
return fmt.Sprintf("%s -> %s", link.connection.process.String(), link.RemoteAddress)
|
||||
return fmt.Sprintf("%s -> %s", link.comm.process.String(), link.RemoteAddress)
|
||||
default:
|
||||
if link.connection.process == nil {
|
||||
return fmt.Sprintf("? -> %s (%s)", link.connection.Domain, link.RemoteAddress)
|
||||
if link.comm.process == nil {
|
||||
return fmt.Sprintf("? -> %s (%s)", link.comm.Domain, link.RemoteAddress)
|
||||
}
|
||||
return fmt.Sprintf("%s to %s (%s)", link.connection.process.String(), link.connection.Domain, link.RemoteAddress)
|
||||
return fmt.Sprintf("%s to %s (%s)", link.comm.process.String(), link.comm.Domain, link.RemoteAddress)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,13 @@ type Verdict uint8
|
|||
// List of values a Status can have
|
||||
const (
|
||||
// UNDECIDED is the default status of new connections
|
||||
UNDECIDED Verdict = iota
|
||||
ACCEPT
|
||||
BLOCK
|
||||
DROP
|
||||
RerouteToNameserver
|
||||
RerouteToTunnel
|
||||
VerdictUndecided Verdict = 0
|
||||
VerdictUndeterminable Verdict = 1
|
||||
VerdictAccept Verdict = 2
|
||||
VerdictBlock Verdict = 3
|
||||
VerdictDrop Verdict = 4
|
||||
VerdictRerouteToNameserver Verdict = 5
|
||||
VerdictRerouteToTunnel Verdict = 6
|
||||
)
|
||||
|
||||
// Packer Directions
|
||||
|
|
|
@ -13,52 +13,52 @@ const (
|
|||
ReasonUnknownProcess = "unknown connection owner: process could not be found"
|
||||
)
|
||||
|
||||
// GetUnknownConnection returns the connection to a packet of unknown owner.
|
||||
func GetUnknownConnection(pkt packet.Packet) (*Connection, error) {
|
||||
// GetUnknownCommunication returns the connection to a packet of unknown owner.
|
||||
func GetUnknownCommunication(pkt packet.Packet) (*Communication, error) {
|
||||
if pkt.IsInbound() {
|
||||
switch netutils.ClassifyIP(pkt.GetIPHeader().Src) {
|
||||
case netutils.HostLocal:
|
||||
return getOrCreateUnknownConnection(pkt, IncomingHost)
|
||||
return getOrCreateUnknownCommunication(pkt, IncomingHost)
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
return getOrCreateUnknownConnection(pkt, IncomingLAN)
|
||||
return getOrCreateUnknownCommunication(pkt, IncomingLAN)
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
return getOrCreateUnknownConnection(pkt, IncomingInternet)
|
||||
return getOrCreateUnknownCommunication(pkt, IncomingInternet)
|
||||
case netutils.Invalid:
|
||||
return getOrCreateUnknownConnection(pkt, IncomingInvalid)
|
||||
return getOrCreateUnknownCommunication(pkt, IncomingInvalid)
|
||||
}
|
||||
}
|
||||
|
||||
switch netutils.ClassifyIP(pkt.GetIPHeader().Dst) {
|
||||
case netutils.HostLocal:
|
||||
return getOrCreateUnknownConnection(pkt, PeerHost)
|
||||
return getOrCreateUnknownCommunication(pkt, PeerHost)
|
||||
case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
return getOrCreateUnknownConnection(pkt, PeerLAN)
|
||||
return getOrCreateUnknownCommunication(pkt, PeerLAN)
|
||||
case netutils.Global, netutils.GlobalMulticast:
|
||||
return getOrCreateUnknownConnection(pkt, PeerInternet)
|
||||
return getOrCreateUnknownCommunication(pkt, PeerInternet)
|
||||
case netutils.Invalid:
|
||||
return getOrCreateUnknownConnection(pkt, PeerInvalid)
|
||||
return getOrCreateUnknownCommunication(pkt, PeerInvalid)
|
||||
}
|
||||
|
||||
// this should never happen
|
||||
return getOrCreateUnknownConnection(pkt, PeerInvalid)
|
||||
return getOrCreateUnknownCommunication(pkt, PeerInvalid)
|
||||
}
|
||||
|
||||
func getOrCreateUnknownConnection(pkt packet.Packet, connClass string) (*Connection, error) {
|
||||
connection, ok := GetConnection(process.UnknownProcess.Pid, connClass)
|
||||
func getOrCreateUnknownCommunication(pkt packet.Packet, connClass string) (*Communication, error) {
|
||||
connection, ok := GetCommunication(process.UnknownProcess.Pid, connClass)
|
||||
if !ok {
|
||||
connection = &Connection{
|
||||
connection = &Communication{
|
||||
Domain: connClass,
|
||||
Direction: pkt.IsInbound(),
|
||||
Verdict: DROP,
|
||||
Verdict: VerdictDrop,
|
||||
Reason: ReasonUnknownProcess,
|
||||
process: process.UnknownProcess,
|
||||
Inspect: true,
|
||||
FirstLinkEstablished: time.Now().Unix(),
|
||||
}
|
||||
if pkt.IsOutbound() {
|
||||
connection.Verdict = BLOCK
|
||||
connection.Verdict = VerdictBlock
|
||||
}
|
||||
}
|
||||
connection.process.AddConnection()
|
||||
connection.process.AddCommunication()
|
||||
return connection, nil
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ func CleanProcessStorage(thresholdDuration time.Duration) {
|
|||
threshold := time.Now().Add(-thresholdDuration).Unix()
|
||||
for _, p := range processes {
|
||||
p.Lock()
|
||||
if p.FirstConnectionEstablished < threshold && p.ConnectionCount == 0 {
|
||||
if p.FirstCommEstablished < threshold && p.CommCount == 0 {
|
||||
go p.Delete()
|
||||
}
|
||||
p.Unlock()
|
||||
|
|
|
@ -42,9 +42,9 @@ type Process struct {
|
|||
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.
|
||||
|
||||
FirstConnectionEstablished int64
|
||||
LastConnectionEstablished int64
|
||||
ConnectionCount uint
|
||||
FirstCommEstablished int64
|
||||
LastCommEstablished int64
|
||||
CommCount uint
|
||||
}
|
||||
|
||||
// ProfileSet returns the assigned profile set.
|
||||
|
@ -66,25 +66,25 @@ func (p *Process) String() string {
|
|||
return fmt.Sprintf("%s:%s:%d", p.UserName, p.Path, p.Pid)
|
||||
}
|
||||
|
||||
// AddConnection increases the connection counter and the last connection timestamp.
|
||||
func (p *Process) AddConnection() {
|
||||
// AddCommunication increases the connection counter and the last connection timestamp.
|
||||
func (p *Process) AddCommunication() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
p.ConnectionCount++
|
||||
p.LastConnectionEstablished = time.Now().Unix()
|
||||
if p.FirstConnectionEstablished == 0 {
|
||||
p.FirstConnectionEstablished = p.LastConnectionEstablished
|
||||
p.CommCount++
|
||||
p.LastCommEstablished = time.Now().Unix()
|
||||
if p.FirstCommEstablished == 0 {
|
||||
p.FirstCommEstablished = p.LastCommEstablished
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveConnection lowers the connection counter by one.
|
||||
func (p *Process) RemoveConnection() {
|
||||
// RemoveCommunication lowers the connection counter by one.
|
||||
func (p *Process) RemoveCommunication() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if p.ConnectionCount > 0 {
|
||||
p.ConnectionCount--
|
||||
if p.CommCount > 0 {
|
||||
p.CommCount--
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,17 +27,15 @@ func makeDefaultFallbackProfile() *Profile {
|
|||
Localhost: status.SecurityLevelsAll,
|
||||
|
||||
// Specials
|
||||
Related: status.SecurityLevelDynamic,
|
||||
PeerToPeer: status.SecurityLevelDynamic,
|
||||
Related: status.SecurityLevelDynamic,
|
||||
},
|
||||
ServiceEndpoints: []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Protocol: 0,
|
||||
StartPort: 0,
|
||||
EndPort: 0,
|
||||
Permit: false,
|
||||
Type: EptAny,
|
||||
Protocol: 0,
|
||||
StartPort: 0,
|
||||
EndPort: 0,
|
||||
Permit: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package profile
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -13,15 +14,44 @@ type Endpoints []*EndpointPermission
|
|||
|
||||
// EndpointPermission holds a decision about an endpoint.
|
||||
type EndpointPermission struct {
|
||||
DomainOrIP string
|
||||
Wildcard bool
|
||||
Protocol uint8
|
||||
StartPort uint16
|
||||
EndPort uint16
|
||||
Permit bool
|
||||
Created int64
|
||||
Type EPType
|
||||
Value string
|
||||
|
||||
Protocol uint8
|
||||
StartPort uint16
|
||||
EndPort uint16
|
||||
|
||||
Permit bool
|
||||
Created int64
|
||||
}
|
||||
|
||||
// EPType represents the type of an EndpointPermission
|
||||
type EPType uint8
|
||||
|
||||
// EPType values
|
||||
const (
|
||||
EptUnknown EPType = 0
|
||||
EptAny EPType = 1
|
||||
EptDomain EPType = 2
|
||||
EptIPv4 EPType = 3
|
||||
EptIPv6 EPType = 4
|
||||
EptIPv4Range EPType = 5
|
||||
EptIPv6Range EPType = 6
|
||||
EptASN EPType = 7
|
||||
EptCountry EPType = 8
|
||||
)
|
||||
|
||||
// EPResult represents the result of a check against an EndpointPermission
|
||||
type EPResult uint8
|
||||
|
||||
// EndpointPermission return values
|
||||
const (
|
||||
NoMatch EPResult = iota
|
||||
Undeterminable
|
||||
Denied
|
||||
Permitted
|
||||
)
|
||||
|
||||
// IsSet returns whether the Endpoints object is "set".
|
||||
func (e Endpoints) IsSet() bool {
|
||||
if len(e) > 0 {
|
||||
|
@ -30,9 +60,28 @@ func (e Endpoints) IsSet() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Check checks if the given domain is governed in the list of domains and returns whether it is permitted.
|
||||
// If getDomainOfIP (returns reverse and forward dns matching domain name) is supplied, an IP will be resolved to a domain, if necessary.
|
||||
func (e Endpoints) Check(domainOrIP string, protocol uint8, port uint16, checkReverseIP bool, securityLevel uint8) (permit bool, reason string, ok bool) {
|
||||
// CheckDomain checks the if the given endpoint matches a EndpointPermission in the list.
|
||||
func (e Endpoints) CheckDomain(domain string) (result EPResult, reason string) {
|
||||
if domain == "" {
|
||||
return Denied, "internal error"
|
||||
}
|
||||
|
||||
for _, entry := range e {
|
||||
if entry != nil {
|
||||
if result, reason = entry.MatchesDomain(domain); result != NoMatch {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NoMatch, ""
|
||||
}
|
||||
|
||||
// CheckIP checks the if the given endpoint matches a EndpointPermission in the list. If _checkReverseIP_ and no domain is given, the IP will be resolved to a domain, if necessary.
|
||||
func (e Endpoints) CheckIP(domain string, ip net.IP, protocol uint8, port uint16, checkReverseIP bool, securityLevel uint8) (result EPResult, reason string) {
|
||||
if ip == nil {
|
||||
return Denied, "internal error"
|
||||
}
|
||||
|
||||
// ip resolving
|
||||
var cachedGetDomainOfIP func() string
|
||||
|
@ -42,7 +91,7 @@ func (e Endpoints) Check(domainOrIP string, protocol uint8, port uint16, checkRe
|
|||
// setup caching wrapper
|
||||
cachedGetDomainOfIP = func() string {
|
||||
if !ipResolved {
|
||||
result, err := intel.ResolveIPAndValidate(domainOrIP, securityLevel)
|
||||
result, err := intel.ResolveIPAndValidate(ip.String(), securityLevel)
|
||||
if err != nil {
|
||||
// log.Debug()
|
||||
ipName = result
|
||||
|
@ -53,54 +102,139 @@ func (e Endpoints) Check(domainOrIP string, protocol uint8, port uint16, checkRe
|
|||
}
|
||||
}
|
||||
|
||||
isDomain := strings.HasSuffix(domainOrIP, ".")
|
||||
|
||||
for _, entry := range e {
|
||||
if entry != nil {
|
||||
if ok, reason := entry.Matches(domainOrIP, protocol, port, isDomain, cachedGetDomainOfIP); ok {
|
||||
return entry.Permit, reason, true
|
||||
if result, reason := entry.MatchesIP(domain, ip, protocol, port, cachedGetDomainOfIP); result != NoMatch {
|
||||
return result, reason
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, "", false
|
||||
return NoMatch, ""
|
||||
}
|
||||
|
||||
func isSubdomainOf(domain, subdomain string) bool {
|
||||
dotPrefixedDomain := "." + domain
|
||||
return strings.HasSuffix(subdomain, dotPrefixedDomain)
|
||||
}
|
||||
|
||||
// Matches checks whether the given endpoint has a managed permission. If getDomainOfIP (returns reverse and forward dns matching domain name) is supplied, this declares an incoming connection.
|
||||
func (ep EndpointPermission) Matches(domainOrIP string, protocol uint8, port uint16, isDomain bool, getDomainOfIP func() string) (match bool, reason string) {
|
||||
if ep.Protocol > 0 && protocol != ep.Protocol {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if ep.StartPort > 0 && (port < ep.StartPort || port > ep.EndPort) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (ep EndpointPermission) matchesDomainOnly(domain string) (matches bool, reason string) {
|
||||
wildcardInFront := strings.HasPrefix(ep.Value, "*")
|
||||
wildcardInBack := strings.HasSuffix(ep.Value, "*")
|
||||
switch {
|
||||
case ep.Wildcard && len(ep.DomainOrIP) == 0:
|
||||
// host wildcard
|
||||
return true, fmt.Sprintf("%s matches %s", domainOrIP, ep)
|
||||
case domainOrIP == ep.DomainOrIP:
|
||||
// host match
|
||||
return true, fmt.Sprintf("%s matches %s", domainOrIP, ep)
|
||||
case isDomain && ep.Wildcard && isSubdomainOf(ep.DomainOrIP, domainOrIP):
|
||||
// subdomain match
|
||||
return true, fmt.Sprintf("%s matches %s", domainOrIP, ep)
|
||||
case !isDomain && getDomainOfIP != nil && getDomainOfIP() == ep.DomainOrIP:
|
||||
// resolved IP match
|
||||
return true, fmt.Sprintf("%s->%s matches %s", domainOrIP, getDomainOfIP(), ep)
|
||||
case !isDomain && getDomainOfIP != nil && ep.Wildcard && isSubdomainOf(ep.DomainOrIP, getDomainOfIP()):
|
||||
// resolved IP subdomain match
|
||||
return true, fmt.Sprintf("%s->%s matches %s", domainOrIP, getDomainOfIP(), ep)
|
||||
case wildcardInFront && wildcardInBack:
|
||||
if strings.Contains(domain, strings.Trim(ep.Value, "*")) {
|
||||
return true, fmt.Sprintf("%s matches %s", domain, ep.Value)
|
||||
}
|
||||
case wildcardInFront:
|
||||
if strings.HasSuffix(domain, strings.TrimLeft(ep.Value, "*")) {
|
||||
return true, fmt.Sprintf("%s matches %s", domain, ep.Value)
|
||||
}
|
||||
case wildcardInBack:
|
||||
if strings.HasPrefix(domain, strings.TrimRight(ep.Value, "*")) {
|
||||
return true, fmt.Sprintf("%s matches %s", domain, ep.Value)
|
||||
}
|
||||
default:
|
||||
// no match
|
||||
return false, ""
|
||||
if domain == ep.Value {
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
||||
return false, ""
|
||||
}
|
||||
|
||||
func (ep EndpointPermission) matchProtocolAndPortsAndReturn(protocol uint8, port uint16) (result EPResult) {
|
||||
// only check if protocol is defined
|
||||
if ep.Protocol > 0 {
|
||||
// if protocol is unknown, return Undeterminable
|
||||
if protocol == 0 {
|
||||
return Undeterminable
|
||||
}
|
||||
// if protocol does not match, return NoMatch
|
||||
if protocol != ep.Protocol {
|
||||
return NoMatch
|
||||
}
|
||||
}
|
||||
|
||||
// only check if port is defined
|
||||
if ep.StartPort > 0 {
|
||||
// if port is unknown, return Undeterminable
|
||||
if port == 0 {
|
||||
return Undeterminable
|
||||
}
|
||||
// if port does not match, return NoMatch
|
||||
if port < ep.StartPort || port > ep.EndPort {
|
||||
return NoMatch
|
||||
}
|
||||
}
|
||||
|
||||
// protocol and port matched or were defined as any
|
||||
if ep.Permit {
|
||||
return Permitted
|
||||
}
|
||||
return Denied
|
||||
}
|
||||
|
||||
// MatchesDomain checks if the given endpoint matches the EndpointPermission.
|
||||
func (ep EndpointPermission) MatchesDomain(domain string) (result EPResult, reason string) {
|
||||
switch ep.Type {
|
||||
case EptAny:
|
||||
// always matches
|
||||
case EptDomain:
|
||||
var matched bool
|
||||
matched, reason = ep.matchesDomainOnly(domain)
|
||||
if !matched {
|
||||
return NoMatch, ""
|
||||
}
|
||||
case EptIPv4:
|
||||
return Undeterminable, ""
|
||||
case EptIPv6:
|
||||
return Undeterminable, ""
|
||||
case EptIPv4Range:
|
||||
return Undeterminable, ""
|
||||
case EptIPv6Range:
|
||||
return Undeterminable, ""
|
||||
case EptASN:
|
||||
return Undeterminable, ""
|
||||
case EptCountry:
|
||||
return Undeterminable, ""
|
||||
default:
|
||||
return Denied, "encountered unknown enpoint permission type"
|
||||
}
|
||||
|
||||
return ep.matchProtocolAndPortsAndReturn(0, 0), reason
|
||||
}
|
||||
|
||||
// MatchesIP checks if the given endpoint matches the EndpointPermission. _getDomainOfIP_, if given, will be used to get the domain if not given.
|
||||
func (ep EndpointPermission) MatchesIP(domain string, ip net.IP, protocol uint8, port uint16, getDomainOfIP func() string) (result EPResult, reason string) {
|
||||
switch ep.Type {
|
||||
case EptAny:
|
||||
// always matches
|
||||
case EptDomain:
|
||||
if domain == "" {
|
||||
if getDomainOfIP == nil {
|
||||
return NoMatch, ""
|
||||
}
|
||||
domain = getDomainOfIP()
|
||||
}
|
||||
|
||||
var matched bool
|
||||
matched, reason = ep.matchesDomainOnly(domain)
|
||||
if !matched {
|
||||
return NoMatch, ""
|
||||
}
|
||||
case EptIPv4, EptIPv6:
|
||||
if ep.Value != ip.String() {
|
||||
return NoMatch, ""
|
||||
}
|
||||
case EptIPv4Range:
|
||||
return Denied, "endpoint type IP Range not yet implemented"
|
||||
case EptIPv6Range:
|
||||
return Denied, "endpoint type IP Range not yet implemented"
|
||||
case EptASN:
|
||||
return Denied, "endpoint type ASN not yet implemented"
|
||||
case EptCountry:
|
||||
return Denied, "endpoint type country not yet implemented"
|
||||
default:
|
||||
return Denied, "encountered unknown enpoint permission type"
|
||||
}
|
||||
|
||||
return ep.matchProtocolAndPortsAndReturn(protocol, port), reason
|
||||
}
|
||||
|
||||
func (e Endpoints) String() string {
|
||||
|
@ -111,9 +245,36 @@ func (e Endpoints) String() string {
|
|||
return fmt.Sprintf("[%s]", strings.Join(s, ", "))
|
||||
}
|
||||
|
||||
func (ep EndpointPermission) String() string {
|
||||
s := ep.DomainOrIP
|
||||
func (ept EPType) String() string {
|
||||
switch ept {
|
||||
case EptAny:
|
||||
return "Any"
|
||||
case EptDomain:
|
||||
return "Domain"
|
||||
case EptIPv4:
|
||||
return "IPv4"
|
||||
case EptIPv6:
|
||||
return "IPv6"
|
||||
case EptIPv4Range:
|
||||
return "IPv4-Range"
|
||||
case EptIPv6Range:
|
||||
return "IPv6-Range"
|
||||
case EptASN:
|
||||
return "ASN"
|
||||
case EptCountry:
|
||||
return "Country"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (ep EndpointPermission) String() string {
|
||||
s := ep.Type.String()
|
||||
|
||||
if ep.Type != EptAny {
|
||||
s += ":"
|
||||
s += ep.Value
|
||||
}
|
||||
s += " "
|
||||
|
||||
if ep.Protocol > 0 {
|
||||
|
@ -136,3 +297,18 @@ func (ep EndpointPermission) String() string {
|
|||
|
||||
return s
|
||||
}
|
||||
|
||||
func (epr EPResult) String() string {
|
||||
switch epr {
|
||||
case NoMatch:
|
||||
return "No Match"
|
||||
case Undeterminable:
|
||||
return "Undeterminable"
|
||||
case Denied:
|
||||
return "Denied"
|
||||
case Permitted:
|
||||
return "Permitted"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,54 +1,159 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/Safing/portbase/utils/testutils"
|
||||
)
|
||||
|
||||
// TODO: RETIRED
|
||||
// func testdeMatcher(t *testing.T, value string, expectedResult bool) {
|
||||
// if domainEndingMatcher.MatchString(value) != expectedResult {
|
||||
// if expectedResult {
|
||||
// t.Errorf("domainEndingMatcher should match %s", value)
|
||||
// } else {
|
||||
// t.Errorf("domainEndingMatcher should not match %s", value)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestdomainEndingMatcher(t *testing.T) {
|
||||
// testdeMatcher(t, "example.com", true)
|
||||
// testdeMatcher(t, "com", true)
|
||||
// testdeMatcher(t, "example.xn--lgbbat1ad8j", true)
|
||||
// testdeMatcher(t, "xn--lgbbat1ad8j", true)
|
||||
// testdeMatcher(t, "fe80::beef", false)
|
||||
// testdeMatcher(t, "fe80::dead:beef", false)
|
||||
// testdeMatcher(t, "10.2.3.4", false)
|
||||
// testdeMatcher(t, "4", false)
|
||||
// }
|
||||
func testEndpointDomainMatch(t *testing.T, ep *EndpointPermission, domain string, expectedResult EPResult) {
|
||||
var result EPResult
|
||||
result, _ = ep.MatchesDomain(domain)
|
||||
if result != expectedResult {
|
||||
t.Errorf(
|
||||
"line %d: unexpected result for endpoint domain match %s: result=%s, expected=%s",
|
||||
testutils.GetLineNumberOfCaller(1),
|
||||
domain,
|
||||
result,
|
||||
expectedResult,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testEndpointIPMatch(t *testing.T, ep *EndpointPermission, domain string, ip net.IP, protocol uint8, port uint16, expectedResult EPResult) {
|
||||
var result EPResult
|
||||
result, _ = ep.MatchesIP(domain, ip, protocol, port, nil)
|
||||
if result != expectedResult {
|
||||
t.Errorf(
|
||||
"line %d: unexpected result for endpoint %s/%s/%d/%d: result=%s, expected=%s",
|
||||
testutils.GetLineNumberOfCaller(1),
|
||||
domain,
|
||||
ip,
|
||||
protocol,
|
||||
port,
|
||||
result,
|
||||
expectedResult,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndpointMatching(t *testing.T) {
|
||||
ep := &EndpointPermission{
|
||||
Type: EptAny,
|
||||
Protocol: 0,
|
||||
StartPort: 0,
|
||||
EndPort: 0,
|
||||
Permit: true,
|
||||
}
|
||||
|
||||
// ANY
|
||||
|
||||
testEndpointDomainMatch(t, ep, "example.com.", Permitted)
|
||||
testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted)
|
||||
|
||||
// DOMAIN
|
||||
|
||||
// wildcard domains
|
||||
ep.Type = EptDomain
|
||||
ep.Value = "*example.com."
|
||||
testEndpointDomainMatch(t, ep, "example.com.", Permitted)
|
||||
testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted)
|
||||
|
||||
ep.Type = EptDomain
|
||||
ep.Value = "example.*"
|
||||
testEndpointDomainMatch(t, ep, "example.com.", Permitted)
|
||||
testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted)
|
||||
|
||||
ep.Type = EptDomain
|
||||
ep.Value = "*.exampl*"
|
||||
testEndpointDomainMatch(t, ep, "abc.example.com.", Permitted)
|
||||
testEndpointIPMatch(t, ep, "abc.example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted)
|
||||
|
||||
ep.Value = "*.com."
|
||||
testEndpointDomainMatch(t, ep, "example.com.", Permitted)
|
||||
testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted)
|
||||
|
||||
// edge case
|
||||
ep.Value = ""
|
||||
testEndpointDomainMatch(t, ep, "example.com", NoMatch)
|
||||
|
||||
// edge case
|
||||
ep.Value = "*"
|
||||
testEndpointDomainMatch(t, ep, "example.com", Permitted)
|
||||
|
||||
// edge case
|
||||
ep.Value = "**"
|
||||
testEndpointDomainMatch(t, ep, "example.com", Permitted)
|
||||
|
||||
// edge case
|
||||
ep.Value = "***"
|
||||
testEndpointDomainMatch(t, ep, "example.com", Permitted)
|
||||
|
||||
// protocol
|
||||
ep.Value = "example.com"
|
||||
ep.Protocol = 17
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 6, 443, NoMatch)
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
|
||||
testEndpointDomainMatch(t, ep, "example.com", Undeterminable)
|
||||
|
||||
// ports
|
||||
ep.StartPort = 442
|
||||
ep.EndPort = 444
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 80, NoMatch)
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
|
||||
ep.StartPort = 442
|
||||
ep.StartPort = 443
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 80, NoMatch)
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
|
||||
ep.StartPort = 443
|
||||
ep.EndPort = 444
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 80, NoMatch)
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
|
||||
ep.StartPort = 443
|
||||
ep.EndPort = 443
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 80, NoMatch)
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
|
||||
testEndpointDomainMatch(t, ep, "example.com", Undeterminable)
|
||||
|
||||
// IP
|
||||
|
||||
ep.Type = EptIPv4
|
||||
ep.Value = "10.2.3.4"
|
||||
ep.Protocol = 0
|
||||
ep.StartPort = 0
|
||||
ep.EndPort = 0
|
||||
testEndpointIPMatch(t, ep, "", net.ParseIP("10.2.3.4"), 6, 80, Permitted)
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
|
||||
testEndpointIPMatch(t, ep, "", net.ParseIP("10.2.3.5"), 6, 80, NoMatch)
|
||||
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.5"), 17, 443, NoMatch)
|
||||
testEndpointDomainMatch(t, ep, "example.com", Undeterminable)
|
||||
}
|
||||
|
||||
func TestEPString(t *testing.T) {
|
||||
var endpoints Endpoints
|
||||
endpoints = []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "example.com",
|
||||
Wildcard: false,
|
||||
Protocol: 6,
|
||||
Permit: true,
|
||||
Type: EptDomain,
|
||||
Value: "example.com",
|
||||
Protocol: 6,
|
||||
Permit: true,
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "8.8.8.8",
|
||||
Protocol: 17, // TCP
|
||||
StartPort: 53, // DNS
|
||||
EndPort: 53,
|
||||
Permit: false,
|
||||
Type: EptIPv4,
|
||||
Value: "1.1.1.1",
|
||||
Protocol: 17, // TCP
|
||||
StartPort: 53, // DNS
|
||||
EndPort: 53,
|
||||
Permit: false,
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "google.com",
|
||||
Wildcard: true,
|
||||
Permit: false,
|
||||
Type: EptDomain,
|
||||
Value: "example.org",
|
||||
Permit: false,
|
||||
},
|
||||
}
|
||||
if endpoints.String() != "[example.com 6/*, 8.8.8.8 17/53, google.com */*]" {
|
||||
if endpoints.String() != "[Domain:example.com 6/*, IPv4:1.1.1.1 17/53, Domain:example.org */*]" {
|
||||
t.Errorf("unexpected result: %s", endpoints.String())
|
||||
}
|
||||
|
||||
|
@ -57,5 +162,4 @@ func TestEPString(t *testing.T) {
|
|||
if noEndpoints.String() != "[]" {
|
||||
t.Errorf("unexpected result: %s", noEndpoints.String())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
package profile
|
||||
|
||||
import "github.com/Safing/portbase/modules"
|
||||
import (
|
||||
"github.com/Safing/portbase/modules"
|
||||
|
||||
// module dependencies
|
||||
_ "github.com/Safing/portmaster/core"
|
||||
)
|
||||
|
||||
var (
|
||||
shutdownSignal = make(chan struct{})
|
||||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("profile", nil, start, stop, "global", "database")
|
||||
modules.Register("profile", nil, start, stop, "core")
|
||||
}
|
||||
|
||||
func start() error {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/Safing/portmaster/status"
|
||||
|
@ -119,8 +120,28 @@ func (set *Set) CheckFlag(flag uint8) (active bool) {
|
|||
return false
|
||||
}
|
||||
|
||||
// CheckEndpoint checks if the given protocol and port are governed in any the lists of ports and returns whether it is permitted.
|
||||
func (set *Set) CheckEndpoint(domainOrIP string, protocol uint8, port uint16, inbound bool) (permit bool, reason string, ok bool) {
|
||||
// CheckEndpointDomain checks if the given endpoint matches an entry in the corresponding list. This is for outbound communication only.
|
||||
func (set *Set) CheckEndpointDomain(domain string) (result EPResult, reason string) {
|
||||
set.Lock()
|
||||
defer set.Unlock()
|
||||
|
||||
for i, profile := range set.profiles {
|
||||
if i == 2 && set.independent {
|
||||
continue
|
||||
}
|
||||
|
||||
if profile != nil {
|
||||
if result, reason = profile.Endpoints.CheckDomain(domain); result != NoMatch {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NoMatch, ""
|
||||
}
|
||||
|
||||
// CheckEndpointIP checks if the given endpoint matches an entry in the corresponding list.
|
||||
func (set *Set) CheckEndpointIP(domain string, ip net.IP, protocol uint8, port uint16, inbound bool) (result EPResult, reason string) {
|
||||
set.Lock()
|
||||
defer set.Unlock()
|
||||
|
||||
|
@ -131,18 +152,18 @@ func (set *Set) CheckEndpoint(domainOrIP string, protocol uint8, port uint16, in
|
|||
|
||||
if profile != nil {
|
||||
if inbound {
|
||||
if permit, reason, ok = profile.ServiceEndpoints.Check(domainOrIP, protocol, port, inbound, set.combinedSecurityLevel); ok {
|
||||
if result, reason = profile.ServiceEndpoints.CheckIP(domain, ip, protocol, port, inbound, set.combinedSecurityLevel); result != NoMatch {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if permit, reason, ok = profile.Endpoints.Check(domainOrIP, protocol, port, inbound, set.combinedSecurityLevel); ok {
|
||||
if result, reason = profile.Endpoints.CheckIP(domain, ip, protocol, port, inbound, set.combinedSecurityLevel); result != NoMatch {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, "", false
|
||||
return NoMatch, ""
|
||||
}
|
||||
|
||||
// getSecurityLevel returns the highest prioritized security level.
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Safing/portbase/utils/testutils"
|
||||
"github.com/Safing/portmaster/status"
|
||||
)
|
||||
|
||||
|
@ -28,31 +30,30 @@ func init() {
|
|||
},
|
||||
Endpoints: []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "good.bad.example.com.",
|
||||
Wildcard: false,
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Type: EptDomain,
|
||||
Value: "good.bad.example.com.",
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "bad.example.com.",
|
||||
Wildcard: true,
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
Type: EptDomain,
|
||||
Value: "*bad.example.com.",
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "example.com.",
|
||||
Wildcard: false,
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Type: EptDomain,
|
||||
Value: "example.com.",
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Permit: true,
|
||||
Protocol: 6,
|
||||
StartPort: 22000,
|
||||
EndPort: 22000,
|
||||
Created: time.Now().Unix(),
|
||||
Type: EptAny,
|
||||
Permit: true,
|
||||
Protocol: 6,
|
||||
StartPort: 22000,
|
||||
EndPort: 22000,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -66,36 +67,33 @@ func init() {
|
|||
// },
|
||||
Endpoints: []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "bad2.example.com.",
|
||||
Wildcard: true,
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
Type: EptDomain,
|
||||
Value: "*bad2.example.com.",
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Permit: true,
|
||||
Protocol: 6,
|
||||
StartPort: 80,
|
||||
EndPort: 80,
|
||||
Created: time.Now().Unix(),
|
||||
Type: EptAny,
|
||||
Permit: true,
|
||||
Protocol: 6,
|
||||
StartPort: 80,
|
||||
EndPort: 80,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
},
|
||||
ServiceEndpoints: []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Permit: true,
|
||||
Protocol: 17,
|
||||
StartPort: 12345,
|
||||
EndPort: 12347,
|
||||
Created: time.Now().Unix(),
|
||||
Type: EptAny,
|
||||
Permit: true,
|
||||
Protocol: 17,
|
||||
StartPort: 12345,
|
||||
EndPort: 12347,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
&EndpointPermission{ // default deny
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
Type: EptAny,
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -104,25 +102,39 @@ func init() {
|
|||
func testFlag(t *testing.T, set *Set, flag uint8, shouldBeActive bool) {
|
||||
active := set.CheckFlag(flag)
|
||||
if active != shouldBeActive {
|
||||
t.Errorf("unexpected result: flag %s: permitted=%v, expected=%v", flagNames[flag], active, shouldBeActive)
|
||||
t.Errorf("unexpected result: flag %s: active=%v, expected=%v", flagNames[flag], active, shouldBeActive)
|
||||
}
|
||||
}
|
||||
|
||||
func testEndpoint(t *testing.T, set *Set, domainOrIP string, protocol uint8, port uint16, inbound bool, shouldBePermitted bool) {
|
||||
var permitted, ok bool
|
||||
permitted, _, ok = set.CheckEndpoint(domainOrIP, protocol, port, inbound)
|
||||
if !ok {
|
||||
t.Errorf("endpoint %s/%d/%d/%v should be in test profile set", domainOrIP, protocol, port, inbound)
|
||||
}
|
||||
if permitted != shouldBePermitted {
|
||||
t.Errorf("unexpected result for endpoint %s/%d/%d/%v: permitted=%v, expected=%v", domainOrIP, protocol, port, inbound, permitted, shouldBePermitted)
|
||||
func testEndpointDomain(t *testing.T, set *Set, domain string, expectedResult EPResult) {
|
||||
var result EPResult
|
||||
result, _ = set.CheckEndpointDomain(domain)
|
||||
if result != expectedResult {
|
||||
t.Errorf(
|
||||
"line %d: unexpected result for endpoint domain %s: result=%s, expected=%s",
|
||||
testutils.GetLineNumberOfCaller(1),
|
||||
domain,
|
||||
result,
|
||||
expectedResult,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testUnregulatedEndpoint(t *testing.T, set *Set, domainOrIP string, protocol uint8, port uint16, inbound bool) {
|
||||
_, _, ok := set.CheckEndpoint(domainOrIP, protocol, port, inbound)
|
||||
if ok {
|
||||
t.Errorf("endpoint %s/%d/%d/%v should not be in test profile set", domainOrIP, protocol, port, inbound)
|
||||
func testEndpointIP(t *testing.T, set *Set, domain string, ip net.IP, protocol uint8, port uint16, inbound bool, expectedResult EPResult) {
|
||||
var result EPResult
|
||||
result, _ = set.CheckEndpointIP(domain, ip, protocol, port, inbound)
|
||||
if result != expectedResult {
|
||||
t.Errorf(
|
||||
"line %d: unexpected result for endpoint %s/%s/%d/%d/%v: result=%s, expected=%s",
|
||||
testutils.GetLineNumberOfCaller(1),
|
||||
domain,
|
||||
ip,
|
||||
protocol,
|
||||
port,
|
||||
inbound,
|
||||
result,
|
||||
expectedResult,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,28 +145,28 @@ func TestProfileSet(t *testing.T) {
|
|||
set.Update(status.SecurityLevelDynamic)
|
||||
testFlag(t, set, Whitelist, false)
|
||||
// testFlag(t, set, Internet, true)
|
||||
testEndpoint(t, set, "example.com.", 0, 0, false, true)
|
||||
testEndpoint(t, set, "bad.example.com.", 0, 0, false, false)
|
||||
testEndpoint(t, set, "other.bad.example.com.", 0, 0, false, false)
|
||||
testEndpoint(t, set, "good.bad.example.com.", 0, 0, false, true)
|
||||
testEndpoint(t, set, "bad2.example.com.", 0, 0, false, false)
|
||||
testEndpoint(t, set, "10.2.3.4", 6, 22000, false, true)
|
||||
testEndpoint(t, set, "fd00::1", 6, 22000, false, true)
|
||||
testEndpoint(t, set, "test.local.", 6, 22000, false, true)
|
||||
testUnregulatedEndpoint(t, set, "other.example.com.", 0, 0, false)
|
||||
testUnregulatedEndpoint(t, set, "10.2.3.4", 17, 53, false)
|
||||
testUnregulatedEndpoint(t, set, "10.2.3.4", 17, 443, false)
|
||||
testUnregulatedEndpoint(t, set, "10.2.3.4", 6, 12346, false)
|
||||
testEndpoint(t, set, "10.2.3.4", 17, 12345, true, true)
|
||||
testEndpoint(t, set, "fd00::1", 17, 12347, true, true)
|
||||
testEndpointDomain(t, set, "example.com.", Permitted)
|
||||
testEndpointDomain(t, set, "bad.example.com.", Denied)
|
||||
testEndpointDomain(t, set, "other.bad.example.com.", Denied)
|
||||
testEndpointDomain(t, set, "good.bad.example.com.", Permitted)
|
||||
testEndpointDomain(t, set, "bad2.example.com.", Undeterminable)
|
||||
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 6, 22000, false, Permitted)
|
||||
testEndpointIP(t, set, "", net.ParseIP("fd00::1"), 6, 22000, false, Permitted)
|
||||
testEndpointDomain(t, set, "test.local.", Undeterminable)
|
||||
testEndpointDomain(t, set, "other.example.com.", Undeterminable)
|
||||
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 17, 53, false, NoMatch)
|
||||
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 17, 443, false, NoMatch)
|
||||
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 6, 12346, false, NoMatch)
|
||||
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 17, 12345, true, Permitted)
|
||||
testEndpointIP(t, set, "", net.ParseIP("fd00::1"), 17, 12347, true, Permitted)
|
||||
|
||||
set.Update(status.SecurityLevelSecure)
|
||||
// testFlag(t, set, Internet, true)
|
||||
|
||||
set.Update(status.SecurityLevelFortress) // Independent!
|
||||
testFlag(t, set, Whitelist, true)
|
||||
testEndpoint(t, set, "10.2.3.4", 17, 12345, true, false)
|
||||
testEndpoint(t, set, "fd00::1", 17, 12347, true, false)
|
||||
testUnregulatedEndpoint(t, set, "10.2.3.4", 6, 80, false)
|
||||
testUnregulatedEndpoint(t, set, "bad2.example.com.", 0, 0, false)
|
||||
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 17, 12345, true, Denied)
|
||||
testEndpointIP(t, set, "", net.ParseIP("fd00::1"), 17, 12347, true, Denied)
|
||||
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 6, 80, false, NoMatch)
|
||||
testEndpointDomain(t, set, "bad2.example.com.", Undeterminable)
|
||||
}
|
||||
|
|
|
@ -48,8 +48,7 @@ func getSpecialProfile(ID string) (*Profile, error) {
|
|||
func ensureServiceEndpointsDenyAll(p *Profile) (changed bool) {
|
||||
for _, ep := range p.ServiceEndpoints {
|
||||
if ep != nil {
|
||||
if ep.DomainOrIP == "" &&
|
||||
ep.Wildcard == true &&
|
||||
if ep.Type == EptAny &&
|
||||
ep.Protocol == 0 &&
|
||||
ep.StartPort == 0 &&
|
||||
ep.EndPort == 0 &&
|
||||
|
@ -60,12 +59,11 @@ func ensureServiceEndpointsDenyAll(p *Profile) (changed bool) {
|
|||
}
|
||||
|
||||
p.ServiceEndpoints = append(p.ServiceEndpoints, &EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Protocol: 0,
|
||||
StartPort: 0,
|
||||
EndPort: 0,
|
||||
Permit: false,
|
||||
Type: EptAny,
|
||||
Protocol: 0,
|
||||
StartPort: 0,
|
||||
EndPort: 0,
|
||||
Permit: false,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package profile
|
|||
|
||||
import (
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/Safing/portbase/database"
|
||||
"github.com/Safing/portbase/database/query"
|
||||
|
@ -56,11 +57,27 @@ func updateListener(sub *database.Subscription) {
|
|||
switch {
|
||||
case strings.HasPrefix(profile.Key(), MakeProfileKey(UserNamespace, "")):
|
||||
updateActiveUserProfile(profile)
|
||||
increaseUpdateVersion()
|
||||
case strings.HasPrefix(profile.Key(), MakeProfileKey(StampNamespace, "")):
|
||||
updateActiveStampProfile(profile)
|
||||
increaseUpdateVersion()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
updateVersion uint32
|
||||
)
|
||||
|
||||
// GetUpdateVersion returns the current profiles internal update version
|
||||
func GetUpdateVersion() uint32 {
|
||||
return atomic.LoadUint32(&updateVersion)
|
||||
}
|
||||
|
||||
func increaseUpdateVersion() {
|
||||
// we intentially want to wrap
|
||||
atomic.AddUint32(&updateVersion, 1)
|
||||
}
|
||||
|
|
|
@ -18,16 +18,16 @@ func max(a, b uint8) uint8 {
|
|||
|
||||
// ConfigIsActive returns whether the given security level dependent config option is on or off.
|
||||
func ConfigIsActive(name string) SecurityLevelOption {
|
||||
activeAtLevel := config.GetAsInt(name, int64(SecurityLevelDynamic))
|
||||
activeAtLevel := config.GetAsInt(name, int64(SecurityLevelsAll))
|
||||
return func(minSecurityLevel uint8) bool {
|
||||
return uint8(activeAtLevel()) <= max(ActiveSecurityLevel(), minSecurityLevel)
|
||||
return uint8(activeAtLevel())&max(ActiveSecurityLevel(), minSecurityLevel) > 0
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigIsActiveConcurrent returns whether the given security level dependent config option is on or off and is concurrency safe.
|
||||
func ConfigIsActiveConcurrent(name string) SecurityLevelOption {
|
||||
activeAtLevel := config.Concurrent.GetAsInt(name, int64(SecurityLevelDynamic))
|
||||
activeAtLevel := config.Concurrent.GetAsInt(name, int64(SecurityLevelsAll))
|
||||
return func(minSecurityLevel uint8) bool {
|
||||
return uint8(activeAtLevel()) <= max(ActiveSecurityLevel(), minSecurityLevel)
|
||||
return uint8(activeAtLevel())&max(ActiveSecurityLevel(), minSecurityLevel) > 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import (
|
|||
"github.com/Safing/portbase/database"
|
||||
"github.com/Safing/portbase/log"
|
||||
"github.com/Safing/portbase/modules"
|
||||
|
||||
// module dependencies
|
||||
_ "github.com/Safing/portmaster/core"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -11,7 +14,7 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("status", nil, start, stop, "database")
|
||||
modules.Register("status", nil, start, stop, "core")
|
||||
}
|
||||
|
||||
func start() error {
|
||||
|
|
6
threats/all.go
Normal file
6
threats/all.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package threats
|
||||
|
||||
import (
|
||||
_ "github.com/Safing/portmaster/threats/arp"
|
||||
_ "github.com/Safing/portmaster/threats/portscan"
|
||||
)
|
7
threats/arp/arpentry.go
Normal file
7
threats/arp/arpentry.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package arp
|
||||
|
||||
type arpEntry struct {
|
||||
IP string
|
||||
MAC string
|
||||
Interface string
|
||||
}
|
48
threats/arp/os_linux.go
Normal file
48
threats/arp/os_linux.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package arp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/portbase/log"
|
||||
)
|
||||
|
||||
const (
|
||||
arpTableProcFile = "/proc/net/arp"
|
||||
)
|
||||
|
||||
func getArpTable() (table []*arpEntry, err error) {
|
||||
// open file
|
||||
arpData, err := os.Open(arpTableProcFile)
|
||||
if err != nil {
|
||||
log.Warningf("threats/arp: could not read %s: %s", arpTableProcFile, err)
|
||||
return nil, err
|
||||
}
|
||||
defer arpData.Close()
|
||||
|
||||
// file scanner
|
||||
scanner := bufio.NewScanner(arpData)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
// parse
|
||||
scanner.Scan() // skip first line
|
||||
for scanner.Scan() {
|
||||
line := strings.Fields(scanner.Text())
|
||||
if len(line) < 6 {
|
||||
continue
|
||||
}
|
||||
|
||||
table = append(table, &arpEntry{
|
||||
IP: line[0],
|
||||
MAC: line[3],
|
||||
Interface: line[5],
|
||||
})
|
||||
}
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func clearArpTable() error {
|
||||
return nil
|
||||
}
|
1
threats/portscan/detection.go
Normal file
1
threats/portscan/detection.go
Normal file
|
@ -0,0 +1 @@
|
|||
package portscan
|
1
threats/portscan/module.go
Normal file
1
threats/portscan/module.go
Normal file
|
@ -0,0 +1 @@
|
|||
package portscan
|
|
@ -103,6 +103,7 @@ func ServeFileFromBundle(w http.ResponseWriter, r *http.Request, bundleName stri
|
|||
return
|
||||
}
|
||||
|
||||
// set content type
|
||||
_, ok := w.Header()["Content-Type"]
|
||||
if !ok {
|
||||
contentType := mime.TypeByExtension(filepath.Ext(path))
|
||||
|
@ -111,6 +112,9 @@ func ServeFileFromBundle(w http.ResponseWriter, r *http.Request, bundleName stri
|
|||
}
|
||||
}
|
||||
|
||||
// set content security policy
|
||||
w.Header().Set("Content-Security-Policy", "default-src 'self'")
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if r.Method != "HEAD" {
|
||||
_, err = io.Copy(w, readCloser)
|
||||
|
|
|
@ -7,7 +7,11 @@ import (
|
|||
"runtime"
|
||||
|
||||
"github.com/Safing/portbase/database"
|
||||
"github.com/Safing/portbase/info"
|
||||
"github.com/Safing/portbase/modules"
|
||||
|
||||
// module dependencies
|
||||
_ "github.com/Safing/portmaster/core"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -15,10 +19,11 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("updates", prep, start, nil, "global", "database")
|
||||
modules.Register("updates", prep, start, nil, "core")
|
||||
}
|
||||
|
||||
func prep() error {
|
||||
status.Core = info.GetInfo()
|
||||
updateStoragePath = filepath.Join(database.GetDatabaseRoot(), "updates")
|
||||
|
||||
err := checkUpdateDirs()
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/Safing/portbase/database"
|
||||
"github.com/Safing/portbase/database/query"
|
||||
"github.com/Safing/portbase/database/record"
|
||||
"github.com/Safing/portbase/info"
|
||||
"github.com/Safing/portbase/log"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
@ -36,7 +37,7 @@ var (
|
|||
|
||||
func init() {
|
||||
status = &versionStatus{
|
||||
Versions: make(map[string]*versionStatusEntry),
|
||||
Modules: make(map[string]*versionStatusEntry),
|
||||
}
|
||||
status.SetKey(statusDBKey)
|
||||
}
|
||||
|
@ -45,7 +46,9 @@ func init() {
|
|||
type versionStatus struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
Versions map[string]*versionStatusEntry
|
||||
|
||||
Core *info.Info
|
||||
Modules map[string]*versionStatusEntry
|
||||
}
|
||||
|
||||
func (vs *versionStatus) save() {
|
||||
|
@ -68,10 +71,10 @@ func updateUsedStatus(identifier string, version string) {
|
|||
status.Lock()
|
||||
defer status.Unlock()
|
||||
|
||||
entry, ok := status.Versions[identifier]
|
||||
entry, ok := status.Modules[identifier]
|
||||
if !ok {
|
||||
entry = &versionStatusEntry{}
|
||||
status.Versions[identifier] = entry
|
||||
status.Modules[identifier] = entry
|
||||
}
|
||||
|
||||
entry.LastVersionUsed = version
|
||||
|
@ -87,10 +90,10 @@ func updateStatus(vClass versionClass, state map[string]string) {
|
|||
|
||||
for identifier, version := range state {
|
||||
|
||||
entry, ok := status.Versions[identifier]
|
||||
entry, ok := status.Modules[identifier]
|
||||
if !ok {
|
||||
entry = &versionStatusEntry{}
|
||||
status.Versions[identifier] = entry
|
||||
status.Modules[identifier] = entry
|
||||
}
|
||||
|
||||
switch vClass {
|
||||
|
|
Loading…
Add table
Reference in a new issue