//go:build windows package divert import ( "errors" "fmt" idivert "github.com/imgk/divert-go" ) // Handle wraps a WinDivert handle. type Handle struct { h *idivert.Handle } // Open opens a WinDivert handle at NETWORK layer for outbound capture. // The filter expression is the standard WinDivert syntax (see // internal/divert/filter.go for our builder). // // Returns ErrAccessDenied when the calling process is not elevated. // Returns ErrDriverFailedPriorUnload when an outdated WinDivert // (e.g. v1.x from zapret) is already loaded. func Open(filter string) (*Handle, error) { h, err := idivert.Open(filter, idivert.LayerNetwork, 0, 0) if err != nil { return nil, mapWinDivertErr(err) } return &Handle{h: h}, nil } // Close closes the handle. Safe to call multiple times. func (h *Handle) Close() error { if h == nil || h.h == nil { return nil } err := h.h.Close() h.h = nil return err } // Recv blocks until a packet arrives that matches the filter, or until // the handle is closed (Close from another goroutine returns // ErrShutdown to the recv'er). buf must be sized for a full Ethernet // MTU (~1600 bytes is fine). // // Returns the captured packet length, the WinDivertAddress (containing // direction, interface index, etc), and any error. func (h *Handle) Recv(buf []byte) (int, *idivert.Address, error) { if h == nil || h.h == nil { return 0, nil, errors.New("handle closed") } addr := new(idivert.Address) n, err := h.h.Recv(buf, addr) if err != nil { return 0, nil, mapWinDivertErr(err) } return int(n), addr, nil } // Send reinjects a packet. The address typically comes from a previous // Recv call (so the kernel knows whether it's outbound or inbound, which // interface, etc). func (h *Handle) Send(buf []byte, addr *idivert.Address) (int, error) { if h == nil || h.h == nil { return 0, errors.New("handle closed") } n, err := h.h.Send(buf, addr) if err != nil { return 0, mapWinDivertErr(err) } return int(n), nil } // Sentinel errors mapped from raw Windows errors so the engine layer // can pattern-match without importing windows package. var ( ErrAccessDenied = errors.New("WinDivert: access denied (need admin)") ErrDriverFailedPriorUnload = errors.New("WinDivert: outdated driver from another tool is loaded; reboot or stop the other tool first") ErrInvalidHandle = errors.New("WinDivert: handle invalidated (driver crashed?)") ErrShutdown = errors.New("WinDivert: shutdown") ) func mapWinDivertErr(err error) error { if err == nil { return nil } msg := err.Error() switch { case contains(msg, "access is denied"), contains(msg, "ACCESS_DENIED"): return ErrAccessDenied case contains(msg, "FAILED_PRIOR_UNLOAD"), contains(msg, "prior unload"): return ErrDriverFailedPriorUnload case contains(msg, "INVALID_HANDLE"): return ErrInvalidHandle case contains(msg, "SHUTDOWN"): return ErrShutdown } return fmt.Errorf("WinDivert: %w", err) } func contains(s, sub string) bool { // case-insensitive if len(sub) == 0 { return true } if len(s) < len(sub) { return false } for i := 0; i+len(sub) <= len(s); i++ { match := true for j := 0; j < len(sub); j++ { a, b := s[i+j], sub[j] if a >= 'A' && a <= 'Z' { a += 32 } if b >= 'A' && b <= 'Z' { b += 32 } if a != b { match = false break } } if match { return true } } return false }