package network import ( "context" "errors" "fmt" "net" "runtime" "sync" "sync/atomic" "time" "github.com/tevino/abool" "github.com/safing/portmaster/base/database/record" "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/base/notifications" "github.com/safing/portmaster/service/intel" "github.com/safing/portmaster/service/netenv" "github.com/safing/portmaster/service/network/netutils" "github.com/safing/portmaster/service/network/packet" "github.com/safing/portmaster/service/network/reference" "github.com/safing/portmaster/service/process" _ "github.com/safing/portmaster/service/process/tags" "github.com/safing/portmaster/service/resolver" "github.com/safing/portmaster/spn/access" "github.com/safing/portmaster/spn/access/account" "github.com/safing/portmaster/spn/navigator" ) // FirewallHandler defines the function signature for a firewall // handle function. A firewall handler is responsible for finding // a reasonable verdict for the connection conn. The connection is // locked before the firewall handler is called. type FirewallHandler func(conn *Connection, pkt packet.Packet) // ProcessContext holds additional information about the process // that initiated a connection. type ProcessContext struct { // ProcessName is the name of the process. ProcessName string // ProfileName is the name of the profile. ProfileName string // BinaryPath is the path to the process binary. BinaryPath string // CmdLine holds the execution parameters. CmdLine string // PID is the process identifier. PID int // CreatedAt the time when the process was created. CreatedAt int64 // Profile is the ID of the main profile that // is applied to the process. Profile string // Source is the source of the profile. Source string } // ConnectionType is a type of connection. type ConnectionType int8 // Connection Types. const ( Undefined ConnectionType = iota IPConnection DNSRequest ) // Connection describes a distinct physical network connection // identified by the IP/Port pair. type Connection struct { //nolint:maligned // TODO: fix alignment record.Base sync.Mutex // ID holds a unique request/connection id and is considered immutable after // creation. ID string // Type defines the connection type. Type ConnectionType // External defines if the connection represents an external request or // connection. External bool // Scope defines the scope of a connection. For DNS requests, the // scope is always set to the domain name. For direct packet // connections the scope consists of the involved network environment // and the packet direction. Once a connection object is created, // Scope is considered immutable. // Deprecated: This field holds duplicate information, which is accessible // clearer through other attributes. Please use conn.Type, conn.Inbound // and conn.Entity.Domain instead. Scope string // IPVersion is set to the packet IP version. It is not set (0) for // connections created from a DNS request. IPVersion packet.IPVersion // Inbound is set to true if the connection is incoming. Inbound is // only set when a connection object is created and is considered // immutable afterwards. Inbound bool // IPProtocol is set to the transport protocol used by the connection. // Is is considered immutable once a connection object has been // created. IPProtocol is not set for connections that have been // created from a DNS request. IPProtocol packet.IPProtocol // LocalIP holds the local IP address of the connection. It is not // set for connections created from DNS requests. LocalIP is // considered immutable once a connection object has been created. LocalIP net.IP // LocalIPScope holds the network scope of the local IP. LocalIPScope netutils.IPScope // LocalPort holds the local port of the connection. It is not // set for connections created from DNS requests. LocalPort is // considered immutable once a connection object has been created. LocalPort uint16 // PID holds the PID of the owning process. PID int // Entity describes the remote entity that the connection has been // established to. The entity might be changed or information might // be added to it during the livetime of a connection. Access to // entity must be guarded by the connection lock. Entity *intel.Entity // Resolver holds information about the resolver used to resolve // Entity.Domain. Resolver *resolver.ResolverInfo // Verdict holds the decisions that are made for a connection // The verdict may change so any access to it must be guarded by the // connection lock. Verdict Verdict // Whether or not the connection has been established at least once. ConnectionEstablished bool // Reason holds information justifying the verdict, as well as additional // information about the reason. // Access to Reason must be guarded by the connection lock. Reason Reason // Started holds the number of seconds in UNIX epoch time at which // the connection has been initiated and first seen by the portmaster. // Started is only ever set when creating a new connection object // and is considered immutable afterwards. Started int64 // Ended is set to the number of seconds in UNIX epoch time at which // the connection is considered terminated. Ended may be set at any // time so access must be guarded by the connection lock. Ended int64 // VerdictPermanent is set to true if the final verdict is permanent // and the connection has been (or will be) handed back to the kernel. // VerdictPermanent may be changed together with the Verdict and Reason // properties and must be guarded using the connection lock. VerdictPermanent bool // Inspecting is set to true if the connection is being inspected // by one or more of the registered inspectors. This property may // be changed during the lifetime of a connection and must be guarded // using the connection lock. Inspecting bool // Tunneled is set to true when the connection has been routed through the // SPN. Tunneled bool // Encrypted is currently unused and MUST be ignored. Encrypted bool // TunnelOpts holds options for tunneling the connection. TunnelOpts *navigator.Options // ProcessContext holds additional information about the process // that initiated the connection. It is set once when the connection // object is created and is considered immutable afterwards. ProcessContext ProcessContext // DNSContext holds additional information about the DNS request that was // probably used to resolve the IP of this connection. DNSContext *resolver.DNSRequestContext // TunnelContext holds additional information about the tunnel that this // connection is using. TunnelContext interface { GetExitNodeID() string StopTunnel() error } // HistoryEnabled is set to true when the connection should be persisted // in the history database. HistoryEnabled bool // BanwidthEnabled is set to true if connection bandwidth data should be persisted // in netquery. BandwidthEnabled bool // BytesReceived holds the observed received bytes of the connection. BytesReceived uint64 // BytesSent holds the observed sent bytes of the connection. BytesSent uint64 // lastSeen holds the timestamp when the connection was last seen. // If permanent verdicts are enabled and bandwidth reporting is not active, // this value will likely not be correct. lastSeen atomic.Int64 // prompt holds the active prompt for this connection, if there is one. prompt *notifications.Notification // promptLock locks the prompt separately from the connection. // This allows goroutines to dismiss the notification, while another goroutine // is waiting for the prompt and holding a lock on the connection. promptLock sync.Mutex // pkgQueue is used to serialize packet handling for a single // connection and is served by the connections packetHandler. pktQueue chan packet.Packet // pktQueueActive signifies whether the packet queue is active and may be written to. pktQueueActive bool // pktQueueLock locks access to pktQueueActive and writing to pktQueue. pktQueueLock sync.Mutex // dataComplete signifies that all information about the connection is // available and an actual packet has been seen. // As long as this flag is not set, the connection may not be evaluated for // a verdict and may not be sent to the UI. dataComplete *abool.AtomicBool // Internal is set to true if the connection is attributed as an // Portmaster internal connection. Internal may be set at different // points and access to it must be guarded by the connection lock. Internal bool // process holds a reference to the actor process. That is, the // process instance that initiated the connection. process *process.Process // firewallHandler is the firewall handler that is called for // each packet sent to pktQueue. firewallHandler FirewallHandler // saveWhenFinished can be set to true during the life-time of // a connection and signals the firewallHandler that a Save() // should be issued after processing the connection. saveWhenFinished bool // activeInspectors is a slice of booleans where each entry // maps to the index of an available inspector. If the value // is true the inspector is currently active. False indicates // that the inspector has finished and should be skipped. activeInspectors []bool // inspectorData holds additional meta data for the inspectors. // using the inspectors index as a map key. inspectorData map[uint8]interface{} // ProfileRevisionCounter is used to track changes to the process // profile and required for correct re-evaluation of a connections // verdict. ProfileRevisionCounter uint64 // addedToMetrics signifies if the connection has already been counted in // the metrics. addedToMetrics bool } // Reason holds information justifying a verdict, as well as additional // information about the reason. type Reason struct { // Msg is a human readable description of the reason. Msg string // OptionKey is the configuration option key of the setting that // was responsible for the verdict. OptionKey string // Profile is the database key of the profile that held the setting // that was responsible for the verdict. Profile string // ReasonContext may hold additional reason-specific information and // any access must be guarded by the connection lock. Context interface{} } func getProcessContext(ctx context.Context, proc *process.Process) ProcessContext { // Gather process information. pCtx := ProcessContext{ ProcessName: proc.Name, BinaryPath: proc.Path, CmdLine: proc.CmdLine, PID: proc.Pid, CreatedAt: proc.CreatedAt, } // Get local profile. localProfile := proc.Profile().LocalProfile() if localProfile == nil { log.Tracer(ctx).Warningf("network: process %s has no profile", proc) return pCtx } // Add profile information and return. pCtx.ProfileName = localProfile.Name pCtx.Profile = localProfile.ID pCtx.Source = string(localProfile.Source) return pCtx } // NewConnectionFromDNSRequest returns a new connection based on the given dns request. func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []string, connID string, localIP net.IP, localPort uint16) *Connection { // Determine IP version. ipVersion := packet.IPv6 if localIP.To4() != nil { ipVersion = packet.IPv4 } // Create packet info for dns request connection. pi := &packet.Info{ Inbound: false, // outbound as we are looking for the process of the source address Version: ipVersion, Protocol: packet.UDP, Src: localIP, // source as in the process we are looking for SrcPort: localPort, // source as in the process we are looking for Dst: nil, // do not record direction DstPort: 0, // do not record direction PID: process.UndefinedProcessID, } // Check if the dns request connection was reported with process info. var proc *process.Process dnsRequestConn, ok := GetDNSRequestConnection(pi) switch { case !ok: // No dns request connection found. case dnsRequestConn.PID < 0: // Process is not identified or is special. case dnsRequestConn.Ended > 0 && dnsRequestConn.Ended < time.Now().Unix()-3: // Connection has already ended (too long ago). log.Tracer(ctx).Debugf("network: found ended dns request connection %s for dns request for %s", dnsRequestConn, fqdn) default: log.Tracer(ctx).Debugf("network: found matching dns request connection %s", dnsRequestConn.String()) // Inherit PID. pi.PID = dnsRequestConn.PID // Inherit process struct itself, as the PID may already be re-used. proc = dnsRequestConn.process } // Find process by remote IP/Port. if pi.PID == process.UndefinedProcessID { pi.PID, _, _ = process.GetPidOfConnection( ctx, pi, ) } // Get process and profile with PID. if proc == nil { proc, _ = process.GetProcessWithProfile(ctx, pi.PID) } timestamp := time.Now().Unix() dnsConn := &Connection{ ID: connID, Type: DNSRequest, Scope: fqdn, PID: proc.Pid, Entity: &intel.Entity{ Domain: fqdn, CNAME: cnames, IPScope: netutils.Global, // Assign a global IP scope as default. }, process: proc, ProcessContext: getProcessContext(ctx, proc), Started: timestamp, Ended: timestamp, dataComplete: abool.NewBool(true), } dnsConn.lastSeen.Store(timestamp) // Inherit internal status of profile. if localProfile := proc.Profile().LocalProfile(); localProfile != nil { dnsConn.Internal = localProfile.Internal if err := dnsConn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) { log.Tracer(ctx).Warningf("network: failed to check for enabled features: %s", err) } } // DNS Requests are saved by the nameserver depending on the result of the // query. Blocked requests are saved immediately, accepted ones are only // saved if they are not "used" by a connection. dnsConn.UpdateMeta() return dnsConn } // NewConnectionFromExternalDNSRequest returns a connection for an external DNS request. func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cnames []string, connID string, remoteIP net.IP) (*Connection, error) { remoteHost, err := process.GetNetworkHost(ctx, remoteIP) if err != nil { return nil, err } timestamp := time.Now().Unix() dnsConn := &Connection{ ID: connID, Type: DNSRequest, External: true, Scope: fqdn, PID: process.NetworkHostProcessID, Entity: &intel.Entity{ Domain: fqdn, CNAME: cnames, IPScope: netutils.Global, // Assign a global IP scope as default. }, process: remoteHost, ProcessContext: getProcessContext(ctx, remoteHost), Started: timestamp, Ended: timestamp, dataComplete: abool.NewBool(true), } dnsConn.lastSeen.Store(timestamp) // Inherit internal status of profile. if localProfile := remoteHost.Profile().LocalProfile(); localProfile != nil { dnsConn.Internal = localProfile.Internal if err := dnsConn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) { log.Tracer(ctx).Warningf("network: failed to check for enabled features: %s", err) } } // DNS Requests are saved by the nameserver depending on the result of the // query. Blocked requests are saved immediately, accepted ones are only // saved if they are not "used" by a connection. dnsConn.UpdateMeta() return dnsConn, nil } var tooOldTimestamp = time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix() // NewIncompleteConnection creates a new incomplete connection with only minimal information. func NewIncompleteConnection(pkt packet.Packet) *Connection { info := pkt.Info() // Create new connection object. // We do not yet know the direction of the connection for sure, so we can only set minimal information. conn := &Connection{ ID: pkt.GetConnectionID(), Type: IPConnection, IPVersion: info.Version, IPProtocol: info.Protocol, Started: info.SeenAt.Unix(), PID: info.PID, Inbound: info.Inbound, dataComplete: abool.NewBool(false), } conn.lastSeen.Store(conn.Started) // Bullshit check Started timestamp. if conn.Started < tooOldTimestamp { // Fix timestamp, use current time as fallback. conn.Started = time.Now().Unix() } // Save connection to internal state in order to mitigate creation of // duplicates. Do not propagate yet, as data is not yet complete. conn.UpdateMeta() conns.add(conn) return conn } // GatherConnectionInfo gathers information on the process and remote entity. func (conn *Connection) GatherConnectionInfo(pkt packet.Packet) (err error) { // Create remote entity. if conn.Entity == nil { // Remote conn.Entity = (&intel.Entity{ IP: pkt.Info().RemoteIP(), Protocol: uint8(pkt.Info().Protocol), Port: pkt.Info().RemotePort(), }).Init(pkt.Info().DstPort) // Local conn.SetLocalIP(pkt.Info().LocalIP()) conn.LocalPort = pkt.Info().LocalPort() if conn.Inbound { switch conn.Entity.IPScope { case netutils.HostLocal: conn.Scope = IncomingHost case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast: conn.Scope = IncomingLAN case netutils.Global, netutils.GlobalMulticast: conn.Scope = IncomingInternet case netutils.Undefined, netutils.Invalid: fallthrough default: conn.Scope = IncomingInvalid } } else { // Outbound direct (possibly P2P) connection. switch conn.Entity.IPScope { case netutils.HostLocal: conn.Scope = PeerHost case netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast: conn.Scope = PeerLAN case netutils.Global, netutils.GlobalMulticast: conn.Scope = PeerInternet case netutils.Undefined, netutils.Invalid: fallthrough default: conn.Scope = PeerInvalid } } } // Get PID if not yet available. if conn.PID == process.UndefinedProcessID { // Get process by looking at the system state tables. // Apply direction as reported from the state tables. conn.PID, conn.Inbound, _ = process.GetPidOfConnection(pkt.Ctx(), pkt.Info()) // Errors are informational and are logged to the context. } // Only get process and profile with first real packet. // TODO: Remove when we got full VM/Docker support. if pkt.InfoOnly() { return nil } // Get Process and Profile. if conn.process == nil { conn.process, err = process.GetProcessWithProfile(pkt.Ctx(), conn.PID) // Errors are informational and are logged to the context. if err != nil { if pkt.InfoOnly() { conn.process = nil // Try again with real packet. log.Tracer(pkt.Ctx()).Debugf("network: failed to get process and profile of PID %d: %s", conn.PID, err) } else { log.Tracer(pkt.Ctx()).Warningf("network: failed to get process and profile of PID %d: %s", conn.PID, err) } } } // Apply process/profile info to connection. if conn.ProfileRevisionCounter == 0 && conn.process != nil { // Add process/profile metadata for connection. conn.ProcessContext = getProcessContext(pkt.Ctx(), conn.process) conn.ProfileRevisionCounter = conn.process.Profile().RevisionCnt() // Inherit internal status of profile. if localProfile := conn.process.Profile().LocalProfile(); localProfile != nil { conn.Internal = localProfile.Internal if err := conn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) { log.Tracer(pkt.Ctx()).Warningf("network: connection %s failed to check for enabled features: %s", conn, err) } } } // Find domain and DNS context of entity. if conn.Entity.Domain == "" && conn.process.Profile() != nil { profileScope := conn.process.Profile().LocalProfile().ID // check if we can find a domain for that IP ipinfo, err := resolver.GetIPInfo(profileScope, pkt.Info().RemoteIP().String()) if err != nil { // Try again with the global scope, in case DNS went through the system resolver. ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String()) } if runtime.GOOS == "windows" && err != nil { // On windows domains may come with delay. if module.instance.Resolver().IsDisabled() && conn.shouldWaitForDomain() { // Flush the dns listener buffer and try again. for i := range 4 { err = module.instance.DNSMonitor().Flush() if err != nil { // Error flushing, dont try again. break } // Try with profile scope ipinfo, err = resolver.GetIPInfo(profileScope, pkt.Info().RemoteIP().String()) if err == nil { log.Tracer(pkt.Ctx()).Debugf("network: found domain with scope (%s) from dnsmonitor after %d tries", profileScope, +1) break } // Try again with the global scope ipinfo, err = resolver.GetIPInfo(resolver.IPInfoProfileScopeGlobal, pkt.Info().RemoteIP().String()) if err == nil { log.Tracer(pkt.Ctx()).Debugf("network: found domain from dnsmonitor after %d tries", i+1) break } time.Sleep(5 * time.Millisecond) } } } if err == nil { lastResolvedDomain := ipinfo.MostRecentDomain() if lastResolvedDomain != nil { conn.Scope = lastResolvedDomain.Domain conn.Entity.Domain = lastResolvedDomain.Domain conn.Entity.CNAME = lastResolvedDomain.CNAMEs conn.DNSContext = lastResolvedDomain.DNSRequestContext conn.Resolver = lastResolvedDomain.Resolver removeOpenDNSRequest(conn.process.Pid, lastResolvedDomain.Domain) } } } // Check if destination IP is the captive portal's IP. if conn.Entity.Domain == "" { portal := netenv.GetCaptivePortal() if pkt.Info().RemoteIP().Equal(portal.IP) { conn.Scope = portal.Domain conn.Entity.Domain = portal.Domain } } // Check if we have all required data for a complete packet. switch { case pkt.InfoOnly(): // We need a full packet. case conn.process == nil: // We need a process. case conn.process.Profile() == nil: // We need a profile. case conn.Entity == nil: // We need an entity. default: // Data is complete! conn.dataComplete.Set() } conn.SaveWhenFinished() return nil } // GetConnection fetches a Connection from the database. func GetConnection(connID string) (*Connection, bool) { return conns.get(connID) } // GetAllConnections Gets all connection. func GetAllConnections() []*Connection { return conns.list() } // GetDNSConnection fetches a DNS Connection from the database. func GetDNSConnection(dnsConnID string) (*Connection, bool) { return dnsConns.get(dnsConnID) } // SetLocalIP sets the local IP address together with its network scope. The // connection is not locked for this. func (conn *Connection) SetLocalIP(ip net.IP) { conn.LocalIP = ip conn.LocalIPScope = netutils.GetIPScope(ip) } // UpdateFeatures checks which connection related features may and should be // used and sets the flags accordingly. // The caller must hold a lock on the connection. func (conn *Connection) UpdateFeatures() error { // Get user. user, err := access.GetUser() if err != nil && !errors.Is(err, access.ErrNotLoggedIn) { return err } // Caution: user may be nil! // Check if history may be used and if it is enabled for this application. conn.HistoryEnabled = false switch { case conn.Internal: // Do not record internal connections, as they are of low interest in the history. // TODO: Should we create a setting for this? case conn.Entity.IPScope.IsLocalhost(): // Do not record localhost-only connections, as they are very low interest in the history. // TODO: Should we create a setting for this? case user.MayUse(account.FeatureHistory): // Check if history may be used and is enabled. lProfile := conn.Process().Profile() if lProfile != nil { conn.HistoryEnabled = lProfile.EnableHistory() } } // Check if bandwidth visibility may be used. conn.BandwidthEnabled = user.MayUse(account.FeatureBWVis) return nil } // AcceptWithContext accepts the connection. func (conn *Connection) AcceptWithContext(reason, reasonOptionKey string, ctx interface{}) { if !conn.SetVerdict(VerdictAccept, reason, reasonOptionKey, ctx) { log.Warningf("filter: tried to accept %s, but current verdict is %s", conn, conn.Verdict) } } // Accept is like AcceptWithContext but only accepts a reason. func (conn *Connection) Accept(reason, reasonOptionKey string) { conn.AcceptWithContext(reason, reasonOptionKey, nil) } // BlockWithContext blocks the connection. func (conn *Connection) BlockWithContext(reason, reasonOptionKey string, ctx interface{}) { if !conn.SetVerdict(VerdictBlock, reason, reasonOptionKey, ctx) { log.Warningf("filter: tried to block %s, but current verdict is %s", conn, conn.Verdict) } } // Block is like BlockWithContext but does only accepts a reason. func (conn *Connection) Block(reason, reasonOptionKey string) { conn.BlockWithContext(reason, reasonOptionKey, nil) } // DropWithContext drops the connection. func (conn *Connection) DropWithContext(reason, reasonOptionKey string, ctx interface{}) { if !conn.SetVerdict(VerdictDrop, reason, reasonOptionKey, ctx) { log.Warningf("filter: tried to drop %s, but current verdict is %s", conn, conn.Verdict) } } // Drop is like DropWithContext but does only accepts a reason. func (conn *Connection) Drop(reason, reasonOptionKey string) { conn.DropWithContext(reason, reasonOptionKey, nil) } // DenyWithContext blocks or drops the link depending on the connection direction. func (conn *Connection) DenyWithContext(reason, reasonOptionKey string, ctx interface{}) { if conn.Inbound { conn.DropWithContext(reason, reasonOptionKey, ctx) } else { conn.BlockWithContext(reason, reasonOptionKey, ctx) } } // Deny is like DenyWithContext but only accepts a reason. func (conn *Connection) Deny(reason, reasonOptionKey string) { conn.DenyWithContext(reason, reasonOptionKey, nil) } // FailedWithContext marks the connection with VerdictFailed and stores the reason. func (conn *Connection) FailedWithContext(reason, reasonOptionKey string, ctx interface{}) { if !conn.SetVerdict(VerdictFailed, reason, reasonOptionKey, ctx) { log.Warningf("filter: tried to drop %s due to error but current verdict is %s", conn, conn.Verdict) } } // Failed is like FailedWithContext but only accepts a string. func (conn *Connection) Failed(reason, reasonOptionKey string) { conn.FailedWithContext(reason, reasonOptionKey, nil) } // SetVerdict sets a new verdict for the connection. func (conn *Connection) SetVerdict(newVerdict Verdict, reason, reasonOptionKey string, reasonCtx interface{}) (ok bool) { conn.SetVerdictDirectly(newVerdict) // Set reason and context. conn.Reason.Msg = reason conn.Reason.Context = reasonCtx // Reset option key. conn.Reason.OptionKey = "" conn.Reason.Profile = "" // Set option key if data is available. if reasonOptionKey != "" { lp := conn.Process().Profile() if lp != nil { conn.Reason.OptionKey = reasonOptionKey conn.Reason.Profile = lp.GetProfileSource(conn.Reason.OptionKey) } } return true // TODO: remove } // SetVerdictDirectly sets the verdict. func (conn *Connection) SetVerdictDirectly(newVerdict Verdict) { conn.Verdict = newVerdict } // VerdictVerb returns the verdict as a verb, while taking any special states // into account. func (conn *Connection) VerdictVerb() string { return conn.Verdict.Verb() } // DataIsComplete returns whether all information about the connection is // available and an actual packet has been seen. // As long as this flag is not set, the connection may not be evaluated for // a verdict and may not be sent to the UI. func (conn *Connection) DataIsComplete() bool { return conn.dataComplete.IsSet() } // Process returns the connection's process. func (conn *Connection) Process() *process.Process { return conn.process } // SaveWhenFinished marks the connection for saving it after the firewall handler. func (conn *Connection) SaveWhenFinished() { conn.saveWhenFinished = true } // Save saves the connection in the storage and propagates the change // through the database system. Save may lock dnsConnsLock or connsLock // in if Save() is called the first time. // Callers must make sure to lock the connection itself before calling // Save(). func (conn *Connection) Save() { conn.UpdateMeta() // nolint:exhaustive switch conn.Verdict { case VerdictAccept, VerdictRerouteToNameserver: conn.ConnectionEstablished = true case VerdictRerouteToTunnel: // this is already handled when the connection tunnel has been // established. default: } // Do not save/update until data is complete. if !conn.DataIsComplete() { return } if !conn.KeyIsSet() { if conn.Type == DNSRequest { conn.SetKey(makeKey(conn.process.Pid, dbScopeDNS, conn.ID)) dnsConns.add(conn) } else { conn.SetKey(makeKey(conn.process.Pid, dbScopeIP, conn.ID)) conns.add(conn) } } conn.addToMetrics() // notify database controller dbController.PushUpdate(conn) } // delete deletes a link from the storage and propagates the change. // delete may lock either the dnsConnsLock or connsLock. Callers // must still make sure to lock the connection itself. func (conn *Connection) delete() { // A connection without an ID has been created from // a DNS request rather than a packet. Choose the correct // connection store here. if conn.Type == IPConnection { conns.delete(conn) } else { dnsConns.delete(conn) } conn.Meta().Delete() // Notify database controller if data is complete and thus connection was previously exposed. if conn.DataIsComplete() { dbController.PushUpdate(conn) } } // GetActiveInspectors returns the list of active inspectors. func (conn *Connection) GetActiveInspectors() []bool { return conn.activeInspectors } // SetActiveInspectors sets the list of active inspectors. func (conn *Connection) SetActiveInspectors(newInspectors []bool) { conn.activeInspectors = newInspectors } // GetInspectorData returns the list of inspector data. func (conn *Connection) GetInspectorData() map[uint8]interface{} { return conn.inspectorData } // SetInspectorData set the list of inspector data. func (conn *Connection) SetInspectorData(newInspectorData map[uint8]interface{}) { conn.inspectorData = newInspectorData } // SetPrompt sets the given prompt on the connection. // If there already is a prompt set, the previous prompt notification is deleted. func (conn *Connection) SetPrompt(prompt *notifications.Notification) { conn.promptLock.Lock() defer conn.promptLock.Unlock() if conn.prompt != nil { conn.prompt.Delete() } conn.prompt = prompt } // RemovePrompt removes the prompt on the connection. func (conn *Connection) RemovePrompt() { conn.promptLock.Lock() defer conn.promptLock.Unlock() if conn.prompt != nil { conn.prompt.Delete() } } // String returns a string representation of conn. func (conn *Connection) String() string { switch { case conn.process == nil || conn.Entity == nil: return conn.ID case conn.Inbound: return fmt.Sprintf("%s <- %s", conn.process, conn.Entity.IP) case conn.Entity.Domain != "": return fmt.Sprintf("%s to %s (%s)", conn.process, conn.Entity.Domain, conn.Entity.IP) default: return fmt.Sprintf("%s -> %s", conn.process, conn.Entity.IP) } } func (conn *Connection) shouldWaitForDomain() bool { // Should wait for Global Unicast, outgoing and not ICMP connections switch { case conn.Entity.IPScope != netutils.Global: return false case conn.Inbound: return false case reference.IsICMP(conn.Entity.Protocol): return false } return true }