mirror of
https://github.com/netbirdio/netbird.git
synced 2025-07-19 07:34:28 +02:00
127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/windows"
|
|
|
|
"github.com/netbirdio/netbird/client/internal"
|
|
"github.com/netbirdio/netbird/client/internal/peer"
|
|
)
|
|
|
|
const (
|
|
envListenEvent = "NB_LISTEN_DEBUG_EVENT"
|
|
debugTriggerEventName = `Global\NetbirdDebugTriggerEvent`
|
|
|
|
waitTimeout = 5 * time.Second
|
|
)
|
|
|
|
// SetupDebugHandler sets up a Windows event to listen for a signal to generate a debug bundle.
|
|
// Example usage with PowerShell:
|
|
// $evt = [System.Threading.EventWaitHandle]::OpenExisting("Global\NetbirdDebugTriggerEvent")
|
|
// $evt.Set()
|
|
// $evt.Close()
|
|
func SetupDebugHandler(
|
|
ctx context.Context,
|
|
config *internal.Config,
|
|
recorder *peer.Status,
|
|
connectClient *internal.ConnectClient,
|
|
logFilePath string,
|
|
) {
|
|
env := os.Getenv(envListenEvent)
|
|
if env == "" {
|
|
return
|
|
}
|
|
|
|
listenEvent, err := strconv.ParseBool(env)
|
|
if err != nil {
|
|
log.Errorf("Failed to parse %s: %v", envListenEvent, err)
|
|
return
|
|
}
|
|
if !listenEvent {
|
|
return
|
|
}
|
|
|
|
eventNamePtr, err := windows.UTF16PtrFromString(debugTriggerEventName)
|
|
if err != nil {
|
|
log.Errorf("Failed to convert event name '%s' to UTF16: %v", debugTriggerEventName, err)
|
|
return
|
|
}
|
|
|
|
// TODO: restrict access by ACL
|
|
eventHandle, err := windows.CreateEvent(nil, 1, 0, eventNamePtr)
|
|
if err != nil {
|
|
if errors.Is(err, windows.ERROR_ALREADY_EXISTS) {
|
|
log.Warnf("Debug trigger event '%s' already exists. Attempting to open.", debugTriggerEventName)
|
|
// SYNCHRONIZE is needed for WaitForSingleObject, EVENT_MODIFY_STATE for ResetEvent.
|
|
eventHandle, err = windows.OpenEvent(windows.SYNCHRONIZE|windows.EVENT_MODIFY_STATE, false, eventNamePtr)
|
|
if err != nil {
|
|
log.Errorf("Failed to open existing debug trigger event '%s': %v", debugTriggerEventName, err)
|
|
return
|
|
}
|
|
log.Infof("Successfully opened existing debug trigger event '%s'.", debugTriggerEventName)
|
|
} else {
|
|
log.Errorf("Failed to create debug trigger event '%s': %v", debugTriggerEventName, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if eventHandle == windows.InvalidHandle {
|
|
log.Errorf("Obtained an invalid handle for debug trigger event '%s'", debugTriggerEventName)
|
|
return
|
|
}
|
|
|
|
log.Infof("Debug handler waiting for signal on event: %s", debugTriggerEventName)
|
|
|
|
go waitForEvent(ctx, config, recorder, connectClient, logFilePath, eventHandle)
|
|
}
|
|
|
|
func waitForEvent(
|
|
ctx context.Context,
|
|
config *internal.Config,
|
|
recorder *peer.Status,
|
|
connectClient *internal.ConnectClient,
|
|
logFilePath string,
|
|
eventHandle windows.Handle,
|
|
) {
|
|
defer func() {
|
|
if err := windows.CloseHandle(eventHandle); err != nil {
|
|
log.Errorf("Failed to close debug event handle '%s': %v", debugTriggerEventName, err)
|
|
}
|
|
}()
|
|
|
|
for {
|
|
if ctx.Err() != nil {
|
|
return
|
|
}
|
|
|
|
status, err := windows.WaitForSingleObject(eventHandle, uint32(waitTimeout.Milliseconds()))
|
|
|
|
switch status {
|
|
case windows.WAIT_OBJECT_0:
|
|
log.Info("Received signal on debug event. Triggering debug bundle generation.")
|
|
|
|
// reset the event so it can be triggered again later (manual reset == 1)
|
|
if err := windows.ResetEvent(eventHandle); err != nil {
|
|
log.Errorf("Failed to reset debug event '%s': %v", debugTriggerEventName, err)
|
|
}
|
|
|
|
go generateDebugBundle(config, recorder, connectClient, logFilePath)
|
|
case uint32(windows.WAIT_TIMEOUT):
|
|
|
|
default:
|
|
log.Errorf("Unexpected status %d from WaitForSingleObject for debug event '%s': %v", status, debugTriggerEventName, err)
|
|
select {
|
|
case <-time.After(5 * time.Second):
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|