mirror of
https://github.com/safing/portmaster
synced 2025-04-19 10:29:11 +00:00
* Move portbase into monorepo * Add new simple module mgr * [WIP] Switch to new simple module mgr * Add StateMgr and more worker variants * [WIP] Switch more modules * [WIP] Switch more modules * [WIP] swtich more modules * [WIP] switch all SPN modules * [WIP] switch all service modules * [WIP] Convert all workers to the new module system * [WIP] add new task system to module manager * [WIP] Add second take for scheduling workers * [WIP] Add FIXME for bugs in new scheduler * [WIP] Add minor improvements to scheduler * [WIP] Add new worker scheduler * [WIP] Fix more bug related to new module system * [WIP] Fix start handing of the new module system * [WIP] Improve startup process * [WIP] Fix minor issues * [WIP] Fix missing subsystem in settings * [WIP] Initialize managers in constructor * [WIP] Move module event initialization to constrictors * [WIP] Fix setting for enabling and disabling the SPN module * [WIP] Move API registeration into module construction * [WIP] Update states mgr for all modules * [WIP] Add CmdLine operation support * Add state helper methods to module group and instance * Add notification and module status handling to status package * Fix starting issues * Remove pilot widget and update security lock to new status data * Remove debug logs * Improve http server shutdown * Add workaround for cleanly shutting down firewall+netquery * Improve logging * Add syncing states with notifications for new module system * Improve starting, stopping, shutdown; resolve FIXMEs/TODOs * [WIP] Fix most unit tests * Review new module system and fix minor issues * Push shutdown and restart events again via API * Set sleep mode via interface * Update example/template module * [WIP] Fix spn/cabin unit test * Remove deprecated UI elements * Make log output more similar for the logging transition phase * Switch spn hub and observer cmds to new module system * Fix log sources * Make worker mgr less error prone * Fix tests and minor issues * Fix observation hub * Improve shutdown and restart handling * Split up big connection.go source file * Move varint and dsd packages to structures repo * Improve expansion test * Fix linter warnings * Fix interception module on windows * Fix linter errors --------- Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
337 lines
8.3 KiB
Go
337 lines
8.3 KiB
Go
package network
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/safing/portmaster/base/api"
|
|
"github.com/safing/portmaster/base/config"
|
|
"github.com/safing/portmaster/base/database/query"
|
|
"github.com/safing/portmaster/base/utils/debug"
|
|
"github.com/safing/portmaster/service/network/state"
|
|
"github.com/safing/portmaster/service/process"
|
|
"github.com/safing/portmaster/service/resolver"
|
|
"github.com/safing/portmaster/service/status"
|
|
"github.com/safing/portmaster/service/updates"
|
|
)
|
|
|
|
func registerAPIEndpoints() error {
|
|
if err := api.RegisterEndpoint(api.Endpoint{
|
|
Path: "debug/network",
|
|
Read: api.PermitUser,
|
|
DataFunc: debugInfo,
|
|
Name: "Get Network Debug Information",
|
|
Description: "Returns network debugging information, similar to debug/core, but with connection data.",
|
|
Parameters: []api.Parameter{
|
|
{
|
|
Method: http.MethodGet,
|
|
Field: "style",
|
|
Value: "github",
|
|
Description: "Specify the formatting style. The default is simple markdown formatting.",
|
|
},
|
|
{
|
|
Method: http.MethodGet,
|
|
Field: "profile",
|
|
Value: "<Source>/<ID>",
|
|
Description: "Specify a profile source and ID for which network connection should be reported.",
|
|
},
|
|
{
|
|
Method: http.MethodGet,
|
|
Field: "where",
|
|
Value: "<query>",
|
|
Description: "Specify a query to limit the connections included in the report. The default is to include all connections.",
|
|
},
|
|
},
|
|
}); err != nil {
|
|
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
|
|
}
|
|
|
|
// debugInfo returns the debugging information for support requests.
|
|
func debugInfo(ar *api.Request) (data []byte, err error) {
|
|
// Create debug information helper.
|
|
di := new(debug.Info)
|
|
di.Style = ar.Request.URL.Query().Get("style")
|
|
|
|
// Add debug information.
|
|
|
|
// Very basic information at the start.
|
|
di.AddVersionInfo()
|
|
di.AddPlatformInfo(ar.Context())
|
|
|
|
// Unexpected logs.
|
|
di.AddLastUnexpectedLogs()
|
|
|
|
// Network Connections.
|
|
AddNetworkDebugData(
|
|
di,
|
|
ar.Request.URL.Query().Get("profile"),
|
|
ar.Request.URL.Query().Get("where"),
|
|
)
|
|
|
|
// Status Information from various modules.
|
|
status.AddToDebugInfo(di)
|
|
// captain.AddToDebugInfo(di) // TODO: Cannot use due to import loop.
|
|
resolver.AddToDebugInfo(di)
|
|
config.AddToDebugInfo(di)
|
|
|
|
// Detailed information.
|
|
updates.AddToDebugInfo(di)
|
|
// compat.AddToDebugInfo(di) // TODO: Cannot use due to interception import requirement which we don't want for SPN Hubs.
|
|
di.AddGoroutineStack()
|
|
|
|
// Return data.
|
|
return di.Bytes(), nil
|
|
}
|
|
|
|
// AddNetworkDebugData adds the network debug data of the given profile to the debug data.
|
|
func AddNetworkDebugData(di *debug.Info, profile, where string) {
|
|
// Prepend where prefix to query if necessary.
|
|
if where != "" && !strings.HasPrefix(where, "where ") {
|
|
where = "where " + where
|
|
}
|
|
|
|
// Build query.
|
|
q, err := query.ParseQuery("query network: " + where)
|
|
if err != nil {
|
|
di.AddSection(
|
|
"Network: Debug Failed",
|
|
debug.NoFlags,
|
|
fmt.Sprintf("Failed to build query: %s", err),
|
|
)
|
|
return
|
|
}
|
|
|
|
// Get iterator.
|
|
it, err := dbController.Query(q, true, true)
|
|
if err != nil {
|
|
di.AddSection(
|
|
"Network: Debug Failed",
|
|
debug.NoFlags,
|
|
fmt.Sprintf("Failed to run query: %s", err),
|
|
)
|
|
return
|
|
}
|
|
|
|
// Collect matching connections.
|
|
var ( //nolint:prealloc // We don't know the size.
|
|
debugConns []*Connection
|
|
accepted int
|
|
total int
|
|
)
|
|
|
|
for maybeConn := range it.Next {
|
|
// Switch to correct type.
|
|
conn, ok := maybeConn.(*Connection)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Check if the profile matches
|
|
if profile != "" {
|
|
found := false
|
|
|
|
// Get layer IDs and search for a match.
|
|
layerIDs := conn.Process().Profile().LayerIDs
|
|
for _, layerID := range layerIDs {
|
|
if profile == layerID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// Skip if the profile does not match.
|
|
if !found {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Count.
|
|
total++
|
|
switch conn.Verdict { //nolint:exhaustive
|
|
case VerdictAccept,
|
|
VerdictRerouteToNameserver,
|
|
VerdictRerouteToTunnel:
|
|
|
|
accepted++
|
|
}
|
|
|
|
// Add to list.
|
|
debugConns = append(debugConns, conn)
|
|
}
|
|
|
|
// Add it all.
|
|
di.AddSection(
|
|
fmt.Sprintf(
|
|
"Network: %d/%d Connections",
|
|
accepted,
|
|
total,
|
|
),
|
|
debug.UseCodeSection|debug.AddContentLineBreaks,
|
|
buildNetworkDebugInfoData(debugConns),
|
|
)
|
|
}
|
|
|
|
func buildNetworkDebugInfoData(debugConns []*Connection) string {
|
|
// Sort
|
|
sort.Sort(connectionsByGroup(debugConns))
|
|
|
|
// Format lines
|
|
var buf strings.Builder
|
|
currentPID := process.UndefinedProcessID
|
|
for _, conn := range debugConns {
|
|
conn.Lock()
|
|
|
|
// Add process infomration if it differs from previous connection.
|
|
if currentPID != conn.ProcessContext.PID {
|
|
if currentPID != process.UndefinedProcessID {
|
|
buf.WriteString("\n\n\n")
|
|
}
|
|
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 PID in order to not print the process information again.
|
|
currentPID = conn.ProcessContext.PID
|
|
}
|
|
|
|
// Add connection.
|
|
buf.WriteString("\n")
|
|
buf.WriteString(conn.debugInfoLine())
|
|
|
|
conn.Unlock()
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
func (conn *Connection) debugInfoLine() string {
|
|
var connectionData string
|
|
if conn.Type == IPConnection {
|
|
// Format IP/Port pair for connections.
|
|
connectionData = fmt.Sprintf(
|
|
"% 15s:%- 5s %s % 15s:%- 5s",
|
|
conn.LocalIP,
|
|
strconv.Itoa(int(conn.LocalPort)),
|
|
conn.fmtProtocolAndDirectionComponent(conn.IPProtocol.String()),
|
|
conn.Entity.IP,
|
|
strconv.Itoa(int(conn.Entity.Port)),
|
|
)
|
|
} else {
|
|
// Leave empty for DNS Requests.
|
|
connectionData = " "
|
|
}
|
|
|
|
return fmt.Sprintf(
|
|
"% 14s %s%- 25s %s-%s P#%d [%s] %s - by %s @ %s",
|
|
conn.VerdictVerb(),
|
|
connectionData,
|
|
conn.fmtDomainComponent(),
|
|
time.Unix(conn.Started, 0).Format("15:04:05"),
|
|
conn.fmtEndTimeComponent(),
|
|
conn.ProcessContext.PID,
|
|
conn.fmtFlagsComponent(),
|
|
conn.Reason.Msg,
|
|
conn.Reason.OptionKey,
|
|
conn.fmtReasonProfileComponent(),
|
|
)
|
|
}
|
|
|
|
func (conn *Connection) fmtDomainComponent() string {
|
|
if conn.Entity.Domain != "" {
|
|
return " to " + conn.Entity.Domain
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (conn *Connection) fmtProtocolAndDirectionComponent(protocol string) string {
|
|
if conn.Inbound {
|
|
return "<" + protocol
|
|
}
|
|
return protocol + ">"
|
|
}
|
|
|
|
func (conn *Connection) fmtFlagsComponent() string {
|
|
var f string
|
|
|
|
if conn.Internal {
|
|
f += "I"
|
|
}
|
|
if conn.Encrypted {
|
|
f += "E"
|
|
}
|
|
if conn.Tunneled {
|
|
f += "T"
|
|
}
|
|
if len(conn.activeInspectors) > 0 {
|
|
f += "A"
|
|
}
|
|
if conn.addedToMetrics {
|
|
f += "M"
|
|
}
|
|
|
|
return f
|
|
}
|
|
|
|
func (conn *Connection) fmtEndTimeComponent() string {
|
|
if conn.Ended == 0 {
|
|
return " " // Use same width as a timestamp.
|
|
}
|
|
return time.Unix(conn.Ended, 0).Format("15:04:05")
|
|
}
|
|
|
|
func (conn *Connection) fmtReasonProfileComponent() string {
|
|
if conn.Reason.Profile == "" {
|
|
return "global"
|
|
}
|
|
return conn.Reason.Profile
|
|
}
|
|
|
|
type connectionsByGroup []*Connection
|
|
|
|
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
|
|
}
|