mirror of
https://github.com/safing/portmaster
synced 2025-09-01 01:59:11 +00:00
Working on portmaster restructure
This commit is contained in:
parent
8fb21fd900
commit
8c11a35590
24 changed files with 850 additions and 554 deletions
|
@ -1,15 +1,14 @@
|
||||||
package firewall
|
package firewall
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Safing/portbase/log"
|
"github.com/Safing/portbase/log"
|
||||||
"github.com/Safing/portmaster/intel"
|
"github.com/Safing/portmaster/intel"
|
||||||
"github.com/Safing/portmaster/network"
|
"github.com/Safing/portmaster/network"
|
||||||
"github.com/Safing/portmaster/network/netutils"
|
|
||||||
"github.com/Safing/portmaster/network/packet"
|
"github.com/Safing/portmaster/network/packet"
|
||||||
|
"github.com/Safing/portmaster/status"
|
||||||
|
|
||||||
"github.com/agext/levenshtein"
|
"github.com/agext/levenshtein"
|
||||||
)
|
)
|
||||||
|
@ -25,6 +24,7 @@ import (
|
||||||
// 4. DecideOnLink
|
// 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 connection 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) {
|
func DecideOnConnectionBeforeIntel(connection *network.Connection, fqdn string) {
|
||||||
// check:
|
// check:
|
||||||
// Profile.DomainWhitelist
|
// Profile.DomainWhitelist
|
||||||
|
@ -35,244 +35,227 @@ func DecideOnConnectionBeforeIntel(connection *network.Connection, fqdn string)
|
||||||
// grant self
|
// grant self
|
||||||
if connection.Process().Pid == os.Getpid() {
|
if connection.Process().Pid == os.Getpid() {
|
||||||
log.Infof("firewall: granting own connection %s", connection)
|
log.Infof("firewall: granting own connection %s", connection)
|
||||||
connection.Accept()
|
connection.Accept("")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there is a profile
|
// check if there is a profile
|
||||||
profileSet := connection.Process().ProfileSetSet
|
profileSet := connection.Process().ProfileSet()
|
||||||
if profile == nil {
|
if profileSet == nil {
|
||||||
log.Infof("firewall: no profile, denying connection %s", connection)
|
log.Errorf("firewall: denying connection %s, no profile set", connection)
|
||||||
connection.AddReason("no profile")
|
connection.Deny("no profile set")
|
||||||
connection.Block()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
profileSet.Update(status.CurrentSecurityLevel())
|
||||||
// check user class
|
|
||||||
if profileSet.CheckFlag(profile.System) {
|
|
||||||
if !connection.Process().IsSystem() {
|
|
||||||
log.Infof("firewall: denying connection %s, profile has System flag set, but process is not executed by System", connection)
|
|
||||||
connection.AddReason("must be executed by system")
|
|
||||||
connection.Block()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if profileSet.CheckFlag(profile.Admin) {
|
|
||||||
if !connection.Process().IsAdmin() {
|
|
||||||
log.Infof("firewall: denying connection %s, profile has Admin flag set, but process is not executed by Admin", connection)
|
|
||||||
connection.AddReason("must be executed by admin")
|
|
||||||
connection.Block()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if profileSet.CheckFlag(profile.User) {
|
|
||||||
if !connection.Process().IsUser() {
|
|
||||||
log.Infof("firewall: denying connection %s, profile has User flag set, but process is not executed by a User", connection)
|
|
||||||
connection.AddReason("must be executed by user")
|
|
||||||
connection.Block()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for any network access
|
// check for any network access
|
||||||
if !profileSet.CheckFlag(profile.Internet) && !profileSet.CheckFlag(profile.LocalNet) {
|
if !profileSet.CheckFlag(profile.Internet) && !profileSet.CheckFlag(profile.LAN) {
|
||||||
log.Infof("firewall: denying connection %s, profile denies Internet and local network access", connection)
|
log.Infof("firewall: denying connection %s, accessing Internet or LAN not allowed", connection)
|
||||||
connection.Block()
|
connection.Deny("accessing Internet or LAN not allowed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check domain whitelist/blacklist
|
// check domain list
|
||||||
if len(profile.DomainWhitelist) > 0 {
|
permitted, ok := profileSet.CheckDomain(fqdn)
|
||||||
matched := false
|
if ok {
|
||||||
for _, entry := range profile.DomainWhitelist {
|
if permitted {
|
||||||
if !strings.HasSuffix(entry, ".") {
|
log.Infof("firewall: accepting connection %s, domain is whitelisted", connection, domainElement, processElement)
|
||||||
entry += "."
|
connection.Accept("domain is whitelisted")
|
||||||
}
|
|
||||||
if strings.HasPrefix(entry, "*") {
|
|
||||||
if strings.HasSuffix(fqdn, strings.Trim(entry, "*")) {
|
|
||||||
matched = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if entry == fqdn {
|
|
||||||
matched = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if matched {
|
|
||||||
if profile.DomainWhitelistIsBlacklist {
|
|
||||||
log.Infof("firewall: denying connection %s, profile has %s in domain blacklist", connection, fqdn)
|
|
||||||
connection.AddReason("domain blacklisted")
|
|
||||||
connection.Block()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if !profile.DomainWhitelistIsBlacklist {
|
log.Infof("firewall: denying connection %s, domain is blacklisted", connection, domainElement, processElement)
|
||||||
log.Infof("firewall: denying connection %s, profile does not have %s in domain whitelist", connection, fqdn)
|
connection.Deny("domain is blacklisted")
|
||||||
connection.AddReason("domain not in whitelist")
|
}
|
||||||
connection.Block()
|
return
|
||||||
return
|
}
|
||||||
|
|
||||||
|
switch profileSet.GetProfileMode() {
|
||||||
|
case profile.Whitelist:
|
||||||
|
log.Infof("firewall: denying connection %s, domain is not whitelisted", connection, domainElement, processElement)
|
||||||
|
connection.Deny("domain is not whitelisted")
|
||||||
|
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
|
||||||
|
// only look at the last two path segments
|
||||||
|
if len(pathElements) > 2 {
|
||||||
|
pathElements = pathElements[len(pathElements)-2:]
|
||||||
}
|
}
|
||||||
}
|
domainElements := strings.Split(fqdn, ".")
|
||||||
}
|
|
||||||
|
|
||||||
}
|
var domainElement string
|
||||||
|
var processElement string
|
||||||
|
|
||||||
func DecideOnConnectionAfterIntel(connection *network.Connection, fqdn string, rrCache *intel.RRCache) *intel.RRCache {
|
matchLoop:
|
||||||
// check:
|
for _, domainElement = range domainElements {
|
||||||
// TODO: Profile.ClassificationBlacklist
|
for _, pathElement := range pathElements {
|
||||||
// TODO: Profile.ClassificationWhitelist
|
if levenshtein.Match(domainElement, pathElement, nil) > 0.5 {
|
||||||
// Profile.Flags
|
matched = true
|
||||||
// - network specific: Strict
|
processElement = pathElement
|
||||||
|
break matchLoop
|
||||||
// check if there is a profile
|
}
|
||||||
profileSet := connection.Process().ProfileSet
|
}
|
||||||
// FIXME: there should always be a profile
|
if levenshtein.Match(domainElement, profile.Name, nil) > 0.5 {
|
||||||
if profileSet == nil {
|
|
||||||
log.Infof("firewall: no profile, denying connection %s", connection)
|
|
||||||
connection.AddReason("no profile")
|
|
||||||
connection.Block()
|
|
||||||
return rrCache
|
|
||||||
}
|
|
||||||
|
|
||||||
// check Strict flag
|
|
||||||
// TODO: drastically improve this!
|
|
||||||
if profileSet.CheckFlag(profile.Related) {
|
|
||||||
matched := false
|
|
||||||
pathElements := strings.Split(connection.Process().Path, "/")
|
|
||||||
if len(pathElements) > 2 {
|
|
||||||
pathElements = pathElements[len(pathElements)-2:]
|
|
||||||
}
|
|
||||||
domainElements := strings.Split(fqdn, ".")
|
|
||||||
matchLoop:
|
|
||||||
for _, domainElement := range domainElements {
|
|
||||||
for _, pathElement := range pathElements {
|
|
||||||
if levenshtein.Match(domainElement, pathElement, nil) > 0.5 {
|
|
||||||
matched = true
|
matched = true
|
||||||
|
processElement = profile.Name
|
||||||
|
break matchLoop
|
||||||
|
}
|
||||||
|
if levenshtein.Match(domainElement, connection.Process().Name, nil) > 0.5 {
|
||||||
|
matched = true
|
||||||
|
processElement = connection.Process().Name
|
||||||
break matchLoop
|
break matchLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if levenshtein.Match(domainElement, profile.Name, nil) > 0.5 {
|
|
||||||
matched = true
|
if matched {
|
||||||
break matchLoop
|
log.Infof("firewall: accepting connection %s, match to domain was found: %s ~= %s", connection, domainElement, processElement)
|
||||||
}
|
connection.Accept("domain is related to process")
|
||||||
if levenshtein.Match(domainElement, connection.Process().Name, nil) > 0.5 {
|
|
||||||
matched = true
|
|
||||||
break matchLoop
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !matched {
|
|
||||||
log.Infof("firewall: denying connection %s, profile has declared Strict flag and no match to domain was found", connection)
|
if connection.Verdict != network.ACCEPT {
|
||||||
connection.AddReason("domain does not relate to process")
|
// TODO
|
||||||
connection.Block()
|
log.Infof("firewall: accepting connection %s, domain permitted (prompting is not yet implemented)", connection, domainElement, processElement)
|
||||||
return rrCache
|
connection.Accept("domain permitted (prompting is not yet implemented)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case profile.Blacklist:
|
||||||
|
log.Infof("firewall: denying connection %s, domain is not blacklisted", connection, domainElement, processElement)
|
||||||
|
connection.Deny("domain is not blacklisted")
|
||||||
}
|
}
|
||||||
|
|
||||||
// tunneling
|
|
||||||
// TODO: link this to real status
|
|
||||||
// gate17Active := mode.Client()
|
|
||||||
// if gate17Active {
|
|
||||||
// tunnelInfo, err := AssignTunnelIP(fqdn)
|
|
||||||
// if err != nil {
|
|
||||||
// log.Errorf("portmaster: could not get tunnel IP for routing %s: %s", connection, err)
|
|
||||||
// return nil // return nxDomain
|
|
||||||
// }
|
|
||||||
// // save original reply
|
|
||||||
// tunnelInfo.RRCache = rrCache
|
|
||||||
// // return tunnel IP
|
|
||||||
// return tunnelInfo.ExportTunnelIP()
|
|
||||||
// }
|
|
||||||
|
|
||||||
return rrCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecideOnConnection(connection *network.Connection, pkt packet.Packet) {
|
// DecideOnConnectionAfterIntel makes a decision about a connection after the dns query is resolved and intel is gathered.
|
||||||
// check:
|
func DecideOnConnectionAfterIntel(connection *network.Connection, fqdn string, rrCache *intel.RRCache) *intel.RRCache {
|
||||||
// Profile.Flags
|
|
||||||
// - process specific: System, Admin, User
|
|
||||||
// - network specific: Internet, LocalNet, Service, Directconnect
|
|
||||||
|
|
||||||
// grant self
|
// grant self
|
||||||
if connection.Process().Pid == os.Getpid() {
|
if connection.Process().Pid == os.Getpid() {
|
||||||
log.Infof("firewall: granting own connection %s", connection)
|
log.Infof("firewall: granting own connection %s", connection)
|
||||||
connection.Accept()
|
connection.Accept("")
|
||||||
|
return rrCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there is a profile
|
||||||
|
profileSet := connection.Process().ProfileSet()
|
||||||
|
if profileSet == nil {
|
||||||
|
log.Errorf("firewall: denying connection %s, no profile set", connection)
|
||||||
|
connection.Deny("no profile")
|
||||||
|
return rrCache
|
||||||
|
}
|
||||||
|
profileSet.Update(status.CurrentSecurityLevel())
|
||||||
|
|
||||||
|
// TODO: Stamp integration
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
|
||||||
|
// grant self
|
||||||
|
if connection.Process().Pid == os.Getpid() {
|
||||||
|
log.Infof("firewall: granting own connection %s", connection)
|
||||||
|
connection.Accept("")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there is a profile
|
// check if there is a profile
|
||||||
profileSet := connection.Process().ProfileSet
|
profileSet := connection.Process().ProfileSet
|
||||||
if profile == nil {
|
if profile == nil {
|
||||||
log.Infof("firewall: no profile, denying connection %s", connection)
|
log.Errorf("firewall: denying connection %s, no profile set", connection)
|
||||||
connection.AddReason("no profile")
|
connection.Deny("no profile")
|
||||||
connection.Block()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// check user class
|
|
||||||
if profileSet.CheckFlag(profile.System) {
|
|
||||||
if !connection.Process().IsSystem() {
|
|
||||||
log.Infof("firewall: denying connection %s, profile has System flag set, but process is not executed by System", connection)
|
|
||||||
connection.AddReason("must be executed by system")
|
|
||||||
connection.Block()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if profileSet.CheckFlag(profile.Admin) {
|
|
||||||
if !connection.Process().IsAdmin() {
|
|
||||||
log.Infof("firewall: denying connection %s, profile has Admin flag set, but process is not executed by Admin", connection)
|
|
||||||
connection.AddReason("must be executed by admin")
|
|
||||||
connection.Block()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if profileSet.CheckFlag(profile.User) {
|
|
||||||
if !connection.Process().IsUser() {
|
|
||||||
log.Infof("firewall: denying connection %s, profile has User flag set, but process is not executed by a User", connection)
|
|
||||||
connection.AddReason("must be executed by user")
|
|
||||||
connection.Block()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for any network access
|
|
||||||
if !profileSet.CheckFlag(profile.Internet) && !profileSet.CheckFlag(profile.LocalNet) {
|
|
||||||
log.Infof("firewall: denying connection %s, profile denies Internet and local network access", connection)
|
|
||||||
connection.AddReason("no network access allowed")
|
|
||||||
connection.Block()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
profileSet.Update(status.CurrentSecurityLevel())
|
||||||
|
|
||||||
|
// check connection type
|
||||||
switch connection.Domain {
|
switch connection.Domain {
|
||||||
case "I":
|
case IncomingHost, IncomingLAN, IncomingInternet, IncomingInvalid:
|
||||||
// check Service flag
|
|
||||||
if !profileSet.CheckFlag(profile.Service) {
|
if !profileSet.CheckFlag(profile.Service) {
|
||||||
log.Infof("firewall: denying connection %s, profile does not declare service", connection)
|
log.Infof("firewall: denying connection %s, not a service", connection)
|
||||||
connection.AddReason("not a service")
|
if connection.Domain == IncomingHost {
|
||||||
connection.Drop()
|
connection.Block("not a service")
|
||||||
|
} else {
|
||||||
|
connection.Drop("not a service")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// check if incoming connections are allowed on any port, but only if there no other restrictions
|
case PeerLAN, PeerInternet, PeerInvalid: // Important: PeerHost is and should be missing!
|
||||||
if !!profileSet.CheckFlag(profile.Internet) && !!profileSet.CheckFlag(profile.LocalNet) && len(profile.ListenPorts) == 0 {
|
|
||||||
log.Infof("firewall: granting connection %s, profile allows incoming connections from anywhere and on any port", connection)
|
|
||||||
connection.Accept()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case "D":
|
|
||||||
// check PeerToPeer flag
|
|
||||||
if !profileSet.CheckFlag(profile.PeerToPeer) {
|
if !profileSet.CheckFlag(profile.PeerToPeer) {
|
||||||
log.Infof("firewall: denying connection %s, profile does not declare direct connections", connection)
|
log.Infof("firewall: denying connection %s, peer to peer connections (to an IP) not allowed", connection)
|
||||||
connection.AddReason("direct connections (without DNS) not allowed")
|
connection.Deny("peer to peer connections (to an IP) not allowed")
|
||||||
connection.Drop()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("firewall: could not decide on connection %s, deciding on per-link basis", connection)
|
// check network scope
|
||||||
connection.CantSay()
|
switch connection.Domain {
|
||||||
|
case IncomingHost:
|
||||||
|
if !profileSet.CheckFlag(profile.Localhost) {
|
||||||
|
log.Infof("firewall: denying connection %s, serving localhost not allowed", connection)
|
||||||
|
connection.Block("serving localhost not allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case IncomingLAN:
|
||||||
|
if !profileSet.CheckFlag(profile.LAN) {
|
||||||
|
log.Infof("firewall: denying connection %s, serving LAN not allowed", connection)
|
||||||
|
connection.Deny("serving LAN not allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case IncomingInternet:
|
||||||
|
if !profileSet.CheckFlag(profile.Internet) {
|
||||||
|
log.Infof("firewall: denying connection %s, serving Internet not allowed", connection)
|
||||||
|
connection.Deny("serving Internet not allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case IncomingInvalid:
|
||||||
|
log.Infof("firewall: denying connection %s, invalid IP address", connection)
|
||||||
|
connection.Drop("invalid IP address")
|
||||||
|
return
|
||||||
|
case PeerHost:
|
||||||
|
if !profileSet.CheckFlag(profile.Localhost) {
|
||||||
|
log.Infof("firewall: denying connection %s, accessing localhost not allowed", connection)
|
||||||
|
connection.Block("accessing localhost not allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case 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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case 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")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case PeerInvalid:
|
||||||
|
log.Infof("firewall: denying connection %s, invalid IP address", connection)
|
||||||
|
connection.Deny("invalid IP address")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("firewall: accepting connection %s", connection)
|
||||||
|
connection.Accept()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DecideOnLink makes a decision about a link with the first packet.
|
||||||
func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet.Packet) {
|
func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet.Packet) {
|
||||||
// check:
|
// check:
|
||||||
// Profile.Flags
|
// Profile.Flags
|
||||||
|
@ -284,107 +267,44 @@ func DecideOnLink(connection *network.Connection, link *network.Link, pkt packet
|
||||||
profileSet := connection.Process().ProfileSet
|
profileSet := connection.Process().ProfileSet
|
||||||
if profile == nil {
|
if profile == nil {
|
||||||
log.Infof("firewall: no profile, denying %s", link)
|
log.Infof("firewall: no profile, denying %s", link)
|
||||||
link.AddReason("no profile")
|
link.Block("no profile")
|
||||||
link.UpdateVerdict(network.BLOCK)
|
return
|
||||||
|
}
|
||||||
|
profileSet.Update(status.CurrentSecurityLevel())
|
||||||
|
|
||||||
|
// get remote Port
|
||||||
|
protocol := pkt.GetIPHeader().Protocol
|
||||||
|
var remotePort uint16
|
||||||
|
tcpUdpHeader := pkt.GetTCPUDPHeader()
|
||||||
|
if tcpUdpHeader != nil {
|
||||||
|
remotePort = tcpUdpHeader.DstPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// check port list
|
||||||
|
permitted, ok := profileSet.CheckPort(connection.Direction, protocol, remotePort)
|
||||||
|
if ok {
|
||||||
|
if permitted {
|
||||||
|
log.Infof("firewall: accepting link %s", link)
|
||||||
|
link.Accept("port whitelisted")
|
||||||
|
} else {
|
||||||
|
log.Infof("firewall: denying link %s: port %d is blacklisted", link, remotePort)
|
||||||
|
link.Deny("port blacklisted")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check LocalNet and Internet flags
|
switch profileSet.GetProfileMode() {
|
||||||
var remoteIP net.IP
|
case profile.Whitelist:
|
||||||
if connection.Direction {
|
log.Infof("firewall: denying link %s: port %d is not whitelisted", link, remotePort)
|
||||||
remoteIP = pkt.GetIPHeader().Src
|
link.Deny("port is not whitelisted")
|
||||||
} else {
|
case profile.Prompt:
|
||||||
remoteIP = pkt.GetIPHeader().Dst
|
log.Infof("firewall: denying link %s: port %d is blacklisted", link, remotePort)
|
||||||
}
|
link.Accept("port permitted (prompting is not yet implemented)")
|
||||||
if netutils.IPIsLocal(remoteIP) {
|
case profile.Blacklist:
|
||||||
if !profileSet.CheckFlag(profile.LocalNet) {
|
log.Infof("firewall: denying link %s: port %d is blacklisted", link, remotePort)
|
||||||
log.Infof("firewall: dropping link %s, profile does not allow communication in the local network", link)
|
link.Deny("port is not blacklisted")
|
||||||
link.AddReason("profile does not allow access to local network")
|
|
||||||
link.UpdateVerdict(network.BLOCK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !profileSet.CheckFlag(profile.Internet) {
|
|
||||||
log.Infof("firewall: dropping link %s, profile does not allow communication with the Internet", link)
|
|
||||||
link.AddReason("profile does not allow access to the Internet")
|
|
||||||
link.UpdateVerdict(network.BLOCK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check connect ports
|
|
||||||
if connection.Domain != "I" && len(profile.ConnectPorts) > 0 {
|
|
||||||
|
|
||||||
tcpUdpHeader := pkt.GetTCPUDPHeader()
|
|
||||||
if tcpUdpHeader == nil {
|
|
||||||
log.Infof("firewall: blocking link %s, profile has declared connect port whitelist, but link is not TCP/UDP", link)
|
|
||||||
link.AddReason("profile has declared connect port whitelist, but link is not TCP/UDP")
|
|
||||||
link.UpdateVerdict(network.BLOCK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// packet *should* be outbound, but we could be deciding on an already active connection.
|
|
||||||
var remotePort uint16
|
|
||||||
if connection.Direction {
|
|
||||||
remotePort = tcpUdpHeader.SrcPort
|
|
||||||
} else {
|
|
||||||
remotePort = tcpUdpHeader.DstPort
|
|
||||||
}
|
|
||||||
|
|
||||||
matched := false
|
|
||||||
for _, port := range profile.ConnectPorts {
|
|
||||||
if remotePort == port {
|
|
||||||
matched = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !matched {
|
|
||||||
log.Infof("firewall: blocking link %s, remote port %d not in profile connect port whitelist", link, remotePort)
|
|
||||||
link.AddReason("destination port not in whitelist")
|
|
||||||
link.UpdateVerdict(network.BLOCK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// check listen ports
|
|
||||||
if connection.Domain == "I" && len(profile.ListenPorts) > 0 {
|
|
||||||
|
|
||||||
tcpUdpHeader := pkt.GetTCPUDPHeader()
|
|
||||||
if tcpUdpHeader == nil {
|
|
||||||
log.Infof("firewall: dropping link %s, profile has declared listen port whitelist, but link is not TCP/UDP", link)
|
|
||||||
link.AddReason("profile has declared listen port whitelist, but link is not TCP/UDP")
|
|
||||||
link.UpdateVerdict(network.DROP)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// packet *should* be inbound, but we could be deciding on an already active connection.
|
|
||||||
var localPort uint16
|
|
||||||
if connection.Direction {
|
|
||||||
localPort = tcpUdpHeader.DstPort
|
|
||||||
} else {
|
|
||||||
localPort = tcpUdpHeader.SrcPort
|
|
||||||
}
|
|
||||||
|
|
||||||
matched := false
|
|
||||||
for _, port := range profile.ListenPorts {
|
|
||||||
if localPort == port {
|
|
||||||
matched = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !matched {
|
|
||||||
log.Infof("firewall: blocking link %s, local port %d not in profile listen port whitelist", link, localPort)
|
|
||||||
link.AddReason("listen port not in whitelist")
|
|
||||||
link.UpdateVerdict(network.BLOCK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("firewall: accepting link %s", link)
|
log.Infof("firewall: accepting link %s", link)
|
||||||
link.UpdateVerdict(network.ACCEPT)
|
link.Accept("")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ type NameRecord struct {
|
||||||
Ns []string
|
Ns []string
|
||||||
Extra []string
|
Extra []string
|
||||||
TTL int64
|
TTL int64
|
||||||
|
Filtered bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeNameRecordKey(domain string, question string) string {
|
func makeNameRecordKey(domain string, question string) string {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
|
|
||||||
"github.com/Safing/portbase/database"
|
"github.com/Safing/portbase/database"
|
||||||
"github.com/Safing/portbase/log"
|
"github.com/Safing/portbase/log"
|
||||||
|
"github.com/Safing/portmaster/network/netutils"
|
||||||
"github.com/Safing/portmaster/status"
|
"github.com/Safing/portmaster/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -76,26 +77,6 @@ func Resolve(fqdn string, qtype dns.Type, securityLevel uint8) *RRCache {
|
||||||
// timed := time.Now()
|
// timed := time.Now()
|
||||||
// defer log.Tracef("intel: took %s to get resolve %s%s", time.Now().Sub(timed).String(), fqdn, qtype.String())
|
// defer log.Tracef("intel: took %s to get resolve %s%s", time.Now().Sub(timed).String(), fqdn, qtype.String())
|
||||||
|
|
||||||
// handle request for localhost
|
|
||||||
if fqdn == "localhost." {
|
|
||||||
var rr dns.RR
|
|
||||||
var err error
|
|
||||||
switch uint16(qtype) {
|
|
||||||
case dns.TypeA:
|
|
||||||
rr, err = dns.NewRR("localhost. 17 IN A 127.0.0.1")
|
|
||||||
case dns.TypeAAAA:
|
|
||||||
rr, err = dns.NewRR("localhost. 17 IN AAAA ::1")
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &RRCache{
|
|
||||||
Answer: []dns.RR{rr},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check cache
|
// check cache
|
||||||
rrCache, err := GetRRCache(fqdn, qtype)
|
rrCache, err := GetRRCache(fqdn, qtype)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -322,6 +303,14 @@ func tryResolver(resolver *Resolver, lastFailBoundary int64, fqdn string, qtype
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
resolver.Initialized.SetToIf(false, true)
|
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
|
return rrCache, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,11 +357,11 @@ func query(resolver *Resolver, fqdn string, qtype dns.Type) (*RRCache, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
new := &RRCache{
|
new := &RRCache{
|
||||||
Domain: fqdn,
|
Domain: fqdn,
|
||||||
Question: qtype,
|
Question: qtype,
|
||||||
Answer: reply.Answer,
|
Answer: reply.Answer,
|
||||||
Ns: reply.Ns,
|
Ns: reply.Ns,
|
||||||
Extra: reply.Extra,
|
Extra: reply.Extra,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check if reply.Answer is valid
|
// TODO: check if reply.Answer is valid
|
||||||
|
|
|
@ -26,6 +26,7 @@ type Resolver struct {
|
||||||
ServerType string
|
ServerType string
|
||||||
ServerAddress string
|
ServerAddress string
|
||||||
ServerIP net.IP
|
ServerIP net.IP
|
||||||
|
ServerIPScope int8
|
||||||
ServerPort uint16
|
ServerPort uint16
|
||||||
VerifyDomain string
|
VerifyDomain string
|
||||||
Source string
|
Source string
|
||||||
|
@ -151,6 +152,7 @@ configuredServersLoop:
|
||||||
ServerType: parts[0],
|
ServerType: parts[0],
|
||||||
ServerAddress: parts[1],
|
ServerAddress: parts[1],
|
||||||
ServerIP: ip,
|
ServerIP: ip,
|
||||||
|
ServerIPScope: netutils.ClassifyAddress(ip),
|
||||||
ServerPort: port,
|
ServerPort: port,
|
||||||
LastFail: &lastFail,
|
LastFail: &lastFail,
|
||||||
Source: "config",
|
Source: "config",
|
||||||
|
@ -205,6 +207,7 @@ assignedServersLoop:
|
||||||
ServerType: "dns",
|
ServerType: "dns",
|
||||||
ServerAddress: urlFormatAddress(nameserver.IP, 53),
|
ServerAddress: urlFormatAddress(nameserver.IP, 53),
|
||||||
ServerIP: nameserver.IP,
|
ServerIP: nameserver.IP,
|
||||||
|
ServerIPScope: netutils.ClassifyAddress(nameserver.IP),
|
||||||
ServerPort: 53,
|
ServerPort: 53,
|
||||||
LastFail: &lastFail,
|
LastFail: &lastFail,
|
||||||
Source: "dhcp",
|
Source: "dhcp",
|
||||||
|
@ -213,7 +216,7 @@ assignedServersLoop:
|
||||||
}
|
}
|
||||||
new.clientManager = newDNSClientManager(new)
|
new.clientManager = newDNSClientManager(new)
|
||||||
|
|
||||||
if netutils.IPIsLocal(nameserver.IP) && len(nameserver.Search) > 0 {
|
if netutils.IPIsLAN(nameserver.IP) && len(nameserver.Search) > 0 {
|
||||||
// only allow searches for local resolvers
|
// only allow searches for local resolvers
|
||||||
var newSearch []string
|
var newSearch []string
|
||||||
for _, value := range nameserver.Search {
|
for _, value := range nameserver.Search {
|
||||||
|
@ -236,7 +239,7 @@ assignedServersLoop:
|
||||||
// make list with local resolvers
|
// make list with local resolvers
|
||||||
localResolvers = make([]*Resolver, 0)
|
localResolvers = make([]*Resolver, 0)
|
||||||
for _, resolver := range globalResolvers {
|
for _, resolver := range globalResolvers {
|
||||||
if resolver.ServerIP != nil && netutils.IPIsLocal(resolver.ServerIP) {
|
if resolver.ServerIP != nil && netutils.IPIsLAN(resolver.ServerIP) {
|
||||||
localResolvers = append(localResolvers, resolver)
|
localResolvers = append(localResolvers, resolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
110
intel/rrcache.go
110
intel/rrcache.go
|
@ -3,9 +3,13 @@
|
||||||
package intel
|
package intel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Safing/portbase/log"
|
||||||
|
"github.com/Safing/portmaster/network/netutils"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,6 +26,7 @@ type RRCache struct {
|
||||||
updated int64
|
updated int64
|
||||||
servedFromCache bool
|
servedFromCache bool
|
||||||
requestingNew bool
|
requestingNew bool
|
||||||
|
Filtered bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean sets all TTLs to 17 and sets cache expiry with specified minimum.
|
// Clean sets all TTLs to 17 and sets cache expiry with specified minimum.
|
||||||
|
@ -77,6 +82,7 @@ func (m *RRCache) ToNameRecord() *NameRecord {
|
||||||
Domain: m.Domain,
|
Domain: m.Domain,
|
||||||
Question: m.Question.String(),
|
Question: m.Question.String(),
|
||||||
TTL: m.TTL,
|
TTL: m.TTL,
|
||||||
|
Filtered: m.Filtered,
|
||||||
}
|
}
|
||||||
|
|
||||||
// stringify RR entries
|
// stringify RR entries
|
||||||
|
@ -130,6 +136,7 @@ func GetRRCache(domain string, question dns.Type) (*RRCache, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rrCache.Filtered = nameRecord.Filtered
|
||||||
rrCache.servedFromCache = true
|
rrCache.servedFromCache = true
|
||||||
return rrCache, nil
|
return rrCache, nil
|
||||||
}
|
}
|
||||||
|
@ -146,19 +153,104 @@ func (m *RRCache) RequestingNew() bool {
|
||||||
|
|
||||||
// Flags formats ServedFromCache and RequestingNew to a condensed, flag-like format.
|
// Flags formats ServedFromCache and RequestingNew to a condensed, flag-like format.
|
||||||
func (m *RRCache) Flags() string {
|
func (m *RRCache) Flags() string {
|
||||||
switch {
|
var s string
|
||||||
case m.servedFromCache && m.requestingNew:
|
if m.servedFromCache {
|
||||||
return " [CR]"
|
s += "C"
|
||||||
case m.servedFromCache:
|
|
||||||
return " [C]"
|
|
||||||
case m.requestingNew:
|
|
||||||
return " [R]" // should never enter this state, but let's leave it here, just in case
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
if m.requestingNew {
|
||||||
|
s += "R"
|
||||||
|
}
|
||||||
|
if m.Filtered {
|
||||||
|
s += "F"
|
||||||
|
}
|
||||||
|
|
||||||
|
if s != "" {
|
||||||
|
return fmt.Sprintf(" [%s]", s)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNXDomain returnes whether the result is nxdomain.
|
// IsNXDomain returnes whether the result is nxdomain.
|
||||||
func (m *RRCache) IsNXDomain() bool {
|
func (m *RRCache) IsNXDomain() bool {
|
||||||
return len(m.Answer) == 0
|
return len(m.Answer) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Duplicate returns a duplicate of the cache. slices are not copied, but referenced.
|
||||||
|
func (m *RRCache) Duplicate() *RRCache {
|
||||||
|
return &RRCache{
|
||||||
|
Domain: m.Domain,
|
||||||
|
Question: m.Question,
|
||||||
|
Answer: m.Answer,
|
||||||
|
Ns: m.Ns,
|
||||||
|
Extra: m.Extra,
|
||||||
|
TTL: m.TTL,
|
||||||
|
updated: m.updated,
|
||||||
|
servedFromCache: m.servedFromCache,
|
||||||
|
requestingNew: m.requestingNew,
|
||||||
|
Filtered: m.Filtered,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.ClassifyAddress(v.A)
|
||||||
|
case *dns.AAAA:
|
||||||
|
classification = netutils.ClassifyAddress(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
|
||||||
|
}
|
||||||
|
|
|
@ -18,8 +18,28 @@ import (
|
||||||
"github.com/Safing/portmaster/firewall"
|
"github.com/Safing/portmaster/firewall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
localhostIPs []dns.RR
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
modules.Register("nameserver", nil, start, nil, "intel")
|
modules.Register("nameserver", prep, start, nil, "intel")
|
||||||
|
}
|
||||||
|
|
||||||
|
func prep() error {
|
||||||
|
localhostIPv4, err := dns.NewRR("localhost. 17 IN A 127.0.0.1")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localhostIPv6, err := dns.NewRR("localhost. 17 IN AAAA ::1")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localhostIPs = []dns.RR{localhostIPv4, localhostIPv6}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() error {
|
func start() error {
|
||||||
|
@ -49,7 +69,6 @@ func nxDomain(w dns.ResponseWriter, query *dns.Msg) {
|
||||||
func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||||
|
|
||||||
// TODO: if there are 3 request for the same domain/type in a row, delete all caches of that domain
|
// TODO: if there are 3 request for the same domain/type in a row, delete all caches of that domain
|
||||||
// TODO: handle securityLevelOff
|
|
||||||
|
|
||||||
// only process first question, that's how everyone does it.
|
// only process first question, that's how everyone does it.
|
||||||
question := query.Question[0]
|
question := query.Question[0]
|
||||||
|
@ -84,6 +103,14 @@ func handleRequest(w dns.ResponseWriter, query *dns.Msg) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle request for localhost
|
||||||
|
if fqdn == "localhost." {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetReply(query)
|
||||||
|
m.Answer = localhostIPs
|
||||||
|
w.WriteMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
// get remote address
|
// get remote address
|
||||||
// start := time.Now()
|
// start := time.Now()
|
||||||
rAddr, ok := w.RemoteAddr().(*net.UDPAddr)
|
rAddr, ok := w.RemoteAddr().(*net.UDPAddr)
|
||||||
|
|
|
@ -38,51 +38,57 @@ func (conn *Connection) Process() *process.Process {
|
||||||
return conn.process
|
return conn.process
|
||||||
}
|
}
|
||||||
|
|
||||||
// CantSay sets the connection verdict to "can't say", the connection will be further analysed.
|
// Accept accepts the connection and adds the given reason.
|
||||||
func (conn *Connection) CantSay() {
|
func (conn *Link) Accept(reason string) {
|
||||||
if conn.Verdict != CANTSAY {
|
conn.AddReason(reason)
|
||||||
conn.Verdict = CANTSAY
|
conn.UpdateVerdict(ACCEPT)
|
||||||
conn.Save()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop sets the connection verdict to drop.
|
// Deny blocks or drops the connection depending on the connection direction and adds the given reason.
|
||||||
func (conn *Connection) Drop() {
|
func (conn *Link) Deny(reason string) {
|
||||||
if conn.Verdict != DROP {
|
if conn.Direction {
|
||||||
conn.Verdict = DROP
|
conn.Drop(reason)
|
||||||
conn.Save()
|
} else {
|
||||||
|
conn.Block(reason)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block sets the connection verdict to block.
|
// Block blocks the connection and adds the given reason.
|
||||||
func (conn *Connection) Block() {
|
func (conn *Link) Block(reason string) {
|
||||||
if conn.Verdict != BLOCK {
|
conn.AddReason(reason)
|
||||||
conn.Verdict = BLOCK
|
conn.UpdateVerdict(BLOCK)
|
||||||
conn.Save()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept sets the connection verdict to accept.
|
// Drop drops the connection and adds the given reason.
|
||||||
func (conn *Connection) Accept() {
|
func (conn *Link) Drop(reason string) {
|
||||||
if conn.Verdict != ACCEPT {
|
conn.AddReason(reason)
|
||||||
conn.Verdict = ACCEPT
|
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
|
||||||
conn.Save()
|
conn.Save()
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddReason adds a human readable string as to why a certain verdict was set in regard to this connection
|
// AddReason adds a human readable string as to why a certain verdict was set in regard to this connection
|
||||||
func (conn *Connection) AddReason(newReason string) {
|
func (conn *Connection) AddReason(reason string) {
|
||||||
|
if reason == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
conn.Lock()
|
conn.Lock()
|
||||||
defer conn.Unlock()
|
defer conn.Unlock()
|
||||||
|
|
||||||
if conn.Reason != "" {
|
if conn.Reason != "" {
|
||||||
conn.Reason += " | "
|
conn.Reason += " | "
|
||||||
}
|
}
|
||||||
conn.Reason += newReason
|
conn.Reason += reason
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConnectionByFirstPacket returns the matching connection from the internal storage.
|
// GetConnectionByFirstPacket returns the matching connection from the internal storage.
|
||||||
|
@ -92,13 +98,25 @@ func GetConnectionByFirstPacket(pkt packet.Packet) (*Connection, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var domain string
|
||||||
|
|
||||||
// if INBOUND
|
// Incoming
|
||||||
if direction {
|
if direction {
|
||||||
connection, ok := GetConnection(proc.Pid, "I")
|
switch netutils.ClassifyIP(pkt.GetIPHeader().Src) {
|
||||||
|
case HostLocal:
|
||||||
|
domain = IncomingHost
|
||||||
|
case LinkLocal, SiteLocal, LocalMulticast:
|
||||||
|
domain = IncomingLAN
|
||||||
|
case Global, GlobalMulticast:
|
||||||
|
domain = IncomingInternet
|
||||||
|
case Invalid:
|
||||||
|
domain = IncomingInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
connection, ok := GetConnection(proc.Pid, domain)
|
||||||
if !ok {
|
if !ok {
|
||||||
connection = &Connection{
|
connection = &Connection{
|
||||||
Domain: "I",
|
Domain: domain,
|
||||||
Direction: Inbound,
|
Direction: Inbound,
|
||||||
process: proc,
|
process: proc,
|
||||||
Inspect: true,
|
Inspect: true,
|
||||||
|
@ -111,12 +129,26 @@ func GetConnectionByFirstPacket(pkt packet.Packet) (*Connection, error) {
|
||||||
|
|
||||||
// get domain
|
// get domain
|
||||||
ipinfo, err := intel.GetIPInfo(pkt.FmtRemoteIP())
|
ipinfo, err := intel.GetIPInfo(pkt.FmtRemoteIP())
|
||||||
|
|
||||||
|
// PeerToPeer
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// if no domain could be found, it must be a direct connection
|
// if no domain could be found, it must be a direct connection
|
||||||
connection, ok := GetConnection(proc.Pid, "D")
|
|
||||||
|
switch netutils.ClassifyIP(pkt.GetIPHeader().Dst) {
|
||||||
|
case HostLocal:
|
||||||
|
domain = PeerHost
|
||||||
|
case LinkLocal, SiteLocal, LocalMulticast:
|
||||||
|
domain = PeerLAN
|
||||||
|
case Global, GlobalMulticast:
|
||||||
|
domain = PeerInternet
|
||||||
|
case Invalid:
|
||||||
|
domain = PeerInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
connection, ok := GetConnection(proc.Pid, domain)
|
||||||
if !ok {
|
if !ok {
|
||||||
connection = &Connection{
|
connection = &Connection{
|
||||||
Domain: "D",
|
Domain: domain,
|
||||||
Direction: Outbound,
|
Direction: Outbound,
|
||||||
process: proc,
|
process: proc,
|
||||||
Inspect: true,
|
Inspect: true,
|
||||||
|
@ -127,6 +159,7 @@ func GetConnectionByFirstPacket(pkt packet.Packet) (*Connection, error) {
|
||||||
return connection, nil
|
return connection, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To Domain
|
||||||
// FIXME: how to handle multiple possible domains?
|
// FIXME: how to handle multiple possible domains?
|
||||||
connection, ok := GetConnection(proc.Pid, ipinfo.Domains[0])
|
connection, ok := GetConnection(proc.Pid, ipinfo.Domains[0])
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -100,7 +100,7 @@ next:
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return nil, errors.New(fmt.Sprintf("failed to parse IP: %s", peer.String()))
|
return nil, errors.New(fmt.Sprintf("failed to parse IP: %s", peer.String()))
|
||||||
}
|
}
|
||||||
if !netutils.IPIsLocal(ip) {
|
if !netutils.IPIsLAN(ip) {
|
||||||
return ip, nil
|
return ip, nil
|
||||||
}
|
}
|
||||||
continue next
|
continue next
|
||||||
|
|
|
@ -80,8 +80,38 @@ func (link *Link) HandlePacket(pkt packet.Packet) {
|
||||||
pkt.Drop()
|
pkt.Drop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accept accepts the link and adds the given reason.
|
||||||
|
func (link *Link) Accept(reason string) {
|
||||||
|
link.AddReason(reason)
|
||||||
|
link.UpdateVerdict(ACCEPT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.Direction {
|
||||||
|
link.Drop(reason)
|
||||||
|
} else {
|
||||||
|
link.Block(reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block blocks the link and adds the given reason.
|
||||||
|
func (link *Link) Block(reason string) {
|
||||||
|
link.AddReason(reason)
|
||||||
|
link.UpdateVerdict(BLOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop drops the link and adds the given reason.
|
||||||
|
func (link *Link) Drop(reason string) {
|
||||||
|
link.AddReason(reason)
|
||||||
|
link.UpdateVerdict(DROP)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateVerdict sets a new verdict for this link, making sure it does not interfere with previous verdicts
|
// UpdateVerdict sets a new verdict for this link, making sure it does not interfere with previous verdicts
|
||||||
func (link *Link) UpdateVerdict(newVerdict Verdict) {
|
func (link *Link) UpdateVerdict(newVerdict Verdict) {
|
||||||
|
link.Lock()
|
||||||
|
defer link.Unlock()
|
||||||
|
|
||||||
if newVerdict > link.Verdict {
|
if newVerdict > link.Verdict {
|
||||||
link.Verdict = newVerdict
|
link.Verdict = newVerdict
|
||||||
link.Save()
|
link.Save()
|
||||||
|
@ -89,14 +119,18 @@ func (link *Link) UpdateVerdict(newVerdict Verdict) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddReason adds a human readable string as to why a certain verdict was set in regard to this link
|
// AddReason adds a human readable string as to why a certain verdict was set in regard to this link
|
||||||
func (link *Link) AddReason(newReason string) {
|
func (link *Link) AddReason(reason string) {
|
||||||
|
if reason == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
link.Lock()
|
link.Lock()
|
||||||
defer link.Unlock()
|
defer link.Unlock()
|
||||||
|
|
||||||
if link.Reason != "" {
|
if link.Reason != "" {
|
||||||
link.Reason += " | "
|
link.Reason += " | "
|
||||||
}
|
}
|
||||||
link.Reason += newReason
|
link.Reason += reason
|
||||||
}
|
}
|
||||||
|
|
||||||
// packetHandler sequentially handles queued packets
|
// packetHandler sequentially handles queued packets
|
||||||
|
|
|
@ -11,6 +11,7 @@ var (
|
||||||
cleanDomainRegex = regexp.MustCompile("^((xn--)?[a-z0-9-_]{0,61}[a-z0-9]{1,1}\\.)*(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\\.[a-z]{2,}\\.)$")
|
cleanDomainRegex = regexp.MustCompile("^((xn--)?[a-z0-9-_]{0,61}[a-z0-9]{1,1}\\.)*(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\\.[a-z]{2,}\\.)$")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsValidFqdn returns whether the given string is a valid fqdn.
|
||||||
func IsValidFqdn(fqdn string) bool {
|
func IsValidFqdn(fqdn string) bool {
|
||||||
return cleanDomainRegex.MatchString(fqdn)
|
return cleanDomainRegex.MatchString(fqdn)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,95 +4,101 @@ package netutils
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
// IP types
|
// IP classifications
|
||||||
const (
|
const (
|
||||||
hostLocal int8 = iota
|
HostLocal int8 = iota
|
||||||
linkLocal
|
LinkLocal
|
||||||
siteLocal
|
SiteLocal
|
||||||
global
|
Global
|
||||||
localMulticast
|
LocalMulticast
|
||||||
globalMulticast
|
GlobalMulticast
|
||||||
invalid
|
Invalid
|
||||||
)
|
)
|
||||||
|
|
||||||
func classifyAddress(ip net.IP) int8 {
|
// ClassifyAddress returns the classification for the given IP address.
|
||||||
|
func ClassifyAddress(ip net.IP) int8 {
|
||||||
if ip4 := ip.To4(); ip4 != nil {
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
// IPv4
|
// IPv4
|
||||||
switch {
|
switch {
|
||||||
case ip4[0] == 127:
|
case ip4[0] == 127:
|
||||||
// 127.0.0.0/8
|
// 127.0.0.0/8
|
||||||
return hostLocal
|
return HostLocal
|
||||||
case ip4[0] == 169 && ip4[1] == 254:
|
case ip4[0] == 169 && ip4[1] == 254:
|
||||||
// 169.254.0.0/16
|
// 169.254.0.0/16
|
||||||
return linkLocal
|
return LinkLocal
|
||||||
case ip4[0] == 10:
|
case ip4[0] == 10:
|
||||||
// 10.0.0.0/8
|
// 10.0.0.0/8
|
||||||
return siteLocal
|
return SiteLocal
|
||||||
case ip4[0] == 172 && ip4[1]&0xf0 == 16:
|
case ip4[0] == 172 && ip4[1]&0xf0 == 16:
|
||||||
// 172.16.0.0/12
|
// 172.16.0.0/12
|
||||||
return siteLocal
|
return SiteLocal
|
||||||
case ip4[0] == 192 && ip4[1] == 168:
|
case ip4[0] == 192 && ip4[1] == 168:
|
||||||
// 192.168.0.0/16
|
// 192.168.0.0/16
|
||||||
return siteLocal
|
return SiteLocal
|
||||||
case ip4[0] == 224:
|
case ip4[0] == 224:
|
||||||
// 224.0.0.0/8
|
// 224.0.0.0/8
|
||||||
return localMulticast
|
return LocalMulticast
|
||||||
case ip4[0] >= 225 && ip4[0] <= 239:
|
case ip4[0] >= 225 && ip4[0] <= 239:
|
||||||
// 225.0.0.0/8 - 239.0.0.0/8
|
// 225.0.0.0/8 - 239.0.0.0/8
|
||||||
return globalMulticast
|
return GlobalMulticast
|
||||||
case ip4[0] >= 240:
|
case ip4[0] >= 240:
|
||||||
// 240.0.0.0/8 - 255.0.0.0/8
|
// 240.0.0.0/8 - 255.0.0.0/8
|
||||||
return invalid
|
return Invalid
|
||||||
default:
|
default:
|
||||||
return global
|
return Global
|
||||||
}
|
}
|
||||||
} else if len(ip) == net.IPv6len {
|
} else if len(ip) == net.IPv6len {
|
||||||
// IPv6
|
// IPv6
|
||||||
switch {
|
switch {
|
||||||
case ip.Equal(net.IPv6loopback):
|
case ip.Equal(net.IPv6loopback):
|
||||||
return hostLocal
|
return HostLocal
|
||||||
case ip[0]&0xfe == 0xfc:
|
case ip[0]&0xfe == 0xfc:
|
||||||
// fc00::/7
|
// fc00::/7
|
||||||
return siteLocal
|
return SiteLocal
|
||||||
case ip[0] == 0xfe && ip[1]&0xc0 == 0x80:
|
case ip[0] == 0xfe && ip[1]&0xc0 == 0x80:
|
||||||
// fe80::/10
|
// fe80::/10
|
||||||
return linkLocal
|
return LinkLocal
|
||||||
case ip[0] == 0xff && ip[1] <= 0x05:
|
case ip[0] == 0xff && ip[1] <= 0x05:
|
||||||
// ff00::/16 - ff05::/16
|
// ff00::/16 - ff05::/16
|
||||||
return localMulticast
|
return LocalMulticast
|
||||||
case ip[0] == 0xff:
|
case ip[0] == 0xff:
|
||||||
// other ff00::/8
|
// other ff00::/8
|
||||||
return globalMulticast
|
return GlobalMulticast
|
||||||
default:
|
default:
|
||||||
return global
|
return Global
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return invalid
|
return Invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPIsLocal returns true if the given IP is a site-local or link-local address
|
// IPIsLocalhost returns whether the IP refers to the host itself.
|
||||||
func IPIsLocal(ip net.IP) bool {
|
func IPIsLocalhost(ip net.IP) bool {
|
||||||
switch classifyAddress(ip) {
|
return ClassifyAddress(ip) == HostLocal
|
||||||
case siteLocal:
|
}
|
||||||
|
|
||||||
|
// IPIsLAN returns true if the given IP is a site-local or link-local address.
|
||||||
|
func IPIsLAN(ip net.IP) bool {
|
||||||
|
switch ClassifyAddress(ip) {
|
||||||
|
case SiteLocal:
|
||||||
return true
|
return true
|
||||||
case linkLocal:
|
case LinkLocal:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPIsGlobal returns true if the given IP is a global address
|
// IPIsGlobal returns true if the given IP is a global address.
|
||||||
func IPIsGlobal(ip net.IP) bool {
|
func IPIsGlobal(ip net.IP) bool {
|
||||||
return classifyAddress(ip) == global
|
return ClassifyAddress(ip) == Global
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPIsLinkLocal returns true if the given IP is a link-local address
|
// IPIsLinkLocal returns true if the given IP is a link-local address.
|
||||||
func IPIsLinkLocal(ip net.IP) bool {
|
func IPIsLinkLocal(ip net.IP) bool {
|
||||||
return classifyAddress(ip) == linkLocal
|
return ClassifyAddress(ip) == LinkLocal
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPIsSiteLocal returns true if the given IP is a site-local address
|
// IPIsSiteLocal returns true if the given IP is a site-local address.
|
||||||
func IPIsSiteLocal(ip net.IP) bool {
|
func IPIsSiteLocal(ip net.IP) bool {
|
||||||
return classifyAddress(ip) == siteLocal
|
return ClassifyAddress(ip) == SiteLocal
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIPClassification(t *testing.T) {
|
func TestIPClassification(t *testing.T) {
|
||||||
testClassification(t, net.IPv4(71, 87, 113, 211), global)
|
testClassification(t, net.IPv4(71, 87, 113, 211), Global)
|
||||||
testClassification(t, net.IPv4(127, 0, 0, 1), hostLocal)
|
testClassification(t, net.IPv4(127, 0, 0, 1), HostLocal)
|
||||||
testClassification(t, net.IPv4(127, 255, 255, 1), hostLocal)
|
testClassification(t, net.IPv4(127, 255, 255, 1), HostLocal)
|
||||||
testClassification(t, net.IPv4(192, 168, 172, 24), siteLocal)
|
testClassification(t, net.IPv4(192, 168, 172, 24), SiteLocal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testClassification(t *testing.T, ip net.IP, expectedClassification int8) {
|
func testClassification(t *testing.T, ip net.IP, expectedClassification int8) {
|
||||||
c := classifyAddress(ip)
|
c := ClassifyAddress(ip)
|
||||||
if c != expectedClassification {
|
if c != expectedClassification {
|
||||||
t.Errorf("%s is %s, expected %s", ip, classificationString(c), classificationString(expectedClassification))
|
t.Errorf("%s is %s, expected %s", ip, classificationString(c), classificationString(expectedClassification))
|
||||||
}
|
}
|
||||||
|
@ -21,19 +21,19 @@ func testClassification(t *testing.T, ip net.IP, expectedClassification int8) {
|
||||||
|
|
||||||
func classificationString(c int8) string {
|
func classificationString(c int8) string {
|
||||||
switch c {
|
switch c {
|
||||||
case hostLocal:
|
case HostLocal:
|
||||||
return "hostLocal"
|
return "hostLocal"
|
||||||
case linkLocal:
|
case LinkLocal:
|
||||||
return "linkLocal"
|
return "linkLocal"
|
||||||
case siteLocal:
|
case SiteLocal:
|
||||||
return "siteLocal"
|
return "siteLocal"
|
||||||
case global:
|
case Global:
|
||||||
return "global"
|
return "global"
|
||||||
case localMulticast:
|
case LocalMulticast:
|
||||||
return "localMulticast"
|
return "localMulticast"
|
||||||
case globalMulticast:
|
case GlobalMulticast:
|
||||||
return "globalMulticast"
|
return "globalMulticast"
|
||||||
case invalid:
|
case Invalid:
|
||||||
return "invalid"
|
return "invalid"
|
||||||
default:
|
default:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
|
@ -9,7 +9,6 @@ type Verdict uint8
|
||||||
const (
|
const (
|
||||||
// UNDECIDED is the default status of new connections
|
// UNDECIDED is the default status of new connections
|
||||||
UNDECIDED Verdict = iota
|
UNDECIDED Verdict = iota
|
||||||
CANTSAY
|
|
||||||
ACCEPT
|
ACCEPT
|
||||||
BLOCK
|
BLOCK
|
||||||
DROP
|
DROP
|
||||||
|
@ -20,3 +19,15 @@ const (
|
||||||
Inbound = true
|
Inbound = true
|
||||||
Outbound = false
|
Outbound = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Non-Domain Connections
|
||||||
|
const (
|
||||||
|
IncomingHost = "IH"
|
||||||
|
IncomingLAN = "IL"
|
||||||
|
IncomingInternet = "II"
|
||||||
|
IncomingInvalid = "IX"
|
||||||
|
PeerHost = "PH"
|
||||||
|
PeerLAN = "PL"
|
||||||
|
PeerInternet = "PI"
|
||||||
|
PeerInvalid = "PX"
|
||||||
|
)
|
||||||
|
|
|
@ -35,29 +35,17 @@ func updateActiveUserProfile(profile *Profile) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateActiveGlobalProfile(profile *Profile) {
|
|
||||||
updateActiveProfile(1, profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateActiveStampProfile(profile *Profile) {
|
func updateActiveStampProfile(profile *Profile) {
|
||||||
updateActiveProfile(2, profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateActiveFallbackProfile(profile *Profile) {
|
|
||||||
updateActiveProfile(3, profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateActiveProfile(setID int, profile *Profile) {
|
|
||||||
activeProfileSetsLock.RLock()
|
activeProfileSetsLock.RLock()
|
||||||
defer activeProfileSetsLock.RUnlock()
|
defer activeProfileSetsLock.RUnlock()
|
||||||
|
|
||||||
for _, activeSet := range activeProfileSets {
|
for _, activeSet := range activeProfileSets {
|
||||||
activeSet.Lock()
|
activeSet.Lock()
|
||||||
activeProfile := activeSet.profiles[setID]
|
activeProfile := activeSet.profiles[2]
|
||||||
if activeProfile != nil {
|
if activeProfile != nil {
|
||||||
activeProfile.Lock()
|
activeProfile.Lock()
|
||||||
if activeProfile.ID == profile.ID {
|
if activeProfile.ID == profile.ID {
|
||||||
activeSet.profiles[setID] = profile
|
activeSet.profiles[2] = profile
|
||||||
}
|
}
|
||||||
activeProfile.Unlock()
|
activeProfile.Unlock()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package profile
|
package profile
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fingerprintWeights = map[string]int{
|
fingerprintWeights = map[string]int{
|
||||||
"full_path": 2,
|
"full_path": 2,
|
||||||
|
@ -10,41 +12,21 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Fingerprint links processes to profiles.
|
||||||
type Fingerprint struct {
|
type Fingerprint struct {
|
||||||
OS string
|
OS string
|
||||||
Type string
|
Type string
|
||||||
Value string
|
Value string
|
||||||
Comment string
|
Comment string
|
||||||
|
LastUsed int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchesOS returns whether the Fingerprint is applicable for the current OS.
|
||||||
func (fp *Fingerprint) MatchesOS() bool {
|
func (fp *Fingerprint) MatchesOS() bool {
|
||||||
return fp.OS == osIdentifier
|
return fp.OS == osIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// GetFingerprintWeight returns the weight of the given fingerprint type.
|
||||||
// func (fp *Fingerprint) Equals(other *Fingerprint) bool {
|
|
||||||
// return fp.OS == other.OS &&
|
|
||||||
// fp.Type == other.Type &&
|
|
||||||
// fp.Value == other.Value
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (fp *Fingerprint) Check(type, value string) (weight int) {
|
|
||||||
// if fp.Match(fpType, value) {
|
|
||||||
// return GetFingerprintWeight(fpType)
|
|
||||||
// }
|
|
||||||
// return 0
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (fp *Fingerprint) Match(fpType, value string) (matches bool) {
|
|
||||||
// switch fp.Type {
|
|
||||||
// case "partial_path":
|
|
||||||
// return
|
|
||||||
// default:
|
|
||||||
// return fp.OS == osIdentifier &&
|
|
||||||
// fp.Type == fpType &&
|
|
||||||
// fp.Value == value
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
func GetFingerprintWeight(fpType string) (weight int) {
|
func GetFingerprintWeight(fpType string) (weight int) {
|
||||||
weight, ok := fingerprintWeights[fpType]
|
weight, ok := fingerprintWeights[fpType]
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -53,47 +35,14 @@ func GetFingerprintWeight(fpType string) (weight int) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
// AddFingerprint adds the given fingerprint to the profile.
|
||||||
// func (p *Profile) GetApplicableFingerprints() (fingerprints []*Fingerprint) {
|
|
||||||
// for _, fp := range p.Fingerprints {
|
|
||||||
// if fp.OS == osIdentifier {
|
|
||||||
// fingerprints = append(fingerprints, fp)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
func (p *Profile) AddFingerprint(fp *Fingerprint) {
|
func (p *Profile) AddFingerprint(fp *Fingerprint) {
|
||||||
if fp.OS == "" {
|
if fp.OS == "" {
|
||||||
fp.OS = osIdentifier
|
fp.OS = osIdentifier
|
||||||
}
|
}
|
||||||
|
if fp.LastUsed == 0 {
|
||||||
|
fp.LastUsed = time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
p.Fingerprints = append(p.Fingerprints, fp)
|
p.Fingerprints = append(p.Fingerprints, fp)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// func (p *Profile) GetApplicableFingerprintTypes() (types []string) {
|
|
||||||
// for _, fp := range p.Fingerprints {
|
|
||||||
// if fp.OS == osIdentifier && !utils.StringInSlice(types, fp.Type) {
|
|
||||||
// types = append(types, fp.Type)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (p *Profile) MatchFingerprints(fingerprints map[string]string) (score int) {
|
|
||||||
// for _, fp := range p.Fingerprints {
|
|
||||||
// if fp.OS == osIdentifier {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func FindUserProfiles() {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func FindProfiles(path string) (*ProfileSet, error) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
|
@ -77,19 +77,6 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// FlagsFromNames creates Flags from a comma seperated list of flagnames (e.g. "System,Strict,Secure")
|
|
||||||
// func FlagsFromNames(words []string) (*Flags, error) {
|
|
||||||
// var flags Flags
|
|
||||||
// for _, entry := range words {
|
|
||||||
// flag, ok := flagIDs[entry]
|
|
||||||
// if !ok {
|
|
||||||
// return nil, ErrFlagsParseFailed
|
|
||||||
// }
|
|
||||||
// flags = append(flags, flag)
|
|
||||||
// }
|
|
||||||
// return &flags, nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Check checks if a flag is set at all and if it's active in the given security level.
|
// Check checks if a flag is set at all and if it's active in the given security level.
|
||||||
func (flags Flags) Check(flag, level uint8) (active bool, ok bool) {
|
func (flags Flags) Check(flag, level uint8) (active bool, ok bool) {
|
||||||
if flags == nil {
|
if flags == nil {
|
||||||
|
|
|
@ -15,12 +15,14 @@ type ProfileIndex struct {
|
||||||
record.Base
|
record.Base
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
|
ID string
|
||||||
|
|
||||||
UserProfiles []string
|
UserProfiles []string
|
||||||
StampProfiles []string
|
StampProfiles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeIndexRecordKey(id string) string {
|
func makeIndexRecordKey(fpType, id string) string {
|
||||||
return fmt.Sprintf("index:profiles/%s", base64.RawURLEncoding.EncodeToString([]byte(id)))
|
return fmt.Sprintf("index:profiles/%s:%s", fpType, base64.RawURLEncoding.EncodeToString([]byte(id)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIndex returns a new ProfileIndex.
|
// NewIndex returns a new ProfileIndex.
|
||||||
|
@ -32,8 +34,8 @@ func NewIndex(id string) *ProfileIndex {
|
||||||
|
|
||||||
// AddUserProfile adds a User Profile to the index.
|
// AddUserProfile adds a User Profile to the index.
|
||||||
func (pi *ProfileIndex) AddUserProfile(identifier string) (changed bool) {
|
func (pi *ProfileIndex) AddUserProfile(identifier string) (changed bool) {
|
||||||
if !utils.StringInSlice(pi.UserProfiles, id) {
|
if !utils.StringInSlice(pi.UserProfiles, identifier) {
|
||||||
pi.UserProfiles = append(pi.UserProfiles, id)
|
pi.UserProfiles = append(pi.UserProfiles, identifier)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -41,8 +43,8 @@ func (pi *ProfileIndex) AddUserProfile(identifier string) (changed bool) {
|
||||||
|
|
||||||
// AddStampProfile adds a Stamp Profile to the index.
|
// AddStampProfile adds a Stamp Profile to the index.
|
||||||
func (pi *ProfileIndex) AddStampProfile(identifier string) (changed bool) {
|
func (pi *ProfileIndex) AddStampProfile(identifier string) (changed bool) {
|
||||||
if !utils.StringInSlice(pi.StampProfiles, id) {
|
if !utils.StringInSlice(pi.StampProfiles, identifier) {
|
||||||
pi.StampProfiles = append(pi.StampProfiles, id)
|
pi.StampProfiles = append(pi.StampProfiles, identifier)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -59,8 +61,8 @@ func (pi *ProfileIndex) RemoveStampProfile(id string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get gets a ProfileIndex from the database.
|
// Get gets a ProfileIndex from the database.
|
||||||
func Get(id string) (*ProfileIndex, error) {
|
func Get(fpType, id string) (*ProfileIndex, error) {
|
||||||
key := makeIndexRecordKey(id)
|
key := makeIndexRecordKey(fpType, id)
|
||||||
|
|
||||||
r, err := indexDB.Get(key)
|
r, err := indexDB.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package index
|
package index
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Safing/portbase/database"
|
"github.com/Safing/portbase/database"
|
||||||
"github.com/Safing/portbase/database/query"
|
"github.com/Safing/portbase/database/query"
|
||||||
"github.com/Safing/portbase/database/record"
|
"github.com/Safing/portbase/database/record"
|
||||||
|
@ -25,7 +23,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
modules.Register("profile:index", nil, start, stop, "database")
|
modules.Register("profile:index", nil, start, stop, "profile", "database")
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() (err error) {
|
func start() (err error) {
|
||||||
|
@ -49,13 +47,17 @@ func indexer() {
|
||||||
case <-shutdownIndexer:
|
case <-shutdownIndexer:
|
||||||
return
|
return
|
||||||
case r := <-indexSub.Feed:
|
case r := <-indexSub.Feed:
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
prof := ensureProfile(r)
|
prof := ensureProfile(r)
|
||||||
if prof != nil {
|
if prof != nil {
|
||||||
for _, id := range prof.Identifiers {
|
for _, fp := range prof.Fingerprints {
|
||||||
if strings.HasPrefix(id, profile.IdentifierPrefix) {
|
if fp.MatchesOS() && fp.Type == "full_path" {
|
||||||
|
|
||||||
// get Profile and ensure identifier is set
|
// get Profile and ensure identifier is set
|
||||||
pi, err := GetIndex(id)
|
pi, err := Get("full_path", fp.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == database.ErrNotFound {
|
if err == database.ErrNotFound {
|
||||||
pi = NewIndex(id)
|
pi = NewIndex(id)
|
||||||
|
|
|
@ -1,3 +1,24 @@
|
||||||
package profile
|
package profile
|
||||||
|
|
||||||
.
|
import "github.com/Safing/portbase/modules"
|
||||||
|
|
||||||
|
var (
|
||||||
|
shutdownSignal = make(chan struct{})
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
modules.Register("profile", nil, start, stop, "database")
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() error {
|
||||||
|
err := initSpecialProfiles()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return initUpdateListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() error {
|
||||||
|
close(shutdownSignal)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
46
profile/ports_test.go
Normal file
46
profile/ports_test.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPorts(t *testing.T) {
|
||||||
|
var ports Ports
|
||||||
|
ports = map[int16][]*Port{
|
||||||
|
6: []*Port{
|
||||||
|
&Port{ // SSH
|
||||||
|
Permit: true,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
Start: 22,
|
||||||
|
End: 22,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
-17: []*Port{
|
||||||
|
&Port{ // HTTP
|
||||||
|
Permit: false,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
Start: 80,
|
||||||
|
End: 81,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
93: []*Port{
|
||||||
|
&Port{ // HTTP
|
||||||
|
Permit: true,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
Start: 93,
|
||||||
|
End: 93,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if ports.String() != "TCP:[permit:22], <UDP:[deny:80-81], 93:[permit:93]" {
|
||||||
|
t.Errorf("unexpected result: %s", ports.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var noPorts Ports
|
||||||
|
noPorts = map[int16][]*Port{}
|
||||||
|
if noPorts.String() != "None" {
|
||||||
|
t.Errorf("unexpected result: %s", ports.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -37,8 +37,10 @@ type Profile struct {
|
||||||
Domains Domains
|
Domains Domains
|
||||||
Ports Ports
|
Ports Ports
|
||||||
|
|
||||||
StampProfileKey string
|
// User Profile Only
|
||||||
StampProfileAssigned int64
|
CoupledPath string `json:",omitempty"`
|
||||||
|
StampProfileKey string `json:",omitempty"`
|
||||||
|
StampProfileAssigned int64 `json:",omitempty"`
|
||||||
|
|
||||||
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process must be found
|
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process must be found
|
||||||
// Framework *Framework `json:",omitempty bson:",omitempty"`
|
// Framework *Framework `json:",omitempty bson:",omitempty"`
|
||||||
|
|
|
@ -42,6 +42,8 @@ func NewSet(user, stamp *Profile) *Set {
|
||||||
|
|
||||||
// Update gets the new global and default profile and updates the independence status. It must be called when reusing a profile set for a series of calls.
|
// Update gets the new global and default profile and updates the independence status. It must be called when reusing a profile set for a series of calls.
|
||||||
func (set *Set) Update(securityLevel uint8) {
|
func (set *Set) Update(securityLevel uint8) {
|
||||||
|
set.Lock()
|
||||||
|
|
||||||
specialProfileLock.RLock()
|
specialProfileLock.RLock()
|
||||||
defer specialProfileLock.RUnlock()
|
defer specialProfileLock.RUnlock()
|
||||||
|
|
||||||
|
@ -58,15 +60,36 @@ func (set *Set) Update(securityLevel uint8) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update independence
|
// update independence
|
||||||
|
set.Unlock()
|
||||||
if set.CheckFlag(Independent) {
|
if set.CheckFlag(Independent) {
|
||||||
|
set.Lock()
|
||||||
set.independent = true
|
set.independent = true
|
||||||
|
set.Unlock()
|
||||||
} else {
|
} else {
|
||||||
|
set.Lock()
|
||||||
set.independent = false
|
set.independent = false
|
||||||
|
set.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProfileMode returns the active profile mode.
|
||||||
|
func (set *Set) GetProfileMode() uint8 {
|
||||||
|
switch {
|
||||||
|
case set.CheckFlag(Whitelist):
|
||||||
|
return Whitelist
|
||||||
|
case set.CheckFlag(Prompt):
|
||||||
|
return Prompt
|
||||||
|
case set.CheckFlag(Blacklist):
|
||||||
|
return Blacklist
|
||||||
|
default:
|
||||||
|
return Whitelist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckFlag returns whether a given flag is set.
|
// CheckFlag returns whether a given flag is set.
|
||||||
func (set *Set) CheckFlag(flag uint8) (active bool) {
|
func (set *Set) CheckFlag(flag uint8) (active bool) {
|
||||||
|
set.Lock()
|
||||||
|
defer set.Unlock()
|
||||||
|
|
||||||
for i, profile := range set.profiles {
|
for i, profile := range set.profiles {
|
||||||
if i == 2 && set.independent {
|
if i == 2 && set.independent {
|
||||||
|
@ -86,6 +109,8 @@ func (set *Set) CheckFlag(flag uint8) (active bool) {
|
||||||
|
|
||||||
// CheckDomain checks if the given domain is governed in any the lists of domains and returns whether it is permitted.
|
// CheckDomain checks if the given domain is governed in any the lists of domains and returns whether it is permitted.
|
||||||
func (set *Set) CheckDomain(domain string) (permit, ok bool) {
|
func (set *Set) CheckDomain(domain string) (permit, ok bool) {
|
||||||
|
set.Lock()
|
||||||
|
defer set.Unlock()
|
||||||
|
|
||||||
for i, profile := range set.profiles {
|
for i, profile := range set.profiles {
|
||||||
if i == 2 && set.independent {
|
if i == 2 && set.independent {
|
||||||
|
@ -105,6 +130,8 @@ func (set *Set) CheckDomain(domain string) (permit, ok bool) {
|
||||||
|
|
||||||
// CheckPort checks if the given protocol and port are governed in any the lists of ports and returns whether it is permitted.
|
// CheckPort checks if the given protocol and port are governed in any the lists of ports and returns whether it is permitted.
|
||||||
func (set *Set) CheckPort(listen bool, protocol uint8, port uint16) (permit, ok bool) {
|
func (set *Set) CheckPort(listen bool, protocol uint8, port uint16) (permit, ok bool) {
|
||||||
|
set.Lock()
|
||||||
|
defer set.Unlock()
|
||||||
|
|
||||||
signedProtocol := int16(protocol)
|
signedProtocol := int16(protocol)
|
||||||
if listen {
|
if listen {
|
||||||
|
|
|
@ -1,17 +1,167 @@
|
||||||
package profile
|
package profile
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Safing/portmaster/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testUserProfile *Profile
|
||||||
|
testStampProfile *Profile
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
specialProfileLock.Lock()
|
||||||
|
defer specialProfileLock.Unlock()
|
||||||
|
|
||||||
|
globalProfile = makeDefaultGlobalProfile()
|
||||||
|
fallbackProfile = makeDefaultFallbackProfile()
|
||||||
|
|
||||||
|
testUserProfile = &Profile{
|
||||||
|
ID: "unit-test-user",
|
||||||
|
Name: "Unit Test User Profile",
|
||||||
|
SecurityLevel: status.SecurityLevelDynamic,
|
||||||
|
Flags: map[uint8]uint8{
|
||||||
|
Independent: status.SecurityLevelFortress,
|
||||||
|
},
|
||||||
|
Domains: map[string]*DomainDecision{
|
||||||
|
"example.com": &DomainDecision{
|
||||||
|
Permit: true,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
IncludeSubdomains: false,
|
||||||
|
},
|
||||||
|
"bad.example.com": &DomainDecision{
|
||||||
|
Permit: false,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
IncludeSubdomains: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ports: map[int16][]*Port{
|
||||||
|
6: []*Port{
|
||||||
|
&Port{
|
||||||
|
Permit: true,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
Start: 22000,
|
||||||
|
End: 22000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testStampProfile = &Profile{
|
||||||
|
ID: "unit-test-stamp",
|
||||||
|
Name: "Unit Test Stamp Profile",
|
||||||
|
SecurityLevel: status.SecurityLevelFortress,
|
||||||
|
Flags: map[uint8]uint8{
|
||||||
|
Internet: status.SecurityLevelsAll,
|
||||||
|
},
|
||||||
|
Domains: map[string]*DomainDecision{
|
||||||
|
"bad2.example.com": &DomainDecision{
|
||||||
|
Permit: false,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
IncludeSubdomains: true,
|
||||||
|
},
|
||||||
|
"good.bad.example.com": &DomainDecision{
|
||||||
|
Permit: true,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
IncludeSubdomains: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ports: map[int16][]*Port{
|
||||||
|
6: []*Port{
|
||||||
|
&Port{
|
||||||
|
Permit: false,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
Start: 80,
|
||||||
|
End: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
-17: []*Port{
|
||||||
|
&Port{
|
||||||
|
Permit: true,
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
Start: 12345,
|
||||||
|
End: 12347,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDomain(t *testing.T, set *Set, domain string, shouldBePermitted bool) {
|
||||||
|
permitted, ok := set.CheckDomain(domain)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("domain %s should be in test profile set", domain)
|
||||||
|
}
|
||||||
|
if permitted != shouldBePermitted {
|
||||||
|
t.Errorf("unexpected result: domain %s: permitted=%v, expected=%v", domain, permitted, shouldBePermitted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUnregulatedDomain(t *testing.T, set *Set, domain string) {
|
||||||
|
_, ok := set.CheckDomain(domain)
|
||||||
|
if ok {
|
||||||
|
t.Errorf("domain %s should not be in test profile set", domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPort(t *testing.T, set *Set, listen bool, protocol uint8, port uint16, shouldBePermitted bool) {
|
||||||
|
permitted, ok := set.CheckPort(listen, protocol, port)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("port [%v %d %d] should be in test profile set", listen, protocol, port)
|
||||||
|
}
|
||||||
|
if permitted != shouldBePermitted {
|
||||||
|
t.Errorf("unexpected result: port [%v %d %d]: permitted=%v, expected=%v", listen, protocol, port, permitted, shouldBePermitted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUnregulatedPort(t *testing.T, set *Set, listen bool, protocol uint8, port uint16) {
|
||||||
|
_, ok := set.CheckPort(listen, protocol, port)
|
||||||
|
if ok {
|
||||||
|
t.Errorf("port [%v %d %d] should not be in test profile set", listen, protocol, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProfileSet(t *testing.T) {
|
func TestProfileSet(t *testing.T) {
|
||||||
|
|
||||||
// new := &Set{
|
set := NewSet(testUserProfile, testStampProfile)
|
||||||
// profiles: [4]*Profile{
|
|
||||||
// user, // Application
|
|
||||||
// nil, // Global
|
|
||||||
// stamp, // Stamp
|
|
||||||
// nil, // Default
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// new.Update(status.SecurityLevelFortress)
|
|
||||||
|
|
||||||
|
set.Update(status.SecurityLevelDynamic)
|
||||||
|
testFlag(t, set, Whitelist, false)
|
||||||
|
testFlag(t, set, Internet, true)
|
||||||
|
testDomain(t, set, "example.com", true)
|
||||||
|
testDomain(t, set, "bad.example.com", false)
|
||||||
|
testDomain(t, set, "other.bad.example.com", false)
|
||||||
|
testDomain(t, set, "good.bad.example.com", false)
|
||||||
|
testDomain(t, set, "bad2.example.com", false)
|
||||||
|
testPort(t, set, false, 6, 443, true)
|
||||||
|
testPort(t, set, false, 6, 143, true)
|
||||||
|
testPort(t, set, false, 6, 22, true)
|
||||||
|
testPort(t, set, false, 6, 80, false)
|
||||||
|
testPort(t, set, false, 6, 80, false)
|
||||||
|
testPort(t, set, true, 17, 12345, true)
|
||||||
|
testPort(t, set, true, 17, 12346, true)
|
||||||
|
testPort(t, set, true, 17, 12347, true)
|
||||||
|
testUnregulatedDomain(t, set, "other.example.com")
|
||||||
|
testUnregulatedPort(t, set, false, 17, 53)
|
||||||
|
testUnregulatedPort(t, set, false, 17, 443)
|
||||||
|
testUnregulatedPort(t, set, true, 6, 443)
|
||||||
|
|
||||||
|
set.Update(status.SecurityLevelSecure)
|
||||||
|
testFlag(t, set, Internet, true)
|
||||||
|
|
||||||
|
set.Update(status.SecurityLevelFortress) // Independent!
|
||||||
|
testFlag(t, set, Internet, false)
|
||||||
|
testPort(t, set, false, 6, 80, true)
|
||||||
|
testUnregulatedDomain(t, set, "bad2.example.com")
|
||||||
|
testUnregulatedPort(t, set, true, 17, 12346)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package profile
|
package profile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Safing/portbase/database"
|
"github.com/Safing/portbase/database"
|
||||||
|
@ -19,35 +18,41 @@ func initUpdateListener() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
slashedUserNamespace = fmt.Sprintf("/%s/", userNamespace)
|
|
||||||
slashedStampNamespace = fmt.Sprintf("/%s/", stampNamespace)
|
|
||||||
)
|
|
||||||
|
|
||||||
func updateListener(sub *database.Subscription) {
|
func updateListener(sub *database.Subscription) {
|
||||||
for r := range sub.Feed {
|
for {
|
||||||
profile, err := ensureProfile(r)
|
select {
|
||||||
if err != nil {
|
case <-shutdownSignal:
|
||||||
log.Errorf("profile: received update for special profile, but could not read: %s", err)
|
return
|
||||||
continue
|
case r := <-sub.Feed:
|
||||||
}
|
|
||||||
|
|
||||||
specialProfileLock.Lock()
|
if r.Meta().IsDeleted() {
|
||||||
switch profile.ID {
|
continue
|
||||||
case "global":
|
|
||||||
globalProfile = profile
|
|
||||||
updateActiveGlobalProfile(profile)
|
|
||||||
case "fallback":
|
|
||||||
fallbackProfile = profile
|
|
||||||
updateActiveFallbackProfile(profile)
|
|
||||||
default:
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(profile.Key(), makeProfileKey(userNamespace, "")):
|
|
||||||
updateActiveUserProfile(profile)
|
|
||||||
case strings.HasPrefix(profile.Key(), makeProfileKey(stampNamespace, "")):
|
|
||||||
updateActiveStampProfile(profile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
profile, err := ensureProfile(r)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("profile: received update for special profile, but could not read: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch profile.ID {
|
||||||
|
case "global":
|
||||||
|
specialProfileLock.Lock()
|
||||||
|
globalProfile = profile
|
||||||
|
specialProfileLock.Unlock()
|
||||||
|
case "fallback":
|
||||||
|
specialProfileLock.Lock()
|
||||||
|
fallbackProfile = profile
|
||||||
|
specialProfileLock.Unlock()
|
||||||
|
default:
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(profile.Key(), makeProfileKey(userNamespace, "")):
|
||||||
|
updateActiveUserProfile(profile)
|
||||||
|
case strings.HasPrefix(profile.Key(), makeProfileKey(stampNamespace, "")):
|
||||||
|
updateActiveStampProfile(profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
specialProfileLock.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue