Files
drover-go/internal/divert/divert.go
T
root 1949abf011
Build / test (push) Failing after 29s
Build / build-windows (push) Has been skipped
internal/divert: WinDivert handle wrapper
Thin Go layer over imgk/divert-go. Exposes Open/Close/Recv/Send and
maps the most relevant Windows errors to sentinels (ErrAccessDenied,
ErrDriverFailedPriorUnload, ErrInvalidHandle, ErrShutdown) so the
engine's recovery classifier can reason about them without importing
golang.org/x/sys/windows.

Verified imgk/divert-go@v0.1.0 API matches plan; only deviation is
Recv/Send returning uint (cast to int at our boundary).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 19:47:58 +03:00

131 lines
3.3 KiB
Go

//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
}