mirror of
https://github.com/safing/portmaster
synced 2025-09-02 02:29:12 +00:00
Revamp Connection.ID
Add Connection.Type and Connection.External Deprecate Connection.Scope
This commit is contained in:
parent
20383226f8
commit
fbf666ee68
6 changed files with 196 additions and 76 deletions
|
@ -100,18 +100,27 @@ func handleRequest(ctx context.Context, w dns.ResponseWriter, request *dns.Msg)
|
|||
// Authenticate request - only requests from the local host, but with any of its IPs, are allowed.
|
||||
local, err := netenv.IsMyIP(remoteAddr.IP)
|
||||
if err != nil {
|
||||
tracer.Warningf("nameserver: failed to check if request for %s%s is local: %s", q.FQDN, q.QType, err)
|
||||
tracer.Warningf("nameserver: failed to check if request for %s is local: %s", q.ID(), err)
|
||||
return nil // Do no reply, drop request immediately.
|
||||
}
|
||||
|
||||
// Create connection ID for dns request.
|
||||
connID := fmt.Sprintf(
|
||||
"%s-%d-#%d-%s",
|
||||
remoteAddr.IP,
|
||||
remoteAddr.Port,
|
||||
request.Id,
|
||||
q.ID(),
|
||||
)
|
||||
|
||||
// Get connection for this request. This identifies the process behind the request.
|
||||
var conn *network.Connection
|
||||
switch {
|
||||
case local:
|
||||
conn = network.NewConnectionFromDNSRequest(ctx, q.FQDN, nil, remoteAddr.IP, uint16(remoteAddr.Port))
|
||||
conn = network.NewConnectionFromDNSRequest(ctx, q.FQDN, nil, connID, remoteAddr.IP, uint16(remoteAddr.Port))
|
||||
|
||||
case networkServiceMode():
|
||||
conn, err = network.NewConnectionFromExternalDNSRequest(ctx, q.FQDN, nil, remoteAddr.IP)
|
||||
conn, err = network.NewConnectionFromExternalDNSRequest(ctx, q.FQDN, nil, connID, remoteAddr.IP)
|
||||
if err != nil {
|
||||
tracer.Warningf("nameserver: failed to get host/profile for request for %s%s: %s", q.FQDN, q.QType, err)
|
||||
return nil // Do no reply, drop request immediately.
|
||||
|
|
|
@ -11,6 +11,8 @@ import (
|
|||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/database/query"
|
||||
"github.com/safing/portbase/utils/debug"
|
||||
"github.com/safing/portmaster/network/state"
|
||||
"github.com/safing/portmaster/process"
|
||||
"github.com/safing/portmaster/status"
|
||||
)
|
||||
|
||||
|
@ -45,6 +47,18 @@ func registerAPIEndpoints() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "debug/network/state",
|
||||
Read: api.PermitUser,
|
||||
StructFunc: func(ar *api.Request) (i interface{}, err error) {
|
||||
return state.GetInfo(), nil
|
||||
},
|
||||
Name: "Get Network State Table Data",
|
||||
Description: "Returns the current network state tables from the OS.",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -156,28 +170,30 @@ func AddNetworkDebugData(di *debug.Info, profile, where string) {
|
|||
|
||||
func buildNetworkDebugInfoData(debugConns []*Connection) string {
|
||||
// Sort
|
||||
sort.Sort(connectionsByStarted(debugConns))
|
||||
sort.Sort(connectionsByGroup(debugConns))
|
||||
|
||||
// Format lines
|
||||
var buf strings.Builder
|
||||
currentBinaryPath := "__"
|
||||
currentPID := process.UndefinedProcessID
|
||||
for _, conn := range debugConns {
|
||||
conn.Lock()
|
||||
|
||||
// Add process infomration if it differs from previous connection.
|
||||
if currentBinaryPath != conn.ProcessContext.BinaryPath {
|
||||
if currentBinaryPath != "__" {
|
||||
if currentPID != conn.ProcessContext.PID {
|
||||
if currentPID != process.UndefinedProcessID {
|
||||
buf.WriteString("\n\n\n")
|
||||
}
|
||||
buf.WriteString("ProcessName: " + conn.ProcessContext.ProcessName)
|
||||
buf.WriteString("\nProfileName: " + conn.ProcessContext.ProfileName)
|
||||
buf.WriteString("\nBinaryPath: " + conn.ProcessContext.BinaryPath)
|
||||
buf.WriteString("ProfileName: " + conn.ProcessContext.ProfileName)
|
||||
buf.WriteString("\nProfile: " + conn.ProcessContext.Profile)
|
||||
buf.WriteString("\nSource: " + conn.ProcessContext.Source)
|
||||
buf.WriteString("\nProcessName: " + conn.ProcessContext.ProcessName)
|
||||
buf.WriteString("\nBinaryPath: " + conn.ProcessContext.BinaryPath)
|
||||
buf.WriteString("\nCmdLine: " + conn.ProcessContext.CmdLine)
|
||||
buf.WriteString("\nPID: " + strconv.Itoa(conn.ProcessContext.PID))
|
||||
buf.WriteString("\n")
|
||||
|
||||
// Set current path in order to not print the process information again.
|
||||
currentBinaryPath = conn.ProcessContext.BinaryPath
|
||||
// Set current PID in order to not print the process information again.
|
||||
currentPID = conn.ProcessContext.PID
|
||||
}
|
||||
|
||||
// Add connection.
|
||||
|
@ -192,7 +208,7 @@ func buildNetworkDebugInfoData(debugConns []*Connection) string {
|
|||
|
||||
func (conn *Connection) debugInfoLine() string {
|
||||
var connectionData string
|
||||
if conn.ID != "" {
|
||||
if conn.Type == DNSRequest { // conn.ID !=
|
||||
// Format IP/Port pair for connections.
|
||||
connectionData = fmt.Sprintf(
|
||||
"% 15s:%- 5s %s % 15s:%- 5s",
|
||||
|
@ -272,13 +288,28 @@ func (conn *Connection) fmtReasonProfileComponent() string {
|
|||
return conn.Reason.Profile
|
||||
}
|
||||
|
||||
type connectionsByStarted []*Connection
|
||||
type connectionsByGroup []*Connection
|
||||
|
||||
func (a connectionsByStarted) Len() int { return len(a) }
|
||||
func (a connectionsByStarted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a connectionsByStarted) Less(i, j int) bool {
|
||||
func (a connectionsByGroup) Len() int { return len(a) }
|
||||
func (a connectionsByGroup) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a connectionsByGroup) Less(i, j int) bool {
|
||||
// Sort by:
|
||||
|
||||
// 1. Profile ID
|
||||
if a[i].ProcessContext.Profile != a[j].ProcessContext.Profile {
|
||||
return a[i].ProcessContext.Profile < a[j].ProcessContext.Profile
|
||||
}
|
||||
|
||||
// 2. Process Binary
|
||||
if a[i].ProcessContext.BinaryPath != a[j].ProcessContext.BinaryPath {
|
||||
return a[i].ProcessContext.BinaryPath < a[j].ProcessContext.BinaryPath
|
||||
}
|
||||
|
||||
// 3. Process ID
|
||||
if a[i].ProcessContext.PID != a[j].ProcessContext.PID {
|
||||
return a[i].ProcessContext.PID < a[j].ProcessContext.PID
|
||||
}
|
||||
|
||||
// 4. Started
|
||||
return a[i].Started < a[j].Started
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ type ProcessContext struct {
|
|||
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
|
||||
// Profile is the ID of the main profile that
|
||||
|
@ -42,21 +44,37 @@ type ProcessContext struct {
|
|||
Source string
|
||||
}
|
||||
|
||||
type ConnectionType int8
|
||||
|
||||
const (
|
||||
Undefined ConnectionType = iota
|
||||
IPConnection
|
||||
DNSRequest
|
||||
// ProxyRequest
|
||||
)
|
||||
|
||||
// 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 may hold unique connection id. It is only set for non-DNS
|
||||
// request connections and is considered immutable after a
|
||||
// connection object has been created.
|
||||
// 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.
|
||||
|
@ -176,8 +194,9 @@ type Reason struct {
|
|||
func getProcessContext(ctx context.Context, proc *process.Process) ProcessContext {
|
||||
// Gather process information.
|
||||
pCtx := ProcessContext{
|
||||
BinaryPath: proc.Path,
|
||||
ProcessName: proc.Name,
|
||||
BinaryPath: proc.Path,
|
||||
CmdLine: proc.CmdLine,
|
||||
PID: proc.Pid,
|
||||
}
|
||||
|
||||
|
@ -196,7 +215,7 @@ func getProcessContext(ctx context.Context, proc *process.Process) ProcessContex
|
|||
}
|
||||
|
||||
// NewConnectionFromDNSRequest returns a new connection based on the given dns request.
|
||||
func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []string, localIP net.IP, localPort uint16) *Connection {
|
||||
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 {
|
||||
|
@ -223,6 +242,8 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
|
|||
|
||||
timestamp := time.Now().Unix()
|
||||
dnsConn := &Connection{
|
||||
ID: connID,
|
||||
Type: DNSRequest,
|
||||
Scope: fqdn,
|
||||
Entity: &intel.Entity{
|
||||
Domain: fqdn,
|
||||
|
@ -239,10 +260,15 @@ func NewConnectionFromDNSRequest(ctx context.Context, fqdn string, cnames []stri
|
|||
dnsConn.Internal = localProfile.Internal
|
||||
}
|
||||
|
||||
// Always mark dns queries from the system resolver as internal.
|
||||
if proc.IsSystemResolver() {
|
||||
dnsConn.Internal = true
|
||||
}
|
||||
|
||||
return dnsConn
|
||||
}
|
||||
|
||||
func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cnames []string, remoteIP net.IP) (*Connection, error) {
|
||||
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
|
||||
|
@ -250,6 +276,9 @@ func NewConnectionFromExternalDNSRequest(ctx context.Context, fqdn string, cname
|
|||
|
||||
timestamp := time.Now().Unix()
|
||||
dnsConn := &Connection{
|
||||
ID: connID,
|
||||
Type: DNSRequest,
|
||||
External: true,
|
||||
Scope: fqdn,
|
||||
Entity: &intel.Entity{
|
||||
Domain: fqdn,
|
||||
|
@ -280,6 +309,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
|||
|
||||
var scope string
|
||||
var entity *intel.Entity
|
||||
var resolverInfo *resolver.ResolverInfo
|
||||
|
||||
if inbound {
|
||||
|
||||
|
@ -316,7 +346,11 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
|||
entity.SetDstPort(entity.Port)
|
||||
|
||||
// check if we can find a domain for that IP
|
||||
ipinfo, err := resolver.GetIPInfo(proc.LocalProfileKey, pkt.Info().Dst.String())
|
||||
ipinfo, err := resolver.GetIPInfo(proc.Profile().LocalProfile().ID, pkt.Info().Dst.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().Dst.String())
|
||||
}
|
||||
if err == nil {
|
||||
lastResolvedDomain := ipinfo.MostRecentDomain()
|
||||
if lastResolvedDomain != nil {
|
||||
|
@ -358,6 +392,7 @@ func NewConnectionFromFirstPacket(pkt packet.Packet) *Connection {
|
|||
// Create new connection object.
|
||||
newConn := &Connection{
|
||||
ID: pkt.GetConnectionID(),
|
||||
Type: IPConnection,
|
||||
Scope: scope,
|
||||
IPVersion: pkt.Info().Version,
|
||||
Inbound: inbound,
|
||||
|
@ -493,14 +528,11 @@ func (conn *Connection) Save() {
|
|||
conn.UpdateMeta()
|
||||
|
||||
if !conn.KeyIsSet() {
|
||||
// A connection without an ID has been created from
|
||||
// a DNS request rather than a packet. Choose the correct
|
||||
// connection store here.
|
||||
if conn.ID == "" {
|
||||
conn.SetKey(fmt.Sprintf("network:tree/%d/%s", conn.process.Pid, conn.Scope))
|
||||
if conn.Type == DNSRequest {
|
||||
conn.SetKey(makeKey(conn.process.Pid, "dns", conn.ID))
|
||||
dnsConns.add(conn)
|
||||
} else {
|
||||
conn.SetKey(fmt.Sprintf("network:tree/%d/%s/%s", conn.process.Pid, conn.Scope, conn.ID))
|
||||
conn.SetKey(makeKey(conn.process.Pid, "ip", conn.ID))
|
||||
conns.add(conn)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -16,25 +15,18 @@ func newConnectionStore() *connectionStore {
|
|||
}
|
||||
}
|
||||
|
||||
func (cs *connectionStore) getID(conn *Connection) string {
|
||||
if conn.ID != "" {
|
||||
return conn.ID
|
||||
}
|
||||
return strconv.Itoa(conn.process.Pid) + "/" + conn.Scope
|
||||
}
|
||||
|
||||
func (cs *connectionStore) add(conn *Connection) {
|
||||
cs.rw.Lock()
|
||||
defer cs.rw.Unlock()
|
||||
|
||||
cs.items[cs.getID(conn)] = conn
|
||||
cs.items[conn.ID] = conn
|
||||
}
|
||||
|
||||
func (cs *connectionStore) delete(conn *Connection) {
|
||||
cs.rw.Lock()
|
||||
defer cs.rw.Unlock()
|
||||
|
||||
delete(cs.items, cs.getID(conn))
|
||||
delete(cs.items, conn.ID)
|
||||
}
|
||||
|
||||
func (cs *connectionStore) get(id string) (*Connection, bool) {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portmaster/network/state"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/database/iterator"
|
||||
"github.com/safing/portbase/database/query"
|
||||
|
@ -27,39 +26,88 @@ type StorageInterface struct {
|
|||
storage.InjectBase
|
||||
}
|
||||
|
||||
// Database prefixes:
|
||||
// Processes: network:tree/<PID>
|
||||
// DNS Requests: network:tree/<PID>/dns/<ID>
|
||||
// IP Connections: network:tree/<PID>/ip/<ID>
|
||||
|
||||
func makeKey(pid int, scope, id string) string {
|
||||
if scope == "" {
|
||||
return "network:tree/" + strconv.Itoa(pid)
|
||||
}
|
||||
return fmt.Sprintf("network:tree/%d/%s/%s", pid, scope, id)
|
||||
}
|
||||
|
||||
func parseDBKey(key string) (pid int, scope, id string, ok bool) {
|
||||
// Split into segments.
|
||||
segments := strings.Split(key, "/")
|
||||
// Check for valid prefix.
|
||||
if !strings.HasPrefix("tree", segments[0]) {
|
||||
return 0, "", "", false
|
||||
}
|
||||
|
||||
// Keys have 2 or 4 segments.
|
||||
switch len(segments) {
|
||||
case 4:
|
||||
id = segments[3]
|
||||
|
||||
fallthrough
|
||||
case 3:
|
||||
scope = segments[2]
|
||||
// Sanity check.
|
||||
switch scope {
|
||||
case "dns", "ip", "":
|
||||
// Parsed id matches possible values.
|
||||
// The empty string is for matching a trailing slash for in query prefix.
|
||||
// TODO: For queries, also prefixes of these values are valid.
|
||||
default:
|
||||
// Unknown scope.
|
||||
return 0, "", "", false
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case 2:
|
||||
var err error
|
||||
if segments[1] == "" {
|
||||
pid = process.UndefinedProcessID
|
||||
} else {
|
||||
pid, err = strconv.Atoi(segments[1])
|
||||
if err != nil {
|
||||
return 0, "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
return pid, scope, id, true
|
||||
case 1:
|
||||
// This is a valid query prefix, but not process ID was given.
|
||||
return process.UndefinedProcessID, "", "", true
|
||||
default:
|
||||
return 0, "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a database record.
|
||||
func (s *StorageInterface) Get(key string) (record.Record, error) {
|
||||
// Parse key and check if valid.
|
||||
pid, scope, id, ok := parseDBKey(strings.TrimPrefix(key, "network:"))
|
||||
if !ok || pid == process.UndefinedProcessID {
|
||||
return nil, storage.ErrNotFound
|
||||
}
|
||||
|
||||
splitted := strings.Split(key, "/")
|
||||
switch splitted[0] { //nolint:gocritic // TODO: implement full key space
|
||||
case "tree":
|
||||
switch len(splitted) {
|
||||
case 2:
|
||||
pid, err := strconv.Atoi(splitted[1])
|
||||
if err == nil {
|
||||
proc, ok := process.GetProcessFromStorage(pid)
|
||||
if ok {
|
||||
switch scope {
|
||||
case "dns":
|
||||
if r, ok := dnsConns.get(id); ok {
|
||||
return r, nil
|
||||
}
|
||||
case "ip":
|
||||
if r, ok := conns.get(id); ok {
|
||||
return r, nil
|
||||
}
|
||||
case "":
|
||||
if proc, ok := process.GetProcessFromStorage(pid); ok {
|
||||
return proc, nil
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
if r, ok := dnsConns.get(splitted[1] + "/" + splitted[2]); ok {
|
||||
return r, nil
|
||||
}
|
||||
case 4:
|
||||
if r, ok := conns.get(splitted[3]); ok {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
case "system":
|
||||
if len(splitted) >= 2 {
|
||||
switch splitted[1] {
|
||||
case "state":
|
||||
return state.GetInfo(), nil
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, storage.ErrNotFound
|
||||
}
|
||||
|
@ -74,9 +122,13 @@ func (s *StorageInterface) Query(q *query.Query, local, internal bool) (*iterato
|
|||
}
|
||||
|
||||
func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
|
||||
slashes := strings.Count(q.DatabaseKeyPrefix(), "/")
|
||||
pid, scope, _, ok := parseDBKey(q.DatabaseKeyPrefix())
|
||||
if !ok {
|
||||
it.Finish(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if slashes <= 1 {
|
||||
if pid == process.UndefinedProcessID {
|
||||
// processes
|
||||
for _, proc := range process.All() {
|
||||
proc.Lock()
|
||||
|
@ -87,7 +139,7 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
|
|||
}
|
||||
}
|
||||
|
||||
if slashes <= 2 {
|
||||
if scope == "" || scope == "dns" {
|
||||
// dns scopes only
|
||||
for _, dnsConn := range dnsConns.clone() {
|
||||
dnsConn.Lock()
|
||||
|
@ -98,7 +150,7 @@ func (s *StorageInterface) processQuery(q *query.Query, it *iterator.Iterator) {
|
|||
}
|
||||
}
|
||||
|
||||
if slashes <= 3 {
|
||||
if scope == "" || scope == "ip" {
|
||||
// connections
|
||||
for _, conn := range conns.clone() {
|
||||
conn.Lock()
|
||||
|
|
|
@ -14,6 +14,10 @@ const (
|
|||
// attributed to a PID for any reason.
|
||||
UnidentifiedProcessID = -1
|
||||
|
||||
// UndefinedProcessID is not used by any (virtual) process and signifies that
|
||||
// the PID is unset.
|
||||
UndefinedProcessID = -2
|
||||
|
||||
// NetworkHostProcessID is the PID used for requests served to the network.
|
||||
NetworkHostProcessID = -255
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue