diff --git a/.gitignore b/.gitignore index aa50ebd2..2dde10d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ dnsonly +main +*.exe diff --git a/dnsonly.go b/dnsonly.go index ac331bad..04ece2fa 100644 --- a/dnsonly.go +++ b/dnsonly.go @@ -9,6 +9,7 @@ import ( "github.com/Safing/portbase/info" "github.com/Safing/portbase/log" "github.com/Safing/portbase/modules" + // include packages here _ "github.com/Safing/portbase/database/dbmodule" diff --git a/firewall/firewall.go b/firewall/firewall.go index 2f317200..a9ed23f9 100644 --- a/firewall/firewall.go +++ b/firewall/firewall.go @@ -1,14 +1,12 @@ -// 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 firewall import ( + "fmt" "net" "os" "sync/atomic" "time" - "github.com/Safing/portbase/config" "github.com/Safing/portbase/log" "github.com/Safing/portbase/modules" "github.com/Safing/portmaster/firewall/inspection" @@ -20,7 +18,6 @@ import ( ) var ( - firewallModule *modules.Module // localNet net.IPNet localhost net.IP dnsServer net.IPNet @@ -154,11 +151,22 @@ func initialHandler(pkt packet.Packet, link *network.Link) { // get Connection connection, err := network.GetConnectionByFirstPacket(pkt) if err != nil { + link.Lock() if err != process.ErrConnectionNotFound { log.Warningf("firewall: could not find process of packet (dropping link %s): %s", pkt.String(), err) + link.AddReason(fmt.Sprintf("could not find process or it does not exist (unsolicited packet): %s", err)) + } else { + log.Warningf("firewall: internal error finding process of packet (dropping link %s): %s", pkt.String(), err) + link.AddReason(fmt.Sprintf("internal error finding process: %s", err)) } - link.UpdateVerdict(network.DROP) - verdict(pkt, network.DROP) + link.Unlock() + + if pkt.IsInbound() { + network.UnknownIncomingConnection.AddLink(link) + } else { + network.UnknownDirectConnection.AddLink(link) + } + verdict(pkt, link.Verdict) return } diff --git a/firewall/inspection/tls/const.go b/firewall/inspection/tls/const.go index 89298c5d..07657014 100644 --- a/firewall/inspection/tls/const.go +++ b/firewall/inspection/tls/const.go @@ -1,5 +1,3 @@ -// 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 tls var ( diff --git a/firewall/inspection/tls/tls.go b/firewall/inspection/tls/tls.go index 63aa65c9..36aade25 100644 --- a/firewall/inspection/tls/tls.go +++ b/firewall/inspection/tls/tls.go @@ -1,5 +1,3 @@ -// 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 tls import ( @@ -12,14 +10,13 @@ import ( "github.com/google/gopacket/layers" "github.com/google/gopacket/tcpassembly" - "github.com/Safing/safing-core/configuration" - "github.com/Safing/safing-core/crypto/verify" - "github.com/Safing/safing-core/firewall/inspection" - "github.com/Safing/safing-core/firewall/inspection/tls/tlslib" - "github.com/Safing/safing-core/log" - "github.com/Safing/safing-core/network" - "github.com/Safing/safing-core/network/netutils" - "github.com/Safing/safing-core/network/packet" + "github.com/Safing/portbase/log" + "github.com/Safing/portmaster/firewall/inspection" + "github.com/Safing/portmaster/firewall/inspection/tls/tlslib" + "github.com/Safing/portmaster/firewall/inspection/tls/verify" + "github.com/Safing/portmaster/network" + "github.com/Safing/portmaster/network/netutils" + "github.com/Safing/portmaster/network/packet" ) // TODO: @@ -31,8 +28,6 @@ var ( tlsInspectorIndex int assemblerManager *netutils.SimpleStreamAssemblerManager assembler *tcpassembly.Assembler - - config = configuration.Get() ) const ( diff --git a/firewall/inspection/tls/tls_test.go b/firewall/inspection/tls/tls_test.go index b3e89e0f..6e7b823b 100644 --- a/firewall/inspection/tls/tls_test.go +++ b/firewall/inspection/tls/tls_test.go @@ -6,7 +6,7 @@ import ( "fmt" "testing" - "github.com/Safing/safing-core/firewall/inspection/tls/tlslib" + "github.com/Safing/portmaster/firewall/inspection/tls/tlslib" ) var clientHelloSample = []byte{ diff --git a/firewall/inspection/tls/verify/cert.go b/firewall/inspection/tls/verify/cert.go index 0af7f120..27f92012 100644 --- a/firewall/inspection/tls/verify/cert.go +++ b/firewall/inspection/tls/verify/cert.go @@ -1,5 +1,3 @@ -// 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 verify import ( @@ -14,15 +12,15 @@ import ( "strings" "github.com/cloudflare/cfssl/crypto/pkcs7" - datastore "github.com/ipfs/go-datastore" - "github.com/Safing/safing-core/crypto/hash" - "github.com/Safing/safing-core/database" + "github.com/Safing/portbase/crypto/hash" + "github.com/Safing/portbase/database" + "github.com/Safing/portbase/database/record" ) // Cert saves a certificate. type Cert struct { - database.Base + record.Record cert *x509.Certificate Raw []byte @@ -120,7 +118,7 @@ func (m *Cert) CreateRevokedCert(caID string, serialNumber *big.Int) error { } // CreateInNamespace saves Cert with the provided name in the provided namespace. -func (m *Cert) CreateInNamespace(namespace *datastore.Key, name string) error { +func (m *Cert) CreateInNamespace(namespace string, name string) error { return m.CreateObject(namespace, name, m) } @@ -140,7 +138,7 @@ func GetCertWithSPKI(spki []byte) (*Cert, error) { } // GetCertFromNamespace gets Cert with the provided name from the provided namespace. -func GetCertFromNamespace(namespace *datastore.Key, name string) (*Cert, error) { +func GetCertFromNamespace(namespace string, name string) (*Cert, error) { object, err := database.GetAndEnsureModel(namespace, name, certModel) if err != nil { return nil, err diff --git a/firewall/inspection/tls/verify/cert_test.go b/firewall/inspection/tls/verify/cert_test.go index 5a9d2312..d9ebe1fd 100644 --- a/firewall/inspection/tls/verify/cert_test.go +++ b/firewall/inspection/tls/verify/cert_test.go @@ -1,5 +1,3 @@ -// 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 verify import ( diff --git a/firewall/inspection/tls/verify/crl.go b/firewall/inspection/tls/verify/crl.go index 6f5136ef..7124ed52 100644 --- a/firewall/inspection/tls/verify/crl.go +++ b/firewall/inspection/tls/verify/crl.go @@ -1,5 +1,3 @@ -// 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 verify import ( @@ -14,16 +12,15 @@ import ( "sync" "time" - datastore "github.com/ipfs/go-datastore" - - "github.com/Safing/safing-core/crypto/hash" - "github.com/Safing/safing-core/database" - "github.com/Safing/safing-core/log" + "github.com/Safing/portbase/crypto/hash" + "github.com/Safing/portbase/database" + "github.com/Safing/portbase/database/record" + "github.com/Safing/portbase/log" ) // CARevocationInfo saves Information on revokation of Certificates of a Certificate Authority. type CARevocationInfo struct { - database.Base + record.Record CRLDistributionPoints []string OCSPServers []string @@ -39,23 +36,17 @@ type CARevocationInfo struct { } var ( - caRevocationInfoModel *CARevocationInfo // only use this as parameter for database.EnsureModel-like functions - dupCrlReqMap = make(map[string]*sync.Mutex) dupCrlReqLock sync.Mutex ) -func init() { - database.RegisterModel(caRevocationInfoModel, func() database.Model { return new(CARevocationInfo) }) -} - // Create saves CARevocationInfo with the provided name in the default namespace. func (m *CARevocationInfo) Create(name string) error { return m.CreateObject(&database.CARevocationInfoCache, name, m) } // CreateInNamespace saves CARevocationInfo with the provided name in the provided namespace. -func (m *CARevocationInfo) CreateInNamespace(namespace *datastore.Key, name string) error { +func (m *CARevocationInfo) CreateInNamespace(namespace string, name string) error { return m.CreateObject(namespace, name, m) } @@ -78,7 +69,7 @@ func GetCARevocationInfo(name string) (*CARevocationInfo, error) { } // GetCARevocationInfoFromNamespace fetches CARevocationInfo with the provided name from the provided namespace. -func GetCARevocationInfoFromNamespace(namespace *datastore.Key, name string) (*CARevocationInfo, error) { +func GetCARevocationInfoFromNamespace(namespace string, name string) (*CARevocationInfo, error) { object, err := database.GetAndEnsureModel(namespace, name, caRevocationInfoModel) if err != nil { return nil, err diff --git a/firewall/inspection/tls/verify/ocsp.go b/firewall/inspection/tls/verify/ocsp.go index e638c947..b4459de5 100644 --- a/firewall/inspection/tls/verify/ocsp.go +++ b/firewall/inspection/tls/verify/ocsp.go @@ -1,5 +1,3 @@ -// 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 verify import ( @@ -16,8 +14,8 @@ import ( "golang.org/x/crypto/ocsp" - "github.com/Safing/safing-core/crypto/hash" - "github.com/Safing/safing-core/log" + "github.com/Safing/portbase/crypto/hash" + "github.com/Safing/portbase/log" ) var ( diff --git a/firewall/inspection/tls/verify/verify.go b/firewall/inspection/tls/verify/verify.go index ba1e6d1f..f162f842 100644 --- a/firewall/inspection/tls/verify/verify.go +++ b/firewall/inspection/tls/verify/verify.go @@ -1,5 +1,3 @@ -// 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 verify import ( @@ -8,9 +6,8 @@ import ( "fmt" "time" - "github.com/Safing/safing-core/configuration" - "github.com/Safing/safing-core/crypto/hash" - "github.com/Safing/safing-core/database" + "github.com/Safing/portbase/crypto/hash" + "github.com/Safing/portbase/database" ) // useful references: @@ -24,10 +21,6 @@ import ( // RE: https://www.grc.com/revocation/crlsets.htm // RE: RE: https://www.imperialviolet.org/2014/04/29/revocationagain.html -var ( - config = configuration.Get() -) - // FullCheckBytes does a full certificate check, certificates are provided as raw bytes. // It parses the raw certificates and calls FullCheck. func FullCheckBytes(name string, certBytes [][]byte) (bool, error) { diff --git a/firewall/interception/interception_windows.go b/firewall/interception/interception_windows.go index 60be31a1..681029fa 100644 --- a/firewall/interception/interception_windows.go +++ b/firewall/interception/interception_windows.go @@ -1,12 +1,10 @@ -// 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 interception import ( - "github.com/Safing/safing-core/firewall/interception/windivert" - "github.com/Safing/safing-core/log" - "github.com/Safing/safing-core/modules" - "github.com/Safing/safing-core/network/packet" + "github.com/Safing/portbase/log" + "github.com/Safing/portbase/modules" + "github.com/Safing/portmaster/firewall/interception/windivert" + "github.com/Safing/portmaster/network/packet" ) var Packets chan packet.Packet diff --git a/firewall/interception/windivert/test/main.exe b/firewall/interception/windivert/test/main.exe deleted file mode 100644 index 3e51b896..00000000 Binary files a/firewall/interception/windivert/test/main.exe and /dev/null differ diff --git a/portmaster/master.go b/firewall/master.go similarity index 95% rename from portmaster/master.go rename to firewall/master.go index 42878df4..dff661c5 100644 --- a/portmaster/master.go +++ b/firewall/master.go @@ -1,25 +1,21 @@ -// 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 portmaster +package firewall import ( "net" "os" "strings" - "github.com/Safing/safing-core/intel" - "github.com/Safing/safing-core/log" - "github.com/Safing/safing-core/network" - "github.com/Safing/safing-core/network/netutils" - "github.com/Safing/safing-core/network/packet" - "github.com/Safing/safing-core/port17/mode" - "github.com/Safing/safing-core/profiles" + "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/port17/mode" + "github.com/Safing/portmaster/profiles" "github.com/agext/levenshtein" ) -// use https://github.com/agext/levenshtein - // Call order: // // 1. DecideOnConnectionBeforeIntel (if connecting to domain) diff --git a/firewall/module.go b/firewall/module.go new file mode 100644 index 00000000..6da952f5 --- /dev/null +++ b/firewall/module.go @@ -0,0 +1,19 @@ +package firewall + +import ( + "github.com/Safing/portbase/modules" + + _ "github.com/Safing/portmaster/network" +) + +func init() { + modules.Register("firewall", nil, start, stop, "network") +} + +func start() error { + return registerAsDatabase() +} + +func stop() error { + +} diff --git a/portmaster/tunnel.go b/firewall/tunnel.go similarity index 99% rename from portmaster/tunnel.go rename to firewall/tunnel.go index c820ee42..badbb98b 100644 --- a/portmaster/tunnel.go +++ b/firewall/tunnel.go @@ -1,4 +1,4 @@ -package portmaster +package firewall import ( "errors" diff --git a/main.go b/main.go new file mode 100644 index 00000000..1e830f80 --- /dev/null +++ b/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/Safing/portbase/info" + "github.com/Safing/portbase/log" + "github.com/Safing/portbase/modules" + + // include packages here + + _ "github.com/Safing/portbase/database/dbmodule" + _ "github.com/Safing/portbase/database/storage/badger" + _ "github.com/Safing/portmaster/intel" + _ "github.com/Safing/portmaster/nameserver/only" + _ "github.com/Safing/portmaster/nameserver/only" +) + +func main() { + + // Set Info + info.Set("Portmaster", "0.0.1") + + // Start + err := modules.Start() + if err != nil { + if err == modules.ErrCleanExit { + os.Exit(0) + } else { + err = modules.Shutdown() + if err != nil { + log.Shutdown() + } + os.Exit(1) + } + } + + // Shutdown + // catch interrupt for clean shutdown + signalCh := make(chan os.Signal) + signal.Notify( + signalCh, + os.Interrupt, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT, + ) + select { + case <-signalCh: + fmt.Println(" ") + log.Warning("main: program was interrupted, shutting down.") + modules.Shutdown() + case <-modules.ShuttingDown(): + } + +} diff --git a/network/clean.go b/network/clean.go index 762aa619..6f84fd49 100644 --- a/network/clean.go +++ b/network/clean.go @@ -5,7 +5,7 @@ package network import ( "time" - "github.com/Safing/safing-core/process" + "github.com/Safing/portmaster/process" ) func init() { diff --git a/network/connection.go b/network/connection.go index e4ef9268..fa61895c 100644 --- a/network/connection.go +++ b/network/connection.go @@ -5,19 +5,20 @@ package network import ( "fmt" "net" + "sync" "time" - "github.com/Safing/portbase/database" + "github.com/Safing/portbase/database/record" "github.com/Safing/portmaster/intel" "github.com/Safing/portmaster/network/packet" "github.com/Safing/portmaster/process" - - datastore "github.com/ipfs/go-datastore" ) // Connection describes a connection between a process and a domain type Connection struct { - database.Base + record.Base + sync.Mutex + Domain string Direction bool Intel *intel.Intel @@ -26,76 +27,62 @@ type Connection struct { Reason string Inspect bool FirstLinkEstablished int64 + LastLinkEstablished int64 } -var connectionModel *Connection // only use this as parameter for database.EnsureModel-like functions - -func init() { - database.RegisterModel(connectionModel, func() database.Model { return new(Connection) }) -} - +// Process returns the process that owns the connection. func (m *Connection) Process() *process.Process { return m.process } -// Create creates a new database entry in the database in the default namespace for this object -func (m *Connection) Create(name string) error { - return m.CreateObject(&database.OrphanedConnection, name, m) -} - -// CreateInProcessNamespace creates a new database entry in the namespace of the connection's process -func (m *Connection) CreateInProcessNamespace() error { - if m.process != nil { - return m.CreateObject(m.process.GetKey(), m.Domain, m) - } - return m.CreateObject(&database.OrphanedConnection, m.Domain, m) -} - -// Save saves the object to the database (It must have been either already created or loaded from the database) -func (m *Connection) Save() error { - return m.SaveObject(m) -} - +// CantSay sets the connection verdict to "can't say", the connection will be further analysed. func (m *Connection) CantSay() { if m.Verdict != CANTSAY { m.Verdict = CANTSAY - m.SaveObject(m) + m.Save() } return } +// Drop sets the connection verdict to drop. func (m *Connection) Drop() { if m.Verdict != DROP { m.Verdict = DROP - m.SaveObject(m) + m.Save() } return } +// Block sets the connection verdict to block. func (m *Connection) Block() { if m.Verdict != BLOCK { m.Verdict = BLOCK - m.SaveObject(m) + m.Save() } return } +// Accept sets the connection verdict to accept. func (m *Connection) Accept() { if m.Verdict != ACCEPT { m.Verdict = ACCEPT - m.SaveObject(m) + m.Save() } return } // AddReason adds a human readable string as to why a certain verdict was set in regard to this connection func (m *Connection) AddReason(newReason string) { + m.Lock() + defer m.Unlock() + if m.Reason != "" { m.Reason += " | " } m.Reason += newReason } +// GetConnectionByFirstPacket returns the matching connection from the internal storage. func GetConnectionByFirstPacket(pkt packet.Packet) (*Connection, error) { // get Process proc, direction, err := process.GetProcessByPacket(pkt) @@ -154,6 +141,7 @@ var ( 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) @@ -173,38 +161,22 @@ func GetConnectionByDNSRequest(ip net.IP, port uint16, fqdn string) (*Connection return connection, nil } -// GetConnection fetches a Connection from the database from the default namespace for this object -func GetConnection(name string) (*Connection, error) { - return GetConnectionFromNamespace(&database.OrphanedConnection, name) -} +// AddLink applies the connection to the link. +func (conn *Connection) AddLink(link *Link) { + link.Lock() + defer link.Unlock() + link.connection = conn + link.Verdict = conn.Verdict + link.Inspect = conn.Inspect + link.Save() -// GetConnectionFromProcessNamespace fetches a Connection from the namespace of its process -func GetConnectionFromProcessNamespace(process *process.Process, domain string) (*Connection, error) { - return GetConnectionFromNamespace(process.GetKey(), domain) -} - -// GetConnectionFromNamespace fetches a Connection form the database, but from a custom namespace -func GetConnectionFromNamespace(namespace *datastore.Key, name string) (*Connection, error) { - object, err := database.GetAndEnsureModel(namespace, name, connectionModel) - if err != nil { - return nil, err + conn.Lock() + defer conn.Unlock() + conn.LastLinkEstablished = time.Now().Unix() + if conn.FirstLinkEstablished == 0 { + conn.FirstLinkEstablished = conn.FirstLinkEstablished } - model, ok := object.(*Connection) - if !ok { - return nil, database.NewMismatchError(object, connectionModel) - } - return model, nil -} - -func (m *Connection) AddLink(link *Link, pkt packet.Packet) { - link.connection = m - link.Verdict = m.Verdict - link.Inspect = m.Inspect - if m.FirstLinkEstablished == 0 { - m.FirstLinkEstablished = time.Now().Unix() - m.Save() - } - link.CreateInConnectionNamespace(pkt.GetConnectionID()) + conn.Save() } // FORMATTING diff --git a/network/database.go b/network/database.go new file mode 100644 index 00000000..3f83c159 --- /dev/null +++ b/network/database.go @@ -0,0 +1,116 @@ +package network + +import ( + "strconv" + "strings" + "sync" + + "github.com/Safing/portbase/database" + "github.com/Safing/portbase/database/iterator" + "github.com/Safing/portbase/database/query" + "github.com/Safing/portbase/database/record" + "github.com/Safing/portbase/database/storage" + "github.com/Safing/portmaster/process" +) + +var ( + links map[string]*Link + connections map[string]*Connection + dataLock sync.RWMutex + + dbController *database.Controller +) + +// StorageInterface provices a storage.Interface to the configuration manager. +type StorageInterface struct { + storage.InjectBase +} + +// Get returns a database record. +func (s *StorageInterface) Get(key string) (record.Record, error) { + + dataLock.RLock() + defer dataLock.RUnlock() + + splitted := strings.Split(key, "/") + switch splitted[0] { + case "tree": + switch len(splitted) { + case 2: + pid, err := strconv.Atoi(splitted[1]) + if err != nil { + return process.GetProcessByPID(pid) + } + case 3: + conn, ok := connections[splitted[2]] + if ok { + return conn, nil + } + case 4: + link, ok := links[splitted[3]] + if ok { + return link, nil + } + } + } + + return nil, storage.ErrNotFound +} + +// Query returns a an iterator for the supplied query. +func (s *StorageInterface) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) { + it := iterator.New() + go s.processQuery(q, it) + // TODO: check local and internal + + return it, nil +} + +func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) { + // processes + for _, proc := range process.All() { + if strings.HasPrefix(proc.Meta().DatabaseKey, q.DatabaseKeyPrefix()) { + it.Next <- proc + } + } + + dataLock.RLock() + defer dataLock.RUnlock() + + // connections + for _, conn := range connections { + if strings.HasPrefix(conn.Meta().DatabaseKey, q.DatabaseKeyPrefix()) { + it.Next <- conn + } + } + + // links + for _, link := range links { + if strings.HasPrefix(opt.Meta().DatabaseKey, q.DatabaseKeyPrefix()) { + it.Next <- link + } + } + + it.Finish(nil) +} + +func registerAsDatabase() error { + _, err := database.Register(&database.Database{ + Name: "network", + Description: "Network and Firewall Data", + StorageType: "injected", + PrimaryAPI: "", + }) + if err != nil { + return err + } + + controller, err := database.InjectDatabase("network", &ConfigStorageInterface{}) + if err != nil { + return err + } + + dbController = controller + process.SetDBController(dbController) + return nil +} diff --git a/network/environment/environment_darwin.go b/network/environment/environment_darwin.go new file mode 100644 index 00000000..40cd6ee8 --- /dev/null +++ b/network/environment/environment_darwin.go @@ -0,0 +1,27 @@ +package environment + +import "net" + +func Nameservers() []Nameserver { + return nil +} + +func Gateways() []*net.IP { + return nil +} + +// TODO: implement using +// ifconfig +// scutil --nwi +// scutil --proxy +// networksetup -listallnetworkservices +// networksetup -listnetworkserviceorder +// networksetup -getdnsservers "Wi-Fi" +// networksetup -getsearchdomains +// networksetup -getftpproxy +// networksetup -getwebproxy +// networksetup -getsecurewebproxy +// networksetup -getstreamingproxy +// networksetup -getgopherproxy +// networksetup -getsocksfirewallproxy +// route -n get default diff --git a/network/environment/environment_test.go b/network/environment/environment_test.go index 3f4aded8..c4db1685 100644 --- a/network/environment/environment_test.go +++ b/network/environment/environment_test.go @@ -1,4 +1,4 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. +// +build linux package environment diff --git a/network/link.go b/network/link.go index d0f43ee7..00944a84 100644 --- a/network/link.go +++ b/network/link.go @@ -3,17 +3,17 @@ package network import ( + "errors" "fmt" "sync" "time" - datastore "github.com/ipfs/go-datastore" - - "github.com/Safing/portbase/database" + "github.com/Safing/portbase/database/record" "github.com/Safing/portbase/log" "github.com/Safing/portmaster/network/packet" ) +// FirewallHandler defines the function signature for a firewall handle function type FirewallHandler func(pkt packet.Packet, link *Link) var ( @@ -22,9 +22,13 @@ var ( allLinksLock sync.RWMutex ) -// Link describes an distinct physical connection (e.g. TCP connection) - like an instance - of a Connection +// Link describes a distinct physical connection (e.g. TCP connection) - like an instance - of a Connection. type Link struct { - database.Base + record.Record + sync.Mutex + + ID string + Verdict Verdict Reason string Tunneled bool @@ -32,180 +36,167 @@ type Link struct { Inspect bool Started int64 Ended int64 - connection *Connection RemoteAddress string - ActiveInspectors []bool `json:"-" bson:"-"` - InspectorData map[uint8]interface{} `json:"-" bson:"-"` pktQueue chan packet.Packet firewallHandler FirewallHandler -} + connection *Connection -var linkModel *Link // only use this as parameter for database.EnsureModel-like functions - -func init() { - database.RegisterModel(linkModel, func() database.Model { return new(Link) }) + activeInspectors []bool + inspectorData map[uint8]interface{} } // Connection returns the Connection the Link is part of -func (m *Link) Connection() *Connection { - return m.connection +func (link *Link) Connection() *Connection { + return link.connection } // FirewallHandlerIsSet returns whether a firewall handler is set or not -func (m *Link) FirewallHandlerIsSet() bool { - return m.firewallHandler != nil +func (link *Link) FirewallHandlerIsSet() bool { + return link.firewallHandler != nil } // SetFirewallHandler sets the firewall handler for this link -func (m *Link) SetFirewallHandler(handler FirewallHandler) { - if m.firewallHandler == nil { - m.firewallHandler = handler - m.pktQueue = make(chan packet.Packet, 1000) - go m.packetHandler() +func (link *Link) SetFirewallHandler(handler FirewallHandler) { + if link.firewallHandler == nil { + link.firewallHandler = handler + link.pktQueue = make(chan packet.Packet, 1000) + go link.packetHandler() return } - m.firewallHandler = handler + link.firewallHandler = handler } // StopFirewallHandler unsets the firewall handler -func (m *Link) StopFirewallHandler() { - m.pktQueue <- nil +func (link *Link) StopFirewallHandler() { + link.pktQueue <- nil } // HandlePacket queues packet of Link for handling -func (m *Link) HandlePacket(pkt packet.Packet) { - if m.firewallHandler != nil { - m.pktQueue <- pkt +func (link *Link) HandlePacket(pkt packet.Packet) { + if link.firewallHandler != nil { + link.pktQueue <- pkt return } - log.Criticalf("network: link %s does not have a firewallHandler, maybe its a copy, dropping packet", m) + log.Criticalf("network: link %s does not have a firewallHandler (maybe it's a copy), dropping packet", link) pkt.Drop() } // UpdateVerdict sets a new verdict for this link, making sure it does not interfere with previous verdicts -func (m *Link) UpdateVerdict(newVerdict Verdict) { - if newVerdict > m.Verdict { - m.Verdict = newVerdict - m.Save() +func (link *Link) UpdateVerdict(newVerdict Verdict) { + if newVerdict > link.Verdict { + link.Verdict = newVerdict + link.Save() } } // AddReason adds a human readable string as to why a certain verdict was set in regard to this link -func (m *Link) AddReason(newReason string) { - if m.Reason != "" { - m.Reason += " | " +func (link *Link) AddReason(newReason string) { + link.Lock() + defer link.Unlock() + + if link.Reason != "" { + link.Reason += " | " } - m.Reason += newReason + link.Reason += newReason } // packetHandler sequentially handles queued packets -func (m *Link) packetHandler() { +func (link *Link) packetHandler() { for { - pkt := <-m.pktQueue + pkt := <-link.pktQueue if pkt == nil { break } - m.firewallHandler(pkt, m) + link.firewallHandler(pkt, link) } - m.firewallHandler = nil + link.firewallHandler = nil } -// Create creates a new database entry in the database in the default namespace for this object -func (m *Link) Create(name string) error { - m.CreateShallow(name) - return m.CreateObject(&database.OrphanedLink, name, m) -} - -// Create creates a new database entry in the database in the default namespace for this object -func (m *Link) CreateShallow(name string) { - allLinksLock.Lock() - allLinks[name] = m - allLinksLock.Unlock() -} - -// CreateWithDefaultKey creates a new database entry in the database in the default namespace for this object using the default key -func (m *Link) CreateInConnectionNamespace(name string) error { - if m.connection != nil { - return m.CreateObject(m.connection.GetKey(), name, m) +// Save saves the link object in the storage and propagates the change. +func (link *Link) Save() error { + if link.connection == nil { + return errors.New("cannot save link without connection") } - return m.CreateObject(&database.OrphanedLink, name, m) + + if link.DatabaseKey() == "" { + + link.SetKey(fmt.Sprintf("network:tree/%d/%s/%s", link.connection.Process().Pid, link.connection.Domain, link.ID)) + link.CreateMeta() + + dataLock.Lock() + defer dataLock.Unlock() + + links[link.ID] = link + } + + if link.orphaned && link.connection != nil { + p.SetKey() + } + + dbController.PushUpdate(link) } -// Save saves the object to the database (It must have been either already created or loaded from the database) -func (m *Link) Save() error { - return m.SaveObject(m) +// Delete deletes a link from the storage and propagates the change. +func (link *Link) Delete() { + dataLock.Lock() + defer dataLock.Unlock() + delete(links, link.ID) + link.Lock() + defer link.Lock() + link.Meta().Delete() + dbController.PushUpdate(link) } // GetLink fetches a Link from the database from the default namespace for this object -func GetLink(name string) (*Link, error) { - allLinksLock.RLock() - link, ok := allLinks[name] - allLinksLock.RUnlock() - if !ok { - return nil, database.ErrNotFound - } - return link, nil - // return GetLinkFromNamespace(&database.RunningLink, name) -} +func GetLink(id string) (*Link, bool) { + dataLock.RLock() + defer dataLock.RUnlock() -func SaveInCache(link *Link) { - -} - -// GetLinkFromNamespace fetches a Link form the database, but from a custom namespace -func GetLinkFromNamespace(namespace *datastore.Key, name string) (*Link, error) { - object, err := database.GetAndEnsureModel(namespace, name, linkModel) - if err != nil { - return nil, err - } - model, ok := object.(*Link) - if !ok { - return nil, database.NewMismatchError(object, linkModel) - } - return model, nil + link, ok := links[id] + return link, ok } // GetOrCreateLinkByPacket returns the associated Link for a packet and a bool expressing if the Link was newly created func GetOrCreateLinkByPacket(pkt packet.Packet) (*Link, bool) { - link, err := GetLink(pkt.GetConnectionID()) - if err != nil { - return CreateLinkFromPacket(pkt), true + link, ok := GetLink(pkt.GetConnectionID()) + if ok { + return link, false } - return link, false + return CreateLinkFromPacket(pkt), true } -// CreateLinkFromPacket creates a new Link based on Packet. The Link is shallowly saved and SHOULD be saved to the database as soon more information is available +// CreateLinkFromPacket creates a new Link based on Packet. func CreateLinkFromPacket(pkt packet.Packet) *Link { link := &Link{ + ID: pkt.GetConnectionID(), Verdict: UNDECIDED, Started: time.Now().Unix(), RemoteAddress: pkt.FmtRemoteAddress(), } - link.CreateShallow(pkt.GetConnectionID()) return link } -// FORMATTING -func (m *Link) String() string { - if m.connection == nil { - return fmt.Sprintf("? <-> %s", m.RemoteAddress) +// String returns a string representation of Link. +func (link *Link) String() string { + if link.connection == nil { + return fmt.Sprintf("? <-> %s", link.RemoteAddress) } - switch m.connection.Domain { + switch link.connection.Domain { case "I": - if m.connection.process == nil { - return fmt.Sprintf("? <- %s", m.RemoteAddress) + if link.connection.process == nil { + return fmt.Sprintf("? <- %s", link.RemoteAddress) } - return fmt.Sprintf("%s <- %s", m.connection.process.String(), m.RemoteAddress) + return fmt.Sprintf("%s <- %s", link.connection.process.String(), link.RemoteAddress) case "D": - if m.connection.process == nil { - return fmt.Sprintf("? -> %s", m.RemoteAddress) + if link.connection.process == nil { + return fmt.Sprintf("? -> %s", link.RemoteAddress) } - return fmt.Sprintf("%s -> %s", m.connection.process.String(), m.RemoteAddress) + return fmt.Sprintf("%s -> %s", link.connection.process.String(), link.RemoteAddress) default: - if m.connection.process == nil { - return fmt.Sprintf("? -> %s (%s)", m.connection.Domain, m.RemoteAddress) + if link.connection.process == nil { + return fmt.Sprintf("? -> %s (%s)", link.connection.Domain, link.RemoteAddress) } - return fmt.Sprintf("%s to %s (%s)", m.connection.process.String(), m.connection.Domain, m.RemoteAddress) + return fmt.Sprintf("%s to %s (%s)", link.connection.process.String(), link.connection.Domain, link.RemoteAddress) } } diff --git a/network/module.go b/network/module.go new file mode 100644 index 00000000..2b598d4f --- /dev/null +++ b/network/module.go @@ -0,0 +1,13 @@ +package network + +import ( + "github.com/Safing/portbase/modules" +) + +func init() { + modules.Register("network", prep, start, nil, "database") +} + +func start() error { + return registerAsDatabase() +} diff --git a/network/status.go b/network/status.go index a9976945..bc77620c 100644 --- a/network/status.go +++ b/network/status.go @@ -2,7 +2,7 @@ package network -// Status describes the status of a connection. +// Verdict describes the decision made about a connection or link. type Verdict uint8 // List of values a Status can have @@ -15,6 +15,7 @@ const ( DROP ) +// Packer Directions const ( Inbound = true Outbound = false diff --git a/network/unknown.go b/network/unknown.go new file mode 100644 index 00000000..060fbd7f --- /dev/null +++ b/network/unknown.go @@ -0,0 +1,31 @@ +package network + +import "github.com/Safing/portmaster/process" + +// Static reasons +const ( + ReasonUnknownProcess = "unknown connection owner: process could not be found" +) + +var ( + UnknownDirectConnection = &Connection{ + Domain: "D", + Direction: Outbound, + Verdict: DROP, + Reason: ReasonUnknownProcess, + process: process.UnknownProcess, + } + + UnknownIncomingConnection = &Connection{ + Domain: "I", + Direction: Inbound, + Verdict: DROP, + Reason: ReasonUnknownProcess, + process: process.UnknownProcess, + } +) + +func init() { + UnknownDirectConnection.Save() + UnknownIncomingConnection.Save() +} diff --git a/process/database.go b/process/database.go new file mode 100644 index 00000000..d47f89a1 --- /dev/null +++ b/process/database.go @@ -0,0 +1,69 @@ +package process + +import ( + "fmt" + "sync" + + "github.com/Safing/portbase/database" + "github.com/tevino/abool" +) + +var ( + processes = make(map[int]*Process) + processesLock sync.RWMutex + + dbController *database.Controller + dbControllerFlag = abool.NewBool(false) +) + +func makeProcessKey(pid int) string { + return fmt.Sprintf("network:tree/%d", pid) +} + +// GetProcessFromStorage returns a process from the internal storage. +func GetProcessFromStorage(pid int) (*Process, bool) { + processesLock.RLock() + defer processesLock.RUnlock() + + p, ok := processes[pid] + return p, ok +} + +// All returns a copy of all process objects. +func All() []*Process { + processesLock.RLock() + defer processesLock.RUnlock() + + all := make([]*Process, 0, len(processes)) + for _, proc := range processes { + all = append(all, proc) + } + + return all +} + +// Save saves the process to the internal state and pushes an update. +func (p *Process) Save() { + p.Lock() + defer p.Unlock() + + if p.DatabaseKey() == "" { + p.SetKey(makeProcessKey(p.Pid)) + p.CreateMeta() + + processesLock.Lock() + defer processesLock.Unlock() + + processes[p.Pid] = p + } + + if dbControllerFlag.IsSet() { + dbController.PushUpdate(p) + } +} + +// SetDBController sets the database controller and allows the package to push database updates on a save. It must be set by the package that registers the "network" database. +func SetDBController(controller *database.Controller) { + dbController = controller + dbControllerFlag.Set() +} diff --git a/process/doc.go b/process/doc.go index bc77fb9c..6db99ce2 100644 --- a/process/doc.go +++ b/process/doc.go @@ -1,21 +1,7 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - /* - -Profiles - -Profiles describe the network behaviour - - -Profiles are found in 3 different paths: -- /Me/Profiles/: Profiles used for this system -- /Data/Profiles/: Profiles supplied by Safing -- /Company/Profiles/: Profiles supplied by the company - -When a program wants to use the network for the first time, Safing first searches for a Profile in the Company namespace, then in the Data namespace. If neither is found, it searches for a default profile in the same order. - -Default profiles are profiles with a path ending with a "/". The default profile with the longest matching path is chosen. +Package process fetches process and socket information from the operating system. +It can find the process owning a network connection. */ package process diff --git a/process/find.go b/process/find.go index 7c3d5cb7..b8e9989f 100644 --- a/process/find.go +++ b/process/find.go @@ -7,11 +7,13 @@ import ( "github.com/Safing/portmaster/network/packet" ) +// Errors var ( ErrConnectionNotFound = errors.New("could not find connection") ErrProcessNotFound = errors.New("could not find process") ) +// GetPidByPacket returns the pid of the owner of the packet. func GetPidByPacket(pkt packet.Packet) (pid int, direction bool, err error) { var localIP net.IP @@ -50,6 +52,7 @@ func GetPidByPacket(pkt packet.Packet) (pid int, direction bool, err error) { } +// GetProcessByPacket returns the process that owns the given packet. func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, err error) { var pid int @@ -70,6 +73,7 @@ func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, er } +// GetPidByEndpoints returns the pid of the owner of the described link. func GetPidByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (pid int, direction bool, err error) { ipVersion := packet.IPv4 @@ -92,6 +96,7 @@ func GetPidByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remote } +// GetProcessByEndpoints returns the process that owns the described link. func GetProcessByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (process *Process, err error) { var pid int @@ -112,37 +117,7 @@ func GetProcessByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, re } +// GetActiveConnectionIDs returns a list of all active connection IDs. func GetActiveConnectionIDs() []string { return getActiveConnectionIDs() } - -// func GetProcessByPid(pid int) *Process { -// process, err := GetOrFindProcess(pid) -// if err != nil { -// log.Warningf("process: failed to get process %d: %s", pid, err) -// return nil -// } -// return process -// } - -// func GetProcessOfConnection(localIP *net.IP, localPort uint16, protocol uint8) (process *Process, status uint8) { -// pid, status := GetPidOfConnection(localIP, localPort, protocol) -// if status == Success { -// process = GetProcessByPid(pid) -// if process == nil { -// return nil, NoProcessInfo -// } -// } -// return -// } - -// func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, status uint8) { -// pid, direction, status := GetPidByPacket(pkt) -// if status == Success { -// process = GetProcessByPid(pid) -// if process == nil { -// return nil, direction, NoProcessInfo -// } -// } -// return -// } diff --git a/process/getpid_linux.go b/process/getpid_linux.go index 79c2a9db..49f26e17 100644 --- a/process/getpid_linux.go +++ b/process/getpid_linux.go @@ -1,6 +1,8 @@ package process -import "github.com/Safing/portmaster/process/proc" +import ( + "github.com/Safing/portmaster/process/proc" +) var ( getTCP4PacketInfo = proc.GetTCP4PacketInfo diff --git a/process/iphelper/test/test.exe b/process/iphelper/test/test.exe deleted file mode 100644 index 7848762d..00000000 Binary files a/process/iphelper/test/test.exe and /dev/null differ diff --git a/process/process.go b/process/process.go index 33b2f636..36e3dbef 100644 --- a/process/process.go +++ b/process/process.go @@ -3,10 +3,8 @@ package process import ( - "errors" "fmt" "runtime" - "strconv" "sync" processInfo "github.com/shirou/gopsutil/process" @@ -36,11 +34,7 @@ type Process struct { // 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. } -// GetProcess fetches Process with the provided name from the default namespace. -func GetProcess(name string) (*Process, error) { - return nil, errors.New("NIY") -} - +// Strings returns a string represenation of process func (m *Process) String() string { if m == nil { return "?" @@ -48,9 +42,10 @@ func (m *Process) String() string { return fmt.Sprintf("%s:%s:%d", m.UserName, m.Path, m.Pid) } +// GetOrFindProcess returns the process for the given PID. func GetOrFindProcess(pid int) (*Process, error) { - process, err := GetProcess(strconv.Itoa(pid)) - if err == nil { + process, ok := GetProcessFromStorage(pid) + if ok { return process, nil } @@ -59,7 +54,7 @@ func GetOrFindProcess(pid int) (*Process, error) { } switch { - case (pid == 0 && runtime.GOOS == "linux") || (pid == 4 && runtime.GOOS == "windows"): + case new.IsKernel(): new.UserName = "Kernel" new.Name = "Operating System" default: @@ -72,7 +67,8 @@ func GetOrFindProcess(pid int) (*Process, error) { // UID // net yet implemented for windows if runtime.GOOS == "linux" { - uids, err := pInfo.Uids() + var uids []int32 + uids, err = pInfo.Uids() if err != nil { log.Warningf("process: failed to get UID: %s", err) } else { @@ -203,8 +199,8 @@ func GetOrFindProcess(pid int) (*Process, error) { } - // save to DB - // new.Save() + // save to storage + new.Save() return new, nil } diff --git a/process/process_linux.go b/process/process_linux.go index edfaa9be..ebfa4104 100644 --- a/process/process_linux.go +++ b/process/process_linux.go @@ -1,13 +1,21 @@ package process +// IsUser returns whether the process is run by a normal user. func (m *Process) IsUser() bool { return m.UserID >= 1000 } +// IsAdmin returns whether the process is run by an admin user. func (m *Process) IsAdmin() bool { return m.UserID >= 0 } +// IsSystem returns whether the process is run by the operating system. func (m *Process) IsSystem() bool { return m.UserID == 0 } + +// IsKernel returns whether the process is the Kernel. +func (m *Process) IsKernel() bool { + return m.Pid == 0 +} diff --git a/process/process_windows.go b/process/process_windows.go index 9a16fe51..b1e38349 100644 --- a/process/process_windows.go +++ b/process/process_windows.go @@ -2,15 +2,23 @@ package process import "strings" +// IsUser returns whether the process is run by a normal user. func (m *Process) IsUser() bool { return m.Pid != 4 && // Kernel !strings.HasPrefix(m.UserName, "NT-") // NT-Authority (localized!) } +// IsAdmin returns whether the process is run by an admin user. func (m *Process) IsAdmin() bool { return strings.HasPrefix(m.UserName, "NT-") // NT-Authority (localized!) } +// IsSystem returns whether the process is run by the operating system. func (m *Process) IsSystem() bool { return m.Pid == 4 } + +// IsKernel returns whether the process is the Kernel. +func (m *Process) IsKernel() bool { + return m.Pid == 4 +} diff --git a/process/unknown.go b/process/unknown.go new file mode 100644 index 00000000..cbadfb35 --- /dev/null +++ b/process/unknown.go @@ -0,0 +1,15 @@ +package process + +var ( + UnknownProcess = &Process{ + UserID: -1, + UserName: "Unknown", + Pid: -1, + ParentPid: -1, + Name: "Unknown Processes", + } +) + +func init() { + UnknownProcess.Save() +} diff --git a/profile/const_darwin.go b/profile/const_darwin.go new file mode 100644 index 00000000..23c9f82a --- /dev/null +++ b/profile/const_darwin.go @@ -0,0 +1,6 @@ +package profile + +// OS Identifier Prefix +const ( + IdentifierPrefix = "mac:" +) diff --git a/profile/profile.go b/profile/profile.go index d98b1746..fa9af7b9 100644 --- a/profile/profile.go +++ b/profile/profile.go @@ -4,7 +4,7 @@ import ( "fmt" "sync" - "github.com/satori/go.uuid" + uuid "github.com/satori/go.uuid" "github.com/Safing/portbase/database/record" "github.com/Safing/portmaster/status" @@ -49,10 +49,12 @@ func New() *Profile { // Save saves the profile to the database func (profile *Profile) Save(namespace string) error { if profile.ID == "" { - u, err := uuid.NewV4() - if err != nil { - return err - } + // FIXME: this is weird, the docs says that it also returns an error + u := uuid.NewV4() + // u, err := uuid.NewV4() + // if err != nil { + // return err + // } profile.ID = u.String() } diff --git a/status/set.go b/status/set.go index 65a29a47..f46b3ded 100644 --- a/status/set.go +++ b/status/set.go @@ -5,41 +5,41 @@ import "sync/atomic" // SetCurrentSecurityLevel sets the current security level. func SetCurrentSecurityLevel(level uint8) { sysStatusLock.Lock() - defer sysStatusLock.Unlock() - sysStatus.CurrentSecurityLevel = level - atomicUpdateCurrentSecurityLevel(level) + defer sysStatusLock.Unlock() + sysStatus.CurrentSecurityLevel = level + atomicUpdateCurrentSecurityLevel(level) } // SetSelectedSecurityLevel sets the selected security level. func SetSelectedSecurityLevel(level uint8) { sysStatusLock.Lock() - defer sysStatusLock.Unlock() - sysStatus.SelectedSecurityLevel = level - atomicUpdateSelectedSecurityLevel(level) + defer sysStatusLock.Unlock() + sysStatus.SelectedSecurityLevel = level + atomicUpdateSelectedSecurityLevel(level) } // SetThreatLevel sets the current threat level. func SetThreatLevel(level uint8) { sysStatusLock.Lock() - defer sysStatusLock.Unlock() - sysStatus.ThreatLevel = level - atomicUpdateThreatLevel(level) + defer sysStatusLock.Unlock() + sysStatus.ThreatLevel = level + atomicUpdateThreatLevel(level) } // SetPortmasterStatus sets the current Portmaster status. func SetPortmasterStatus(status uint8) { sysStatusLock.Lock() - defer sysStatusLock.Unlock() - sysStatus.PortmasterStatus = status - atomicUpdatePortmasterStatus(status) + defer sysStatusLock.Unlock() + sysStatus.PortmasterStatus = status + atomicUpdatePortmasterStatus(status) } // SetGate17Status sets the current Gate17 status. func SetGate17Status(status uint8) { sysStatusLock.Lock() - defer sysStatusLock.Unlock() - sysStatus.Gate17Status = status - atomicUpdateGate17Status(status) + defer sysStatusLock.Unlock() + sysStatus.Gate17Status = status + atomicUpdateGate17Status(status) } // update functions for atomic stuff