From 7a83e772f40994cdf43dd6b36c9f4ea02ca5b35d Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Thu, 1 Oct 2020 11:30:18 +0200 Subject: [PATCH 1/2] Add simple packet metrics --- firewall/interception/interception.go | 15 +++- firewall/interception/interception_default.go | 2 +- firewall/interception/interception_linux.go | 6 +- firewall/interception/interception_windows.go | 5 +- firewall/interception/introspection.go | 78 +++++++++++++++++++ firewall/interception/nfqueue_linux.go | 25 +++--- firewall/interception/packet_tracer.go | 67 ++++++++++++++++ 7 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 firewall/interception/introspection.go create mode 100644 firewall/interception/packet_tracer.go diff --git a/firewall/interception/interception.go b/firewall/interception/interception.go index b4631ee3..8ee72788 100644 --- a/firewall/interception/interception.go +++ b/firewall/interception/interception.go @@ -25,7 +25,18 @@ func Start() error { return nil } - return start() + var ch = Packets + if packetMetricsDestination != "" { + go metrics.writeMetrics() + ch = make(chan packet.Packet) + go func() { + for p := range ch { + Packets <- tracePacket(p) + } + }() + } + + return start(ch) } // Stop starts the interception. @@ -34,5 +45,7 @@ func Stop() error { return nil } + close(metrics.done) + return stop() } diff --git a/firewall/interception/interception_default.go b/firewall/interception/interception_default.go index ba8c91d6..3f19b1e7 100644 --- a/firewall/interception/interception_default.go +++ b/firewall/interception/interception_default.go @@ -7,7 +7,7 @@ import ( ) // start starts the interception. -func start() error { +func start(ch chan packet.Packet) error { log.Info("interception: this platform has no support for packet interception - a lot of functionality will be broken") return nil } diff --git a/firewall/interception/interception_linux.go b/firewall/interception/interception_linux.go index 5feb00f9..e1b13874 100644 --- a/firewall/interception/interception_linux.go +++ b/firewall/interception/interception_linux.go @@ -1,8 +1,10 @@ package interception +import "github.com/safing/portmaster/network/packet" + // start starts the interception. -func start() error { - return StartNfqueueInterception() +func start(ch chan packet.Packet) error { + return StartNfqueueInterception(ch) } // stop starts the interception. diff --git a/firewall/interception/interception_windows.go b/firewall/interception/interception_windows.go index cbefec43..89a859f8 100644 --- a/firewall/interception/interception_windows.go +++ b/firewall/interception/interception_windows.go @@ -7,11 +7,12 @@ import ( "github.com/safing/portbase/notifications" "github.com/safing/portbase/utils/osdetail" "github.com/safing/portmaster/firewall/interception/windowskext" + "github.com/safing/portmaster/network/packet" "github.com/safing/portmaster/updates" ) // start starts the interception. -func start() error { +func start(ch chan packet.Packet) error { dllFile, err := updates.GetPlatformFile("kext/portmaster-kext.dll") if err != nil { return fmt.Errorf("interception: could not get kext dll: %s", err) @@ -31,7 +32,7 @@ func start() error { return fmt.Errorf("interception: could not start windows kext: %s", err) } - go windowskext.Handler(Packets) + go windowskext.Handler(ch) go handleWindowsDNSCache() return nil diff --git a/firewall/interception/introspection.go b/firewall/interception/introspection.go new file mode 100644 index 00000000..40091ba3 --- /dev/null +++ b/firewall/interception/introspection.go @@ -0,0 +1,78 @@ +package interception + +import ( + "flag" + "fmt" + "os" + "sync" + "time" + + "github.com/safing/portbase/log" +) + +var ( + packetMetricsDestination string + metrics = &packetMetrics{ + done: make(chan struct{}), + } +) + +func init() { + flag.StringVar(&packetMetricsDestination, "packet-metrics", "", "Write packet metrics") +} + +type ( + performanceRecord struct { + start int64 + duration time.Duration + verdict string + } + + packetMetrics struct { + done chan struct{} + l sync.Mutex + records []*performanceRecord + } +) + +func (pm *packetMetrics) record(tp *tracedPacket, verdict string) { + go func(start int64, duration time.Duration) { + pm.l.Lock() + defer pm.l.Unlock() + + pm.records = append(pm.records, &performanceRecord{ + start: start, + duration: duration, + verdict: verdict, + }) + }(tp.start.UnixNano(), time.Since(tp.start)) +} + +func (pm *packetMetrics) writeMetrics() { + if packetMetricsDestination == "" { + return + } + + f, err := os.Create(packetMetricsDestination) + if err != nil { + log.Errorf("Failed to create packet metrics file: %s", err) + return + } + defer f.Close() + + for { + select { + case <-pm.done: + return + case <-time.After(time.Second * 5): + } + pm.l.Lock() + records := pm.records + pm.records = nil + pm.l.Unlock() + + for _, r := range records { + fmt.Fprintf(f, "%d;%s;%s;%.2f\n", r.start, r.verdict, r.duration, float64(r.duration)/float64(time.Microsecond)) + } + } +} diff --git a/firewall/interception/nfqueue_linux.go b/firewall/interception/nfqueue_linux.go index 3c3fed42..a36e13d7 100644 --- a/firewall/interception/nfqueue_linux.go +++ b/firewall/interception/nfqueue_linux.go @@ -223,7 +223,7 @@ func deactivateIPTables(protocol iptables.Protocol, rules, chains []string) erro } // StartNfqueueInterception starts the nfqueue interception. -func StartNfqueueInterception() (err error) { +func StartNfqueueInterception(packets chan<- packet.Packet) (err error) { // @deprecated, remove in v1 if experimentalNfqueueBackend { log.Warningf("[DEPRECATED] --experimental-nfqueue has been deprecated as the backend is now used by default") @@ -257,7 +257,7 @@ func StartNfqueueInterception() (err error) { return fmt.Errorf("nfqueue(IPv6, in): %w", err) } - go handleInterception() + go handleInterception(packets) return nil } @@ -286,23 +286,26 @@ func StopNfqueueInterception() error { return nil } -func handleInterception() { +func handleInterception(packets chan<- packet.Packet) { for { + var pkt packet.Packet select { case <-shutdownSignal: return - case pkt := <-out4Queue.PacketChannel(): + case pkt = <-out4Queue.PacketChannel(): pkt.SetOutbound() - Packets <- pkt - case pkt := <-in4Queue.PacketChannel(): + case pkt = <-in4Queue.PacketChannel(): pkt.SetInbound() - Packets <- pkt - case pkt := <-out6Queue.PacketChannel(): + case pkt = <-out6Queue.PacketChannel(): pkt.SetOutbound() - Packets <- pkt - case pkt := <-in6Queue.PacketChannel(): + case pkt = <-in6Queue.PacketChannel(): pkt.SetInbound() - Packets <- pkt + } + + select { + case packets <- pkt: + case <-shutdownSignal: + return } } } diff --git a/firewall/interception/packet_tracer.go b/firewall/interception/packet_tracer.go new file mode 100644 index 00000000..4d822a42 --- /dev/null +++ b/firewall/interception/packet_tracer.go @@ -0,0 +1,67 @@ +package interception + +import ( + "time" + + "github.com/safing/portmaster/network/packet" +) + +type tracedPacket struct { + start time.Time + packet.Packet +} + +func tracePacket(p packet.Packet) packet.Packet { + return &tracedPacket{ + start: time.Now(), + Packet: p, + } +} + +func (p *tracedPacket) markServed(v string) { + if packetMetricsDestination == "" { + return + } + + metrics.record(p, v) +} + +func (p *tracedPacket) Accept() error { + defer p.markServed("accept") + return p.Packet.Accept() +} + +func (p *tracedPacket) Block() error { + defer p.markServed("block") + return p.Packet.Block() +} + +func (p *tracedPacket) Drop() error { + defer p.markServed("drop") + return p.Packet.Drop() +} + +func (p *tracedPacket) PermanentAccept() error { + defer p.markServed("perm-accept") + return p.Packet.PermanentAccept() +} + +func (p *tracedPacket) PermanentBlock() error { + defer p.markServed("perm-block") + return p.Packet.PermanentBlock() +} + +func (p *tracedPacket) PermanentDrop() error { + defer p.markServed("perm-drop") + return p.Packet.PermanentDrop() +} + +func (p *tracedPacket) RerouteToNameserver() error { + defer p.markServed("reroute-ns") + return p.Packet.RerouteToNameserver() +} + +func (p *tracedPacket) RerouteToTunnel() error { + defer p.markServed("reroute-tunnel") + return p.Packet.RerouteToTunnel() +} From 1b45659f472f01d2bf5c339b21d0bcfc74e49695 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Thu, 1 Oct 2020 16:14:16 +0200 Subject: [PATCH 2/2] Add review changes --- firewall/interception/interception.go | 8 ++++---- firewall/interception/introspection.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/firewall/interception/interception.go b/firewall/interception/interception.go index 8ee72788..3c4bb5b1 100644 --- a/firewall/interception/interception.go +++ b/firewall/interception/interception.go @@ -25,18 +25,18 @@ func Start() error { return nil } - var ch = Packets + var inputPackets = Packets if packetMetricsDestination != "" { go metrics.writeMetrics() - ch = make(chan packet.Packet) + inputPackets = make(chan packet.Packet) go func() { - for p := range ch { + for p := range inputPackets { Packets <- tracePacket(p) } }() } - return start(ch) + return start(inputPackets) } // Stop starts the interception. diff --git a/firewall/interception/introspection.go b/firewall/interception/introspection.go index 40091ba3..b8e949eb 100644 --- a/firewall/interception/introspection.go +++ b/firewall/interception/introspection.go @@ -18,7 +18,7 @@ var ( ) func init() { - flag.StringVar(&packetMetricsDestination, "packet-metrics", "", "Write packet metrics") + flag.StringVar(&packetMetricsDestination, "write-packet-metrics", "", "Write packet metrics to the specified file") } type (