//go:build windows // +build windows package dnsmonitor import ( "fmt" "runtime" "sync" "sync/atomic" "github.com/safing/portmaster/service/integration" "golang.org/x/sys/windows" ) type ETWSession struct { i *integration.ETWFunctions shutdownGuard atomic.Bool shutdownMutex sync.Mutex state uintptr } // NewSession creates new ETW event listener and initializes it. This is a low level interface, make sure to call DestroySession when you are done using it. func NewSession(etwInterface *integration.ETWFunctions, callback func(domain string, pid uint32, result string)) (*ETWSession, error) { if etwInterface == nil { return nil, fmt.Errorf("etw interface was nil") } etwSession := &ETWSession{ i: etwInterface, } // Make sure session from previous instances are not running. _ = etwSession.i.StopOldSession() // Initialize notification activated callback win32Callback := windows.NewCallback(func(domain *uint16, pid uint32, result *uint16) uintptr { callback(windows.UTF16PtrToString(domain), pid, windows.UTF16PtrToString(result)) return 0 }) // The function only allocates memory it will not fail. etwSession.state = etwSession.i.CreateState(win32Callback) // Make sure DestroySession is called even if caller forgets to call it. runtime.SetFinalizer(etwSession, func(s *ETWSession) { _ = s.i.DestroySession(s.state) }) // Initialize session. err := etwSession.i.InitializeSession(etwSession.state) if err != nil { return nil, fmt.Errorf("failed to initialize session: %q", err) } return etwSession, nil } // StartTrace starts the tracing session of dns events. This is a blocking call. It will not return until the trace is stopped. func (l *ETWSession) StartTrace() error { return l.i.StartTrace(l.state) } // IsRunning returns true if DestroySession has NOT been called. func (l *ETWSession) IsRunning() bool { return !l.shutdownGuard.Load() } // FlushTrace flushes the trace buffer. func (l *ETWSession) FlushTrace() error { if l.i == nil { return fmt.Errorf("session not initialized") } l.shutdownMutex.Lock() defer l.shutdownMutex.Unlock() // Make sure session is still running. if l.shutdownGuard.Load() { return nil } return l.i.FlushTrace(l.state) } // StopTrace stops the trace. This will cause StartTrace to return. func (l *ETWSession) StopTrace() error { return l.i.StopTrace(l.state) } // DestroySession closes the session and frees the allocated memory. Listener cannot be used after this function is called. func (l *ETWSession) DestroySession() error { if l.i == nil { return fmt.Errorf("session not initialized") } l.shutdownMutex.Lock() defer l.shutdownMutex.Unlock() if l.shutdownGuard.Swap(true) { return nil } err := l.i.DestroySession(l.state) if err != nil { return err } l.state = 0 return nil }