diff --git a/client/server/panic_generic.go b/client/server/panic_generic.go new file mode 100644 index 0000000000..f027b954b3 --- /dev/null +++ b/client/server/panic_generic.go @@ -0,0 +1,7 @@ +//go:build !windows + +package server + +func handlePanicLog() error { + return nil +} diff --git a/client/server/panic_windows.go b/client/server/panic_windows.go new file mode 100644 index 0000000000..1d4ba4b756 --- /dev/null +++ b/client/server/panic_windows.go @@ -0,0 +1,83 @@ +package server + +import ( + "fmt" + "os" + "path/filepath" + "syscall" + + log "github.com/sirupsen/logrus" + + "github.com/netbirdio/netbird/util" +) + +const ( + windowsPanicLogEnvVar = "NB_WINDOWS_PANIC_LOG" + // STD_ERROR_HANDLE ((DWORD)-12) = 4294967284 + stdErrorHandle = ^uintptr(11) +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + + // https://learn.microsoft.com/en-us/windows/console/setstdhandle + setStdHandleFn = kernel32.NewProc("SetStdHandle") +) + +func handlePanicLog() error { + logPath := os.Getenv(windowsPanicLogEnvVar) + if logPath == "" { + return nil + } + + // Ensure the directory exists + logDir := filepath.Dir(logPath) + if err := os.MkdirAll(logDir, 0750); err != nil { + return fmt.Errorf("create panic log directory: %w", err) + } + if err := util.EnforcePermission(logPath); err != nil { + return fmt.Errorf("enforce permission on panic log file: %w", err) + } + + // Open log file with append mode + f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("open panic log file: %w", err) + } + + // Redirect stderr to the file + if err = redirectStderr(f); err != nil { + if closeErr := f.Close(); closeErr != nil { + log.Warnf("failed to close file after redirect error: %v", closeErr) + } + return fmt.Errorf("redirect stderr: %w", err) + } + + log.Infof("successfully configured panic logging to: %s", logPath) + return nil +} + +// redirectStderr redirects stderr to the provided file +func redirectStderr(f *os.File) error { + // Get the current process's stderr handle + if err := setStdHandle(f); err != nil { + return fmt.Errorf("failed to set stderr handle: %w", err) + } + + // Also set os.Stderr for Go's standard library + os.Stderr = f + + return nil +} + +func setStdHandle(f *os.File) error { + handle := f.Fd() + r0, _, e1 := setStdHandleFn.Call(stdErrorHandle, handle) + if r0 == 0 { + if e1 != nil { + return e1 + } + return syscall.EINVAL + } + return nil +} diff --git a/client/server/server.go b/client/server/server.go index a033220819..4d921851f9 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -97,6 +97,10 @@ func (s *Server) Start() error { defer s.mutex.Unlock() state := internal.CtxGetState(s.rootCtx) + if err := handlePanicLog(); err != nil { + log.Warnf("failed to redirect stderr: %v", err) + } + if err := restoreResidualState(s.rootCtx); err != nil { log.Warnf(errRestoreResidualState, err) }