mirror of
https://github.com/safing/portmaster
synced 2025-04-11 14:39:11 +00:00
830 lines
24 KiB
Go
830 lines
24 KiB
Go
package firewall
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/google/gopacket/layers"
|
|
"github.com/tevino/abool"
|
|
|
|
"github.com/safing/portbase/log"
|
|
"github.com/safing/portmaster/service/compat"
|
|
_ "github.com/safing/portmaster/service/core/base"
|
|
"github.com/safing/portmaster/service/firewall/inspection"
|
|
"github.com/safing/portmaster/service/firewall/interception"
|
|
"github.com/safing/portmaster/service/netenv"
|
|
"github.com/safing/portmaster/service/netquery"
|
|
"github.com/safing/portmaster/service/network"
|
|
"github.com/safing/portmaster/service/network/netutils"
|
|
"github.com/safing/portmaster/service/network/packet"
|
|
"github.com/safing/portmaster/service/process"
|
|
"github.com/safing/portmaster/spn/access"
|
|
)
|
|
|
|
var (
|
|
nameserverIPMatcher func(ip net.IP) bool
|
|
nameserverIPMatcherSet = abool.New()
|
|
nameserverIPMatcherReady = abool.New()
|
|
|
|
packetsAccepted = new(uint64)
|
|
packetsBlocked = new(uint64)
|
|
packetsDropped = new(uint64)
|
|
packetsFailed = new(uint64)
|
|
|
|
blockedIPv4 = net.IPv4(0, 0, 0, 17)
|
|
blockedIPv6 = net.ParseIP("::17")
|
|
|
|
ownPID = os.Getpid()
|
|
)
|
|
|
|
func resetSingleConnectionVerdict(connID string) {
|
|
// Create tracing context.
|
|
ctx, tracer := log.AddTracer(context.Background())
|
|
defer tracer.Submit()
|
|
|
|
conn, ok := network.GetConnection(connID)
|
|
if !ok {
|
|
conn, ok = network.GetDNSConnection(connID)
|
|
if !ok {
|
|
tracer.Debugf("filter: could not find re-attributed connection %s for re-evaluation", connID)
|
|
return
|
|
}
|
|
}
|
|
|
|
resetConnectionVerdict(ctx, conn)
|
|
}
|
|
|
|
func resetProfileConnectionVerdict(profileSource, profileID string) {
|
|
// Create tracing context.
|
|
ctx, tracer := log.AddTracer(context.Background())
|
|
defer tracer.Submit()
|
|
|
|
// Resetting will force all the connection to be evaluated by the firewall again
|
|
// this will set new verdicts if configuration was update or spn has been disabled or enabled.
|
|
tracer.Infof("filter: re-evaluating connections of %s/%s", profileSource, profileID)
|
|
|
|
// Re-evaluate all connections.
|
|
var changedVerdicts int
|
|
for _, conn := range network.GetAllConnections() {
|
|
// Check if connection is complete and attributed to the deleted profile.
|
|
if conn.DataIsComplete() &&
|
|
conn.ProcessContext.Profile == profileID &&
|
|
conn.ProcessContext.Source == profileSource {
|
|
if resetConnectionVerdict(ctx, conn) {
|
|
changedVerdicts++
|
|
}
|
|
}
|
|
}
|
|
tracer.Infof("filter: changed verdict on %d connections", changedVerdicts)
|
|
}
|
|
|
|
func resetAllConnectionVerdicts() {
|
|
// Create tracing context.
|
|
ctx, tracer := log.AddTracer(context.Background())
|
|
defer tracer.Submit()
|
|
|
|
// Resetting will force all the connection to be evaluated by the firewall again
|
|
// this will set new verdicts if configuration was update or spn has been disabled or enabled.
|
|
tracer.Info("filter: re-evaluating all connections")
|
|
|
|
// Re-evaluate all connections.
|
|
var changedVerdicts int
|
|
for _, conn := range network.GetAllConnections() {
|
|
// Skip incomplete connections.
|
|
if !conn.DataIsComplete() {
|
|
continue
|
|
}
|
|
|
|
if resetConnectionVerdict(ctx, conn) {
|
|
changedVerdicts++
|
|
}
|
|
}
|
|
tracer.Infof("filter: changed verdict on %d connections", changedVerdicts)
|
|
}
|
|
|
|
func resetConnectionVerdict(ctx context.Context, conn *network.Connection) (verdictChanged bool) {
|
|
tracer := log.Tracer(ctx)
|
|
|
|
// Remove any active prompt as the settings are being re-evaluated.
|
|
conn.RemovePrompt()
|
|
|
|
conn.Lock()
|
|
defer conn.Unlock()
|
|
|
|
// Do not re-evaluate connection that have already ended.
|
|
if conn.Ended > 0 {
|
|
return false
|
|
}
|
|
|
|
// Update feature flags.
|
|
if err := conn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
|
tracer.Warningf("filter: failed to update connection feature flags: %s", err)
|
|
}
|
|
|
|
// Skip internal connections:
|
|
// - Pre-authenticated connections from Portmaster
|
|
// - Redirected DNS requests
|
|
// - SPN Uplink to Home Hub
|
|
if conn.Internal {
|
|
// tracer.Tracef("filter: skipping internal connection %s", conn)
|
|
return false
|
|
}
|
|
|
|
tracer.Debugf("filter: re-evaluating verdict of %s", conn)
|
|
previousVerdict := conn.Verdict
|
|
|
|
// Apply privacy filter and check tunneling.
|
|
FilterConnection(ctx, conn, nil, true, true)
|
|
|
|
// Stop existing SPN tunnel if not needed anymore.
|
|
if conn.Verdict != network.VerdictRerouteToTunnel && conn.TunnelContext != nil {
|
|
err := conn.TunnelContext.StopTunnel()
|
|
if err != nil {
|
|
tracer.Debugf("filter: failed to stopped unneeded tunnel: %s", err)
|
|
}
|
|
}
|
|
|
|
// Save if verdict changed.
|
|
if conn.Verdict != previousVerdict {
|
|
err := interception.UpdateVerdictOfConnection(conn)
|
|
if err != nil {
|
|
log.Debugf("filter: failed to update connection verdict: %s", err)
|
|
}
|
|
conn.Save()
|
|
tracer.Infof("filter: verdict of connection %s changed from %s to %s", conn, previousVerdict.Verb(), conn.VerdictVerb())
|
|
|
|
// Update verdict in OS integration, if an IP connection.
|
|
if conn.Type == network.IPConnection {
|
|
err := interception.UpdateVerdictOfConnection(conn)
|
|
if err != nil {
|
|
log.Debugf("filter: failed to update connection verdict: %s", err)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
tracer.Tracef("filter: verdict to connection %s unchanged at %s", conn, conn.VerdictVerb())
|
|
return false
|
|
}
|
|
|
|
// SetNameserverIPMatcher sets a function that is used to match the internal
|
|
// nameserver IP(s). Can only bet set once.
|
|
func SetNameserverIPMatcher(fn func(ip net.IP) bool) error {
|
|
if !nameserverIPMatcherSet.SetToIf(false, true) {
|
|
return errors.New("nameserver IP matcher already set")
|
|
}
|
|
|
|
nameserverIPMatcher = fn
|
|
nameserverIPMatcherReady.Set()
|
|
return nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// 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)
|
|
|
|
// Let the new connection handler worker handle the packet.
|
|
conn.HandlePacket(pkt)
|
|
}
|
|
|
|
// fastTrackedPermit quickly permits certain network critical or internal connections.
|
|
func fastTrackedPermit(conn *network.Connection, 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.Tracer(pkt.Ctx()).Debugf("filter: fast-tracked by OS integration: %s", pkt)
|
|
return network.VerdictAccept, true
|
|
}
|
|
|
|
// Check if connection was already blocked.
|
|
if meta.Dst.Equal(blockedIPv4) || meta.Dst.Equal(blockedIPv6) {
|
|
return network.VerdictBlock, true
|
|
}
|
|
|
|
// Some programs do a network self-check where they connects to the same
|
|
// IP/Port to test network capabilities.
|
|
// Eg. dig: https://gitlab.isc.org/isc-projects/bind9/-/issues/1140
|
|
if meta.SrcPort == meta.DstPort &&
|
|
meta.Src.Equal(meta.Dst) {
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track network self-check: %s", pkt)
|
|
return network.VerdictAccept, true
|
|
}
|
|
|
|
switch meta.Protocol { //nolint:exhaustive // Checking for specific values only.
|
|
case packet.ICMP, packet.ICMPv6:
|
|
// Load packet data.
|
|
err := pkt.LoadPacketData()
|
|
if err != nil {
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: failed to load ICMP packet data: %s", err)
|
|
return network.VerdictAccept, true
|
|
}
|
|
|
|
// Submit to ICMP listener.
|
|
submitted := netenv.SubmitPacketToICMPListener(pkt)
|
|
if submitted {
|
|
// If the packet was submitted to the listener, we must not do a
|
|
// permanent accept, because then we won't see any future packets of that
|
|
// connection and thus cannot continue to submit them.
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track tracing ICMP/v6: %s", pkt)
|
|
return network.VerdictAccept, false
|
|
}
|
|
|
|
// Handle echo request and replies regularly.
|
|
// Other ICMP packets are considered system business.
|
|
icmpLayers := pkt.Layers().LayerClass(layers.LayerClassIPControl)
|
|
switch icmpLayer := icmpLayers.(type) {
|
|
case *layers.ICMPv4:
|
|
switch icmpLayer.TypeCode.Type() {
|
|
case layers.ICMPv4TypeEchoRequest,
|
|
layers.ICMPv4TypeEchoReply:
|
|
return network.VerdictUndecided, false
|
|
}
|
|
case *layers.ICMPv6:
|
|
switch icmpLayer.TypeCode.Type() {
|
|
case layers.ICMPv6TypeEchoRequest,
|
|
layers.ICMPv6TypeEchoReply:
|
|
return network.VerdictUndecided, false
|
|
}
|
|
}
|
|
|
|
// Permit all ICMP/v6 packets that are not echo requests or replies.
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track accepting ICMP/v6: %s", pkt)
|
|
return network.VerdictAccept, true
|
|
|
|
case packet.UDP, packet.TCP:
|
|
switch meta.DstPort {
|
|
|
|
case 67, 68, 546, 547:
|
|
// Always allow DHCP, DHCPv6.
|
|
|
|
// DHCP and DHCPv6 must be UDP.
|
|
if meta.Protocol != packet.UDP {
|
|
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 network.VerdictUndecided, false
|
|
}
|
|
|
|
// Log and permit.
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track accepting DHCP: %s", pkt)
|
|
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 network.VerdictUndecided, false
|
|
}
|
|
|
|
// Check if the api port is even set.
|
|
if !apiPortSet {
|
|
return network.VerdictUndecided, false
|
|
}
|
|
|
|
// Must be destined for the API IP.
|
|
if !meta.Dst.Equal(apiIP) {
|
|
return network.VerdictUndecided, false
|
|
}
|
|
|
|
// Only fast-track local requests.
|
|
isMe, err := netenv.IsMyIP(meta.Src)
|
|
switch {
|
|
case err != nil:
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
|
|
return network.VerdictUndecided, false
|
|
case !isMe:
|
|
return network.VerdictUndecided, false
|
|
}
|
|
|
|
// Log and permit.
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track accepting api connection: %s", pkt)
|
|
return network.VerdictAccept, true
|
|
|
|
case 53:
|
|
// Always allow direct access to the Portmaster Nameserver.
|
|
// DNS is both UDP and TCP.
|
|
|
|
// Check if a nameserver IP matcher is set.
|
|
if !nameserverIPMatcherReady.IsSet() {
|
|
return network.VerdictUndecided, false
|
|
}
|
|
|
|
// Check if packet is destined for a nameserver IP.
|
|
if !nameserverIPMatcher(meta.Dst) {
|
|
return network.VerdictUndecided, false
|
|
}
|
|
|
|
// Only fast-track local requests.
|
|
isMe, err := netenv.IsMyIP(meta.Src)
|
|
switch {
|
|
case err != nil:
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: failed to check if %s is own IP for fast-track: %s", meta.Src, err)
|
|
return network.VerdictUndecided, false
|
|
case !isMe:
|
|
return network.VerdictUndecided, false
|
|
}
|
|
|
|
// Log and permit.
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: fast-track accepting local dns: %s", pkt)
|
|
|
|
// Add to DNS request connections to attribute DNS request if outgoing.
|
|
if pkt.IsOutbound() {
|
|
// Assign PID from packet directly, as processing stops after fast-track.
|
|
conn.PID = pkt.Info().PID
|
|
network.SaveDNSRequestConnection(conn, pkt)
|
|
}
|
|
|
|
// Accept local DNS, but only make permanent if we have the PID too.
|
|
return network.VerdictAccept, conn.PID != process.UndefinedProcessID
|
|
}
|
|
|
|
case compat.SystemIntegrationCheckProtocol:
|
|
if pkt.Info().Dst.Equal(compat.SystemIntegrationCheckDstIP) {
|
|
compat.SubmitSystemIntegrationCheckPacket(pkt)
|
|
return network.VerdictDrop, false
|
|
}
|
|
}
|
|
|
|
return network.VerdictUndecided, false
|
|
}
|
|
|
|
func fastTrackHandler(conn *network.Connection, pkt packet.Packet) {
|
|
conn.SaveWhenFinished()
|
|
|
|
fastTrackedVerdict, permanent := fastTrackedPermit(conn, pkt)
|
|
if fastTrackedVerdict != network.VerdictUndecided {
|
|
// Set verdict on connection.
|
|
conn.Verdict = 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)
|
|
|
|
// 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) {
|
|
conn.SaveWhenFinished()
|
|
|
|
// 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.
|
|
switch conn.IPProtocol { //nolint:exhaustive
|
|
case packet.ICMP, packet.ICMPv6:
|
|
conn.UpdateFirewallHandler(icmpFilterHandler)
|
|
icmpFilterHandler(conn, pkt)
|
|
|
|
default:
|
|
conn.UpdateFirewallHandler(filterHandler)
|
|
filterHandler(conn, pkt)
|
|
}
|
|
}
|
|
|
|
func filterHandler(conn *network.Connection, pkt packet.Packet) {
|
|
conn.SaveWhenFinished()
|
|
|
|
// Skip if data is not complete or packet is info-only.
|
|
if !conn.DataIsComplete() || pkt.InfoOnly() {
|
|
return
|
|
}
|
|
|
|
filterConnection := true
|
|
|
|
// Check for special (internal) connection cases.
|
|
switch {
|
|
case !conn.Inbound && localPortIsPreAuthenticated(conn.Entity.Protocol, conn.LocalPort):
|
|
// Approve connection.
|
|
conn.Accept("connection by Portmaster", noReasonOptionKey)
|
|
conn.Internal = true
|
|
filterConnection = false
|
|
log.Tracer(pkt.Ctx()).Infof("filter: granting own pre-authenticated connection %s", conn)
|
|
|
|
// Redirect outbound DNS packets if enabled,
|
|
case dnsQueryInterception() &&
|
|
pkt.IsOutbound() &&
|
|
pkt.Info().DstPort == 53 &&
|
|
// that don't match the address of our nameserver,
|
|
nameserverIPMatcherReady.IsSet() &&
|
|
!nameserverIPMatcher(pkt.Info().Dst) &&
|
|
// and are not broadcast queries by us.
|
|
// Context:
|
|
// - Unicast queries by the resolver are pre-authenticated.
|
|
// - Unicast queries by the compat self-check should be redirected.
|
|
!(conn.Process().Pid == ownPID &&
|
|
conn.Entity.IPScope == netutils.LocalMulticast):
|
|
|
|
// Reroute rogue dns queries back to Portmaster.
|
|
conn.SetVerdict(network.VerdictRerouteToNameserver, "redirecting rogue dns query", "", nil)
|
|
conn.Internal = true
|
|
log.Tracer(pkt.Ctx()).Infof("filter: redirecting dns query %s to Portmaster", conn)
|
|
|
|
// Add to DNS request connections to attribute DNS request.
|
|
network.SaveDNSRequestConnection(conn, pkt)
|
|
|
|
// End directly, as no other processing is necessary.
|
|
conn.StopFirewallHandler()
|
|
|
|
issueVerdict(conn, pkt, 0, true)
|
|
return
|
|
}
|
|
|
|
// Apply privacy filter and check tunneling.
|
|
FilterConnection(pkt.Ctx(), conn, pkt, filterConnection, true)
|
|
|
|
// Decide how to continue handling connection.
|
|
switch {
|
|
case conn.Inspecting:
|
|
log.Tracer(pkt.Ctx()).Trace("filter: start inspecting")
|
|
conn.UpdateFirewallHandler(inspectAndVerdictHandler)
|
|
inspectAndVerdictHandler(conn, pkt)
|
|
|
|
default:
|
|
conn.StopFirewallHandler()
|
|
verdictHandler(conn, pkt)
|
|
}
|
|
}
|
|
|
|
// 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")
|
|
decideOnConnection(ctx, conn, pkt)
|
|
} else {
|
|
conn.Accept("privacy filter disabled", noReasonOptionKey)
|
|
}
|
|
}
|
|
|
|
// TODO: Enable inspection framework again.
|
|
conn.Inspecting = false
|
|
|
|
// TODO: Quick fix for the SPN.
|
|
// Use inspection framework for proper encryption detection.
|
|
switch conn.Entity.DstPort() {
|
|
case
|
|
22, // SSH
|
|
443, // HTTPS
|
|
465, // SMTP-SSL
|
|
853, // DoT
|
|
993, // IMAP-SSL
|
|
995: // POP3-SSL
|
|
conn.Encrypted = true
|
|
}
|
|
|
|
// Check if connection should be tunneled.
|
|
if checkTunnel {
|
|
checkTunneling(ctx, conn)
|
|
}
|
|
|
|
// Request tunneling if no tunnel is set and connection should be tunneled.
|
|
if conn.Verdict == network.VerdictRerouteToTunnel &&
|
|
conn.TunnelContext == nil {
|
|
err := requestTunneling(ctx, conn)
|
|
if err == nil {
|
|
conn.ConnectionEstablished = true
|
|
} else {
|
|
// Set connection to failed, but keep tunneling data.
|
|
// The tunneling data makes connection easy to recognize as a failed SPN
|
|
// connection and the data will help with debugging and displaying in the UI.
|
|
conn.Failed(fmt.Sprintf("failed to request tunneling: %s", err), "")
|
|
}
|
|
}
|
|
}
|
|
|
|
// defaultFirewallHandler is used when no other firewall handler is set on a connection.
|
|
func defaultFirewallHandler(conn *network.Connection, pkt packet.Packet) {
|
|
switch conn.IPProtocol { //nolint:exhaustive
|
|
case packet.ICMP, packet.ICMPv6:
|
|
// Always use the ICMP handler for ICMP connections.
|
|
icmpFilterHandler(conn, pkt)
|
|
|
|
default:
|
|
verdictHandler(conn, pkt)
|
|
}
|
|
}
|
|
|
|
func verdictHandler(conn *network.Connection, pkt packet.Packet) {
|
|
// Ignore info-only packets in this handler.
|
|
if pkt.InfoOnly() {
|
|
return
|
|
}
|
|
|
|
issueVerdict(conn, pkt, 0, true)
|
|
}
|
|
|
|
func inspectAndVerdictHandler(conn *network.Connection, pkt packet.Packet) {
|
|
// Ignore info-only packets in this handler.
|
|
if pkt.InfoOnly() {
|
|
return
|
|
}
|
|
|
|
// Run inspectors.
|
|
pktVerdict, continueInspection := inspection.RunInspectors(conn, pkt)
|
|
if continueInspection {
|
|
issueVerdict(conn, pkt, pktVerdict, false)
|
|
return
|
|
}
|
|
|
|
// we are done with inspecting
|
|
conn.StopFirewallHandler()
|
|
issueVerdict(conn, pkt, 0, true)
|
|
}
|
|
|
|
func icmpFilterHandler(conn *network.Connection, pkt packet.Packet) {
|
|
// Load packet data.
|
|
err := pkt.LoadPacketData()
|
|
if err != nil {
|
|
log.Tracer(pkt.Ctx()).Debugf("filter: failed to load ICMP packet data: %s", err)
|
|
issueVerdict(conn, pkt, network.VerdictDrop, false)
|
|
return
|
|
}
|
|
|
|
// Submit to ICMP listener.
|
|
submitted := netenv.SubmitPacketToICMPListener(pkt)
|
|
if submitted {
|
|
issueVerdict(conn, pkt, network.VerdictDrop, false)
|
|
return
|
|
}
|
|
|
|
// Handle echo request and replies regularly.
|
|
// Other ICMP packets are considered system business.
|
|
icmpLayers := pkt.Layers().LayerClass(layers.LayerClassIPControl)
|
|
switch icmpLayer := icmpLayers.(type) {
|
|
case *layers.ICMPv4:
|
|
switch icmpLayer.TypeCode.Type() {
|
|
case layers.ICMPv4TypeEchoRequest,
|
|
layers.ICMPv4TypeEchoReply:
|
|
// Continue
|
|
default:
|
|
issueVerdict(conn, pkt, network.VerdictAccept, false)
|
|
return
|
|
}
|
|
|
|
case *layers.ICMPv6:
|
|
switch icmpLayer.TypeCode.Type() {
|
|
case layers.ICMPv6TypeEchoRequest,
|
|
layers.ICMPv6TypeEchoReply:
|
|
// Continue
|
|
|
|
default:
|
|
issueVerdict(conn, pkt, network.VerdictAccept, false)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Check if we already have a verdict.
|
|
switch conn.Verdict { //nolint:exhaustive
|
|
case network.VerdictUndecided, network.VerdictUndeterminable:
|
|
// Apply privacy filter and check tunneling.
|
|
FilterConnection(pkt.Ctx(), conn, pkt, true, false)
|
|
|
|
// Save and propagate changes.
|
|
conn.SaveWhenFinished()
|
|
}
|
|
|
|
// Outbound direction has priority.
|
|
if conn.Inbound && conn.Ended == 0 && pkt.IsOutbound() {
|
|
// Change direction from inbound to outbound on first outbound ICMP packet.
|
|
conn.Inbound = false
|
|
|
|
// Apply privacy filter and check tunneling.
|
|
FilterConnection(pkt.Ctx(), conn, pkt, true, false)
|
|
|
|
// Save and propagate changes.
|
|
conn.SaveWhenFinished()
|
|
}
|
|
|
|
issueVerdict(conn, pkt, 0, false)
|
|
}
|
|
|
|
func issueVerdict(conn *network.Connection, pkt packet.Packet, verdict network.Verdict, allowPermanent bool) {
|
|
// Check if packed was already fast-tracked by the OS integration.
|
|
if pkt.FastTrackedByIntegration() {
|
|
return
|
|
}
|
|
|
|
// Enable permanent verdict.
|
|
if allowPermanent && !conn.VerdictPermanent && permanentVerdicts() {
|
|
conn.VerdictPermanent = true
|
|
conn.SaveWhenFinished()
|
|
}
|
|
|
|
// do not allow to circumvent decision: e.g. to ACCEPT packets from a DROP-ed connection
|
|
if verdict < conn.Verdict {
|
|
verdict = conn.Verdict
|
|
}
|
|
|
|
var err error
|
|
switch verdict {
|
|
case network.VerdictAccept:
|
|
atomic.AddUint64(packetsAccepted, 1)
|
|
if conn.VerdictPermanent {
|
|
err = pkt.PermanentAccept()
|
|
} else {
|
|
err = pkt.Accept()
|
|
}
|
|
case network.VerdictBlock:
|
|
atomic.AddUint64(packetsBlocked, 1)
|
|
if conn.VerdictPermanent {
|
|
err = pkt.PermanentBlock()
|
|
} else {
|
|
err = pkt.Block()
|
|
}
|
|
case network.VerdictDrop:
|
|
atomic.AddUint64(packetsDropped, 1)
|
|
if conn.VerdictPermanent {
|
|
err = pkt.PermanentDrop()
|
|
} else {
|
|
err = pkt.Drop()
|
|
}
|
|
case network.VerdictRerouteToNameserver:
|
|
err = pkt.RerouteToNameserver()
|
|
case network.VerdictRerouteToTunnel:
|
|
err = pkt.RerouteToTunnel()
|
|
case network.VerdictFailed:
|
|
atomic.AddUint64(packetsFailed, 1)
|
|
err = pkt.Drop()
|
|
case network.VerdictUndecided, network.VerdictUndeterminable:
|
|
log.Tracer(pkt.Ctx()).Warningf("filter: tried to apply verdict %s to pkt %s: dropping instead", verdict, pkt)
|
|
fallthrough
|
|
default:
|
|
atomic.AddUint64(packetsDropped, 1)
|
|
err = pkt.Drop()
|
|
}
|
|
|
|
if err != nil {
|
|
log.Tracer(pkt.Ctx()).Warningf("filter: failed to apply verdict to pkt %s: %s", pkt, err)
|
|
}
|
|
}
|
|
|
|
// func tunnelHandler(pkt packet.Packet) {
|
|
// tunnelInfo := GetTunnelInfo(pkt.Info().Dst)
|
|
// if tunnelInfo == nil {
|
|
// pkt.Block()
|
|
// return
|
|
// }
|
|
//
|
|
// entry.CreateTunnel(pkt, tunnelInfo.Domain, tunnelInfo.RRCache.ExportAllARecords())
|
|
// log.Tracef("filter: rerouting %s to tunnel entry point", pkt)
|
|
// pkt.RerouteToTunnel()
|
|
// return
|
|
// }
|
|
|
|
func packetHandler(ctx context.Context) error {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case pkt := <-interception.Packets:
|
|
if pkt != nil {
|
|
handlePacket(pkt)
|
|
} else {
|
|
return errors.New("received nil packet from interception")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func bandwidthUpdateHandler(ctx context.Context) error {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case bwUpdate := <-interception.BandwidthUpdates:
|
|
if bwUpdate != nil {
|
|
// DEBUG:
|
|
// log.Debugf("filter: bandwidth update: %s", bwUpdate)
|
|
updateBandwidth(ctx, bwUpdate)
|
|
} else {
|
|
return errors.New("received nil bandwidth update from interception")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateBandwidth(ctx context.Context, bwUpdate *packet.BandwidthUpdate) {
|
|
// Check if update makes sense.
|
|
if bwUpdate.BytesReceived == 0 && bwUpdate.BytesSent == 0 {
|
|
return
|
|
}
|
|
|
|
// Get connection.
|
|
conn, ok := network.GetConnection(bwUpdate.ConnID)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Do not wait for connections that are locked.
|
|
// TODO: Use atomic operations for updating bandwidth stats.
|
|
if !conn.TryLock() {
|
|
// DEBUG:
|
|
// log.Warningf("filter: failed to lock connection for bandwidth update: %s", conn)
|
|
return
|
|
}
|
|
defer conn.Unlock()
|
|
|
|
bytesIn := bwUpdate.BytesReceived
|
|
bytesOut := bwUpdate.BytesSent
|
|
|
|
// Update stats according to method.
|
|
switch bwUpdate.Method {
|
|
case packet.Absolute:
|
|
bytesIn = bwUpdate.BytesReceived - conn.BytesReceived
|
|
bytesOut = bwUpdate.BytesSent - conn.BytesSent
|
|
|
|
conn.BytesReceived = bwUpdate.BytesReceived
|
|
conn.BytesSent = bwUpdate.BytesSent
|
|
case packet.Additive:
|
|
conn.BytesReceived += bwUpdate.BytesReceived
|
|
conn.BytesSent += bwUpdate.BytesSent
|
|
default:
|
|
log.Warningf("filter: unsupported bandwidth update method: %d", bwUpdate.Method)
|
|
return
|
|
}
|
|
|
|
// Update bandwidth in the netquery module.
|
|
if netquery.DefaultModule != nil && conn.BandwidthEnabled {
|
|
if err := netquery.DefaultModule.Store.UpdateBandwidth(
|
|
ctx,
|
|
conn.HistoryEnabled,
|
|
fmt.Sprintf("%s/%s", conn.ProcessContext.Source, conn.ProcessContext.Profile),
|
|
conn.Process().GetKey(),
|
|
conn.ID,
|
|
bytesIn,
|
|
bytesOut,
|
|
); err != nil {
|
|
log.Errorf("filter: failed to persist bandwidth data: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func statLogger(ctx context.Context) error {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case <-time.After(10 * time.Second):
|
|
log.Tracef(
|
|
"filter: packets accepted %d, blocked %d, dropped %d, failed %d",
|
|
atomic.LoadUint64(packetsAccepted),
|
|
atomic.LoadUint64(packetsBlocked),
|
|
atomic.LoadUint64(packetsDropped),
|
|
atomic.LoadUint64(packetsFailed),
|
|
)
|
|
atomic.StoreUint64(packetsAccepted, 0)
|
|
atomic.StoreUint64(packetsBlocked, 0)
|
|
atomic.StoreUint64(packetsDropped, 0)
|
|
atomic.StoreUint64(packetsFailed, 0)
|
|
}
|
|
}
|
|
}
|