Revamp connection handling flow to fix race condition and support info-only packets
This commit is contained in:
@@ -141,7 +141,11 @@ func authenticateAPIRequest(ctx context.Context, pktInfo *packet.Info) (retry bo
|
||||
authenticatedPath += string(filepath.Separator)
|
||||
|
||||
// Get process of request.
|
||||
proc, _, err := process.GetProcessByConnection(ctx, pktInfo)
|
||||
pid, _, _ := process.GetPidOfConnection(ctx, pktInfo)
|
||||
if pid < 0 {
|
||||
return false, fmt.Errorf(deniedMsgUnidentified, api.ErrAPIAccessDeniedMessage) //nolint:stylecheck // message for user
|
||||
}
|
||||
proc, err := process.GetOrFindProcess(ctx, pid)
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Debugf("filter: failed to get process of api request: %s", err)
|
||||
originalPid = process.UnidentifiedProcessID
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/tevino/abool"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
@@ -54,7 +53,7 @@ func init() {
|
||||
// TODO: Move interception module to own package (dir).
|
||||
interceptionModule = modules.Register("interception", interceptionPrep, interceptionStart, interceptionStop, "base", "updates", "network", "notifications", "profiles")
|
||||
|
||||
network.SetDefaultFirewallHandler(defaultHandler)
|
||||
network.SetDefaultFirewallHandler(verdictHandler)
|
||||
}
|
||||
|
||||
func interceptionPrep() error {
|
||||
@@ -120,6 +119,11 @@ func resetAllConnectionVerdicts() {
|
||||
// Re-evaluate all connections.
|
||||
var changedVerdicts int
|
||||
for _, conn := range network.GetAllConnections() {
|
||||
// Skip incomplete connections.
|
||||
if !conn.DataIsComplete() {
|
||||
continue
|
||||
}
|
||||
|
||||
func() {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
@@ -167,15 +171,10 @@ func resetAllConnectionVerdicts() {
|
||||
|
||||
func interceptionStart() error {
|
||||
getConfig()
|
||||
|
||||
if err := registerMetrics(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
startAPIAuth()
|
||||
|
||||
interceptionModule.StartWorker("stat logger", statLogger)
|
||||
interceptionModule.StartWorker("packet handler", packetHandler)
|
||||
interceptionModule.StartServiceWorker("stat logger", 0, statLogger)
|
||||
interceptionModule.StartServiceWorker("packet handler", 0, packetHandler)
|
||||
|
||||
return interception.Start()
|
||||
}
|
||||
@@ -196,92 +195,38 @@ func SetNameserverIPMatcher(fn func(ip net.IP) bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePacket(ctx context.Context, pkt packet.Packet) {
|
||||
// Record metrics.
|
||||
startTime := time.Now()
|
||||
defer packetHandlingHistogram.UpdateDuration(startTime)
|
||||
|
||||
if fastTrackedPermit(pkt) {
|
||||
func handlePacket(pkt packet.Packet) {
|
||||
// First, check for an existing connection.
|
||||
conn, ok := network.GetConnection(pkt.GetConnectionID())
|
||||
if ok {
|
||||
// Add packet to connection handler queue or apply verdict directly.
|
||||
conn.HandlePacket(pkt)
|
||||
return
|
||||
}
|
||||
|
||||
// Add context tracer and set context on packet.
|
||||
traceCtx, tracer := log.AddTracer(ctx)
|
||||
if tracer != nil {
|
||||
// The trace is submitted in `network.Connection.packetHandler()`.
|
||||
tracer.Tracef("filter: handling packet: %s", pkt)
|
||||
}
|
||||
pkt.SetCtx(traceCtx)
|
||||
// Else create new incomplete connection from the packet and start the new handler.
|
||||
conn = network.NewIncompleteConnection(pkt)
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
conn.SetFirewallHandler(fastTrackHandler)
|
||||
|
||||
// Get connection of packet.
|
||||
conn, err := getConnection(pkt)
|
||||
if err != nil {
|
||||
tracer.Errorf("filter: packet %s dropped: %s", pkt, err)
|
||||
_ = pkt.Drop()
|
||||
return
|
||||
}
|
||||
|
||||
// handle packet
|
||||
// Let the new connection handler worker handle the packet.
|
||||
conn.HandlePacket(pkt)
|
||||
}
|
||||
|
||||
var getConnectionSingleInflight singleflight.Group
|
||||
|
||||
func getConnection(pkt packet.Packet) (*network.Connection, error) {
|
||||
created := false
|
||||
|
||||
// Create or get connection in single inflight lock in order to prevent duplicates.
|
||||
newConn, err, shared := getConnectionSingleInflight.Do(pkt.GetConnectionID(), func() (interface{}, error) {
|
||||
// First, check for an existing connection.
|
||||
conn, ok := network.GetConnection(pkt.GetConnectionID())
|
||||
if ok {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Else create new one from the packet.
|
||||
conn = network.NewConnectionFromFirstPacket(pkt)
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
conn.SetFirewallHandler(initialHandler)
|
||||
created = true
|
||||
return conn, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get connection: %w", err)
|
||||
}
|
||||
if newConn == nil {
|
||||
return nil, errors.New("connection getter returned nil")
|
||||
}
|
||||
|
||||
// Transform and log result.
|
||||
conn := newConn.(*network.Connection) //nolint:forcetypeassert // Can only be a *network.Connection.
|
||||
sharedIndicator := ""
|
||||
if shared {
|
||||
sharedIndicator = " (shared)"
|
||||
}
|
||||
if created {
|
||||
log.Tracer(pkt.Ctx()).Tracef("filter: created new connection %s%s", conn.ID, sharedIndicator)
|
||||
} else {
|
||||
log.Tracer(pkt.Ctx()).Tracef("filter: assigned connection %s%s", conn.ID, sharedIndicator)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// fastTrackedPermit quickly permits certain network critical or internal connections.
|
||||
func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
||||
func fastTrackedPermit(pkt packet.Packet) (verdict network.Verdict, permanent bool) {
|
||||
meta := pkt.Info()
|
||||
|
||||
// Check if packed was already fast-tracked by the OS integration.
|
||||
if pkt.FastTrackedByIntegration() {
|
||||
log.Debugf("filter: fast-tracked by OS integration: %s", pkt)
|
||||
return true
|
||||
return network.VerdictAccept, true
|
||||
}
|
||||
|
||||
// Check if connection was already blocked.
|
||||
if meta.Dst.Equal(blockedIPv4) || meta.Dst.Equal(blockedIPv6) {
|
||||
_ = pkt.PermanentBlock()
|
||||
return true
|
||||
return network.VerdictBlock, true
|
||||
}
|
||||
|
||||
// Some programs do a network self-check where they connects to the same
|
||||
@@ -290,8 +235,8 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
||||
if meta.SrcPort == meta.DstPort &&
|
||||
meta.Src.Equal(meta.Dst) {
|
||||
log.Debugf("filter: fast-track network self-check: %s", pkt)
|
||||
_ = pkt.PermanentAccept()
|
||||
return true
|
||||
return network.VerdictAccept, true
|
||||
|
||||
}
|
||||
|
||||
switch meta.Protocol { //nolint:exhaustive // Checking for specific values only.
|
||||
@@ -300,8 +245,7 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
||||
err := pkt.LoadPacketData()
|
||||
if err != nil {
|
||||
log.Debugf("filter: failed to load ICMP packet data: %s", err)
|
||||
_ = pkt.PermanentAccept()
|
||||
return true
|
||||
return network.VerdictAccept, true
|
||||
}
|
||||
|
||||
// Submit to ICMP listener.
|
||||
@@ -311,8 +255,7 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
||||
// permanent accept, because then we won't see any future packets of that
|
||||
// connection and thus cannot continue to submit them.
|
||||
log.Debugf("filter: fast-track tracing ICMP/v6: %s", pkt)
|
||||
_ = pkt.Accept()
|
||||
return true
|
||||
return network.VerdictAccept, false
|
||||
}
|
||||
|
||||
// Handle echo request and replies regularly.
|
||||
@@ -323,20 +266,19 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
||||
switch icmpLayer.TypeCode.Type() {
|
||||
case layers.ICMPv4TypeEchoRequest,
|
||||
layers.ICMPv4TypeEchoReply:
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
case *layers.ICMPv6:
|
||||
switch icmpLayer.TypeCode.Type() {
|
||||
case layers.ICMPv6TypeEchoRequest,
|
||||
layers.ICMPv6TypeEchoReply:
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
}
|
||||
|
||||
// Permit all ICMP/v6 packets that are not echo requests or replies.
|
||||
log.Debugf("filter: fast-track accepting ICMP/v6: %s", pkt)
|
||||
_ = pkt.PermanentAccept()
|
||||
return true
|
||||
return network.VerdictAccept, true
|
||||
|
||||
case packet.UDP, packet.TCP:
|
||||
switch meta.DstPort {
|
||||
@@ -346,37 +288,36 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
||||
|
||||
// DHCP and DHCPv6 must be UDP.
|
||||
if meta.Protocol != packet.UDP {
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// DHCP is only valid in local network scopes.
|
||||
switch netutils.ClassifyIP(meta.Dst) { //nolint:exhaustive // Checking for specific values only.
|
||||
case netutils.HostLocal, netutils.LinkLocal, netutils.SiteLocal, netutils.LocalMulticast:
|
||||
default:
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Log and permit.
|
||||
log.Debugf("filter: fast-track accepting DHCP: %s", pkt)
|
||||
_ = pkt.PermanentAccept()
|
||||
return true
|
||||
return network.VerdictAccept, true
|
||||
|
||||
case apiPort:
|
||||
// Always allow direct access to the Portmaster API.
|
||||
|
||||
// Portmaster API is TCP only.
|
||||
if meta.Protocol != packet.TCP {
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Check if the api port is even set.
|
||||
if !apiPortSet {
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Must be destined for the API IP.
|
||||
if !meta.Dst.Equal(apiIP) {
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Only fast-track local requests.
|
||||
@@ -384,15 +325,14 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
case !isMe:
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Log and permit.
|
||||
log.Debugf("filter: fast-track accepting api connection: %s", pkt)
|
||||
_ = pkt.PermanentAccept()
|
||||
return true
|
||||
return network.VerdictAccept, true
|
||||
|
||||
case 53:
|
||||
// Always allow direct access to the Portmaster Nameserver.
|
||||
@@ -400,12 +340,12 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
||||
|
||||
// Check if a nameserver IP matcher is set.
|
||||
if !nameserverIPMatcherReady.IsSet() {
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Check if packet is destined for a nameserver IP.
|
||||
if !nameserverIPMatcher(meta.Dst) {
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Only fast-track local requests.
|
||||
@@ -413,32 +353,76 @@ func fastTrackedPermit(pkt packet.Packet) (handled bool) {
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
case !isMe:
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
// Log and permit.
|
||||
log.Debugf("filter: fast-track accepting local dns: %s", pkt)
|
||||
_ = pkt.PermanentAccept()
|
||||
return true
|
||||
return network.VerdictAccept, true
|
||||
}
|
||||
|
||||
case compat.SystemIntegrationCheckProtocol:
|
||||
if pkt.Info().Dst.Equal(compat.SystemIntegrationCheckDstIP) {
|
||||
compat.SubmitSystemIntegrationCheckPacket(pkt)
|
||||
_ = pkt.Drop()
|
||||
return true
|
||||
return network.VerdictDrop, false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return network.VerdictUndecided, false
|
||||
}
|
||||
|
||||
func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
func fastTrackHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
fastTrackedVerdict, permanent := fastTrackedPermit(pkt)
|
||||
if fastTrackedVerdict != network.VerdictUndecided {
|
||||
// Set verdict on connection.
|
||||
conn.Verdict.Active = fastTrackedVerdict
|
||||
conn.Verdict.Firewall = fastTrackedVerdict
|
||||
// Apply verdict to (real) packet.
|
||||
if !pkt.InfoOnly() {
|
||||
issueVerdict(conn, pkt, fastTrackedVerdict, permanent)
|
||||
}
|
||||
// Stop handler if permanent.
|
||||
if permanent {
|
||||
conn.SetVerdict(fastTrackedVerdict, "fast-tracked", "", nil)
|
||||
conn.Verdict.Worst = fastTrackedVerdict
|
||||
// Do not finalize verdict, as we are missing necessary data.
|
||||
conn.StopFirewallHandler()
|
||||
}
|
||||
|
||||
// Do not continue to next handler.
|
||||
return
|
||||
}
|
||||
|
||||
// If packet is not fast-tracked, continue with gathering more information.
|
||||
conn.UpdateFirewallHandler(gatherDataHandler)
|
||||
gatherDataHandler(conn, pkt)
|
||||
}
|
||||
|
||||
func gatherDataHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
// Get process info
|
||||
_ = conn.GatherConnectionInfo(pkt)
|
||||
// Errors are informational and are logged to the context.
|
||||
|
||||
// Run this handler again if data is not yet complete.
|
||||
if !conn.DataIsComplete() {
|
||||
return
|
||||
}
|
||||
|
||||
// Continue to filter handler, when connection data is complete.
|
||||
conn.UpdateFirewallHandler(filterHandler)
|
||||
filterHandler(conn, pkt)
|
||||
}
|
||||
|
||||
func filterHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
// Skip if data is not complete.
|
||||
if !conn.DataIsComplete() {
|
||||
return
|
||||
}
|
||||
|
||||
filterConnection := true
|
||||
|
||||
log.Tracer(pkt.Ctx()).Trace("filter: handing over to connection-based handler")
|
||||
// Check for special (internal) connection cases.
|
||||
switch {
|
||||
case !conn.Inbound && localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort):
|
||||
@@ -480,8 +464,8 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
switch {
|
||||
case conn.Inspecting:
|
||||
log.Tracer(pkt.Ctx()).Trace("filter: start inspecting")
|
||||
conn.SetFirewallHandler(inspectThenVerdict)
|
||||
inspectThenVerdict(conn, pkt)
|
||||
conn.SetFirewallHandler(inspectAndVerdictHandler)
|
||||
inspectAndVerdictHandler(conn, pkt)
|
||||
default:
|
||||
conn.StopFirewallHandler()
|
||||
issueVerdict(conn, pkt, 0, true)
|
||||
@@ -490,6 +474,11 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
|
||||
// FilterConnection runs all the filtering (and tunneling) procedures.
|
||||
func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet, checkFilter, checkTunnel bool) {
|
||||
// Skip if data is not complete.
|
||||
if !conn.DataIsComplete() {
|
||||
return
|
||||
}
|
||||
|
||||
if checkFilter {
|
||||
if filterEnabled() {
|
||||
log.Tracer(ctx).Trace("filter: starting decision process")
|
||||
@@ -537,12 +526,11 @@ func FilterConnection(ctx context.Context, conn *network.Connection, pkt packet.
|
||||
}
|
||||
}
|
||||
|
||||
func defaultHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
// TODO: `pkt` has an active trace log, which we currently don't submit.
|
||||
func verdictHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
issueVerdict(conn, pkt, 0, true)
|
||||
}
|
||||
|
||||
func inspectThenVerdict(conn *network.Connection, pkt packet.Packet) {
|
||||
func inspectAndVerdictHandler(conn *network.Connection, pkt packet.Packet) {
|
||||
pktVerdict, continueInspection := inspection.RunInspectors(conn, pkt)
|
||||
if continueInspection {
|
||||
issueVerdict(conn, pkt, pktVerdict, false)
|
||||
@@ -689,10 +677,11 @@ func packetHandler(ctx context.Context) error {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case pkt := <-interception.Packets:
|
||||
interceptionModule.StartWorker("initial packet handler", func(workerCtx context.Context) error {
|
||||
handlePacket(workerCtx, pkt)
|
||||
return nil
|
||||
})
|
||||
if pkt != nil {
|
||||
handlePacket(pkt)
|
||||
} else {
|
||||
return errors.New("received nil packet from interception")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,12 @@ type infoPacket struct {
|
||||
pmpacket.Base
|
||||
}
|
||||
|
||||
// InfoOnly returns whether the packet is informational only and does not
|
||||
// represent an actual packet.
|
||||
func (pkt *infoPacket) InfoOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// LoadPacketData does nothing on Linux, as data is always fully parsed.
|
||||
func (pkt *infoPacket) LoadPacketData() error {
|
||||
return fmt.Errorf("can't load data in info only packet")
|
||||
|
||||
@@ -5,11 +5,13 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cilium/ebpf/link"
|
||||
"github.com/cilium/ebpf/ringbuf"
|
||||
"github.com/cilium/ebpf/rlimit"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portmaster/network/packet"
|
||||
)
|
||||
@@ -17,6 +19,7 @@ import (
|
||||
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags "-O2 -g -Wall -Werror" -type Event bpf program/monitor.c
|
||||
var stopper chan struct{}
|
||||
|
||||
// StartEBPFWorker starts the ebpf worker.
|
||||
func StartEBPFWorker(ch chan packet.Packet) {
|
||||
stopper = make(chan struct{})
|
||||
go func() {
|
||||
@@ -32,7 +35,7 @@ func StartEBPFWorker(ch chan packet.Packet) {
|
||||
log.Errorf("ebpf: failed to load ebpf object: %s", err)
|
||||
return
|
||||
}
|
||||
defer objs.Close()
|
||||
defer objs.Close() //nolint:errcheck
|
||||
|
||||
// Create a link to the tcp_connect program.
|
||||
linkTCPConnect, err := link.AttachTracing(link.TracingOptions{
|
||||
@@ -42,7 +45,7 @@ func StartEBPFWorker(ch chan packet.Packet) {
|
||||
log.Errorf("ebpf: failed to attach to tcp_v4_connect: %s ", err)
|
||||
return
|
||||
}
|
||||
defer linkTCPConnect.Close()
|
||||
defer linkTCPConnect.Close() //nolint:errcheck
|
||||
|
||||
// Create a link to the udp_v4_connect program.
|
||||
linkUDPV4, err := link.AttachTracing(link.TracingOptions{
|
||||
@@ -52,7 +55,7 @@ func StartEBPFWorker(ch chan packet.Packet) {
|
||||
log.Errorf("ebpf: failed to attach to udp_v4_connect: %s ", err)
|
||||
return
|
||||
}
|
||||
defer linkUDPV4.Close()
|
||||
defer linkUDPV4.Close() //nolint:errcheck
|
||||
|
||||
// Create a link to the udp_v6_connect program.
|
||||
linkUDPV6, err := link.AttachTracing(link.TracingOptions{
|
||||
@@ -62,14 +65,14 @@ func StartEBPFWorker(ch chan packet.Packet) {
|
||||
log.Errorf("ebpf: failed to attach to udp_v6_connect: %s ", err)
|
||||
return
|
||||
}
|
||||
defer linkUDPV6.Close()
|
||||
defer linkUDPV6.Close() //nolint:errcheck
|
||||
|
||||
rd, err := ringbuf.NewReader(objs.bpfMaps.Events)
|
||||
if err != nil {
|
||||
log.Errorf("ebpf: failed to open ring buffer: %s", err)
|
||||
return
|
||||
}
|
||||
defer rd.Close()
|
||||
defer rd.Close() //nolint:errcheck
|
||||
|
||||
go func() {
|
||||
<-stopper
|
||||
@@ -107,7 +110,8 @@ func StartEBPFWorker(ch chan packet.Packet) {
|
||||
DstPort: event.Dport,
|
||||
Src: arrayToIP(event.Saddr, packet.IPVersion(event.IpVersion)),
|
||||
Dst: arrayToIP(event.Daddr, packet.IPVersion(event.IpVersion)),
|
||||
PID: event.Pid,
|
||||
PID: int(event.Pid),
|
||||
SeenAt: time.Now(),
|
||||
}
|
||||
if isEventValid(event) {
|
||||
log.Debugf("ebpf: PID: %d conn: %s:%d -> %s:%d %s %s", info.PID, info.LocalIP(), info.LocalPort(), info.RemoteIP(), info.RemotePort(), info.Version.String(), info.Protocol.String())
|
||||
@@ -123,6 +127,7 @@ func StartEBPFWorker(ch chan packet.Packet) {
|
||||
}()
|
||||
}
|
||||
|
||||
// StopEBPFWorker stops the ebpf worker.
|
||||
func StopEBPFWorker() {
|
||||
close(stopper)
|
||||
}
|
||||
@@ -148,11 +153,12 @@ func isEventValid(event bpfEvent) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// arrayToIP converts IP number array to net.IP
|
||||
// arrayToIP converts IP number array to net.IP.
|
||||
func arrayToIP(ipNum [4]uint32, ipVersion packet.IPVersion) net.IP {
|
||||
if ipVersion == packet.IPv4 {
|
||||
// FIXME: maybe convertIPv4 from windowskext package
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 4)
|
||||
} else {
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 16)
|
||||
}
|
||||
// FIXME: maybe use convertIPv6 from windowskext package
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 16)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
pmpacket "github.com/safing/portmaster/network/packet"
|
||||
"github.com/safing/portmaster/process"
|
||||
)
|
||||
|
||||
// Queue wraps a nfqueue.
|
||||
@@ -175,10 +176,11 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
|
||||
pkt := &packet{
|
||||
pktID: *attrs.PacketID,
|
||||
queue: q,
|
||||
received: time.Now(),
|
||||
verdictSet: make(chan struct{}),
|
||||
verdictPending: abool.New(),
|
||||
}
|
||||
pkt.Info().PID = process.UndefinedProcessID
|
||||
pkt.Info().SeenAt = time.Now()
|
||||
|
||||
if attrs.Payload == nil {
|
||||
// There is not payload.
|
||||
@@ -194,11 +196,11 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
|
||||
|
||||
select {
|
||||
case q.packets <- pkt:
|
||||
log.Tracef("nfqueue: queued packet %s (%s -> %s) after %s", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, time.Since(pkt.received))
|
||||
log.Tracef("nfqueue: queued packet %s (%s -> %s) after %s", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, time.Since(pkt.Info().SeenAt))
|
||||
case <-ctx.Done():
|
||||
return 0
|
||||
case <-time.After(time.Second):
|
||||
log.Warningf("nfqueue: failed to queue packet (%s since it was handed over by the kernel)", time.Since(pkt.received))
|
||||
log.Warningf("nfqueue: failed to queue packet (%s since it was handed over by the kernel)", time.Since(pkt.Info().SeenAt))
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -206,7 +208,7 @@ func (q *Queue) packetHandler(ctx context.Context) func(nfqueue.Attribute) int {
|
||||
case <-pkt.verdictSet:
|
||||
|
||||
case <-time.After(20 * time.Second):
|
||||
log.Warningf("nfqueue: no verdict set for packet %s (%s -> %s) after %s, dropping", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, time.Since(pkt.received))
|
||||
log.Warningf("nfqueue: no verdict set for packet %s (%s -> %s) after %s, dropping", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, time.Since(pkt.Info().SeenAt))
|
||||
if err := pkt.Drop(); err != nil {
|
||||
log.Warningf("nfqueue: failed to apply default-drop to unveridcted packet %s (%s -> %s)", pkt.ID(), pkt.Info().Src, pkt.Info().Dst)
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ func markToString(mark int) string {
|
||||
type packet struct {
|
||||
pmpacket.Base
|
||||
pktID uint32
|
||||
received time.Time
|
||||
queue *Queue
|
||||
verdictSet chan struct{}
|
||||
verdictPending *abool.AtomicBool
|
||||
@@ -118,7 +117,7 @@ func (pkt *packet) setMark(mark int) error {
|
||||
}
|
||||
break
|
||||
}
|
||||
log.Tracer(pkt.Ctx()).Tracef("nfqueue: marking packet %s (%s -> %s) on queue %d with %s after %s", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, pkt.queue.id, markToString(mark), time.Since(pkt.received))
|
||||
log.Tracer(pkt.Ctx()).Tracef("nfqueue: marking packet %s (%s -> %s) on queue %d with %s after %s", pkt.ID(), pkt.Info().Src, pkt.Info().Dst, pkt.queue.id, markToString(mark), time.Since(pkt.Info().SeenAt))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/safing/portmaster/process"
|
||||
|
||||
"github.com/tevino/abool"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
@@ -103,21 +106,28 @@ func Handler(packets chan packet.Packet) {
|
||||
verdictRequest: packetInfo,
|
||||
verdictSet: abool.NewBool(false),
|
||||
}
|
||||
|
||||
info := new.Info()
|
||||
info.Inbound = packetInfo.direction > 0
|
||||
info.InTunnel = false
|
||||
info.Protocol = packet.IPProtocol(packetInfo.protocol)
|
||||
info.PID = packetInfo.pid
|
||||
info.PID = int(packetInfo.pid)
|
||||
info.SeenAt = time.Now()
|
||||
|
||||
// IP version
|
||||
// Check PID
|
||||
if info.PID == 0 {
|
||||
// Windows does not have zero PIDs.
|
||||
// Set to UndefinedProcessID.
|
||||
info.PID = process.UndefinedProcessID
|
||||
}
|
||||
|
||||
// Set IP version
|
||||
if packetInfo.ipV6 == 1 {
|
||||
info.Version = packet.IPv6
|
||||
} else {
|
||||
info.Version = packet.IPv4
|
||||
}
|
||||
|
||||
// IPs
|
||||
// Set IPs
|
||||
if info.Version == packet.IPv4 {
|
||||
// IPv4
|
||||
if info.Inbound {
|
||||
@@ -142,7 +152,7 @@ func Handler(packets chan packet.Packet) {
|
||||
}
|
||||
}
|
||||
|
||||
// Ports
|
||||
// Set Ports
|
||||
if info.Inbound {
|
||||
// Inbound
|
||||
info.SrcPort = packetInfo.remotePort
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windowskext
|
||||
@@ -23,6 +24,12 @@ type Packet struct {
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// InfoOnly returns whether the packet is informational only and does not
|
||||
// represent an actual packet.
|
||||
func (pkt *Packet) InfoOnly() bool {
|
||||
return pkt.verdictRequest.flags&VerdictRequestFlagSocketAuth > 0
|
||||
}
|
||||
|
||||
// FastTrackedByIntegration returns whether the packet has been fast-track
|
||||
// accepted by the OS integration.
|
||||
func (pkt *Packet) FastTrackedByIntegration() bool {
|
||||
|
||||
@@ -25,19 +25,6 @@ import (
|
||||
"github.com/safing/portmaster/profile/endpoints"
|
||||
)
|
||||
|
||||
// Call order:
|
||||
//
|
||||
// DNS Query:
|
||||
// 1. DecideOnConnection
|
||||
// is called when a DNS query is made, may set verdict to Undeterminable to permit a DNS reply.
|
||||
// is called with a nil packet.
|
||||
// 2. DecideOnResolvedDNS
|
||||
// is called to (possibly) filter out A/AAAA records that the filter would deny later.
|
||||
//
|
||||
// Network Connection:
|
||||
// 3. DecideOnConnection
|
||||
// is called with the first packet of a network connection.
|
||||
|
||||
const noReasonOptionKey = ""
|
||||
|
||||
type deciderFn func(context.Context, *network.Connection, *profile.LayeredProfile, packet.Packet) bool
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/config"
|
||||
"github.com/safing/portbase/metrics"
|
||||
)
|
||||
|
||||
var packetHandlingHistogram *metrics.Histogram
|
||||
|
||||
func registerMetrics() (err error) {
|
||||
packetHandlingHistogram, err = metrics.NewHistogram(
|
||||
"firewall/handling/duration/seconds",
|
||||
nil,
|
||||
&metrics.Options{
|
||||
Permission: api.PermitUser,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user