Documentation
¶
Overview ¶
Package windows provides transparent per-process TCP interception for the Echo HTTP MITM proxy on Windows.
It uses WinDivert to intercept outbound TCP connections from specified processes at the kernel level, NAT-rewrites them to a local relay port, and the relay forwards the traffic to Echo using standard HTTP proxy protocol (CONNECT for HTTPS, absolute-URL for HTTP).
Architecture:
Target Process (chrome.exe) Echo Proxy (:8899)
│ outbound TCP ▲
▼ │ HTTP proxy protocol
WinDivert kernel intercept │
│ NAT rewrite dst → 127.0.0.1:34010 │
▼ │
Relay Server (:34010) ──────────────────────┘
(CONNECT / absolute-URL conversion)
Requirements:
- Windows 10+ (64-bit)
- Administrator privileges
- WinDivert.dll and WinDivert64.sys in the executable directory
Example usage:
// Create and start Echo proxy
e, _ := echo.NewEcho(certPEM, keyPEM)
e.AddPlugin(&echo.Plugin{
Match: "*.example.com",
OnRequest: func(ctx *echo.Context) {
// inspect/modify request
},
})
go http.ListenAndServe(":8899", e)
// Create the interceptor targeting Echo
interceptor := windows.NewInterceptor("127.0.0.1:8899")
// Add rules for which processes to intercept
interceptor.AddRule(&windows.ProcessRule{
ProcessName: "chrome.exe",
TargetHosts: "*",
TargetPorts: "*",
Action: windows.ActionProxy,
Enabled: true,
})
// Optionally monitor intercepted connections
interceptor.SetConnectionCallback(func(pid uint32, name string,
srcIP, dstIP uint32, srcPort, dstPort uint16, action windows.Action) {
fmt.Printf("[%s] %s:%d → %s:%d (%s)\n",
name, windows.IP32to4(srcIP), srcPort,
windows.IP32to4(dstIP), dstPort, action)
})
// Start intercepting (requires admin privileges)
if err := interceptor.Start(); err != nil {
log.Fatal(err)
}
defer interceptor.Stop()
Index ¶
- Constants
- func CalcChecksums(buf []byte, addr *Address, flags uint64) error
- func Close(h Handle) error
- func IP4to32(ip net.IP) uint32
- func IP32to4(ip uint32) string
- func IsBroadcastOrMulticast(ip uint32) bool
- func Localhost127BE() uint32
- func ParsePacket(buf []byte) (*IPv4Header, *TCPHeader, bool)
- func RecvRaw(h Handle, buf []byte, addr *Address) (int, error)
- func Send(h Handle, buf []byte, addr *Address) error
- func SetDLLPath(dllPath string)
- func SetParam(h Handle, param int, value uint64) error
- func Shutdown(h Handle, how int) error
- type Action
- type Address
- type ConnEntry
- type ConnectionCallback
- type Handle
- type IPv4Header
- func (h *IPv4Header) DstAddr() uint32
- func (h *IPv4Header) DstIP() net.IP
- func (h *IPv4Header) HeaderLen() int
- func (h *IPv4Header) IsLoopbackDst() bool
- func (h *IPv4Header) IsLoopbackSrc() bool
- func (h *IPv4Header) Protocol() uint8
- func (h *IPv4Header) SetDstAddr(ip uint32)
- func (h *IPv4Header) SetSrcAddr(ip uint32)
- func (h *IPv4Header) SrcAddr() uint32
- func (h *IPv4Header) SrcIP() net.IP
- func (h *IPv4Header) TotalLen() uint16
- type Interceptor
- func (i *Interceptor) AddRule(rule *ProcessRule) uint32
- func (i *Interceptor) DisableRule(ruleID uint32) error
- func (i *Interceptor) EnableRule(ruleID uint32) error
- func (i *Interceptor) RemoveRule(ruleID uint32) error
- func (i *Interceptor) SetConnectionCallback(cb ConnectionCallback)
- func (i *Interceptor) SetDNSViaProxy(enabled bool)
- func (i *Interceptor) Start() error
- func (i *Interceptor) Stop() error
- type NATTable
- func (t *NATTable) Add(srcPort uint16, srcIP, destIP uint32, destPort uint16)
- func (t *NATTable) Cleanup()
- func (t *NATTable) Clear()
- func (t *NATTable) IsTracked(srcPort uint16) bool
- func (t *NATTable) Lookup(srcPort uint16) (destIP uint32, destPort uint16, ok bool)
- func (t *NATTable) Remove(srcPort uint16)
- type ProcessResolver
- type ProcessRule
- type Relay
- type RuleManager
- func (rm *RuleManager) AddRule(rule *ProcessRule) uint32
- func (rm *RuleManager) BuildPortFilter() (string, bool)
- func (rm *RuleManager) DisableRule(id uint32) error
- func (rm *RuleManager) EnableRule(id uint32) error
- func (rm *RuleManager) HasActiveRules() bool
- func (rm *RuleManager) Match(processPath string, destIP uint32, destPort uint16) Action
- func (rm *RuleManager) RemoveRule(id uint32) error
- type TCPHeader
- func (h *TCPHeader) DataOffset() int
- func (h *TCPHeader) DstPort() uint16
- func (h *TCPHeader) Flags() uint8
- func (h *TCPHeader) IsFIN() bool
- func (h *TCPHeader) IsRST() bool
- func (h *TCPHeader) IsSYN() bool
- func (h *TCPHeader) SetDstPort(port uint16)
- func (h *TCPHeader) SetSrcPort(port uint16)
- func (h *TCPHeader) SrcPort() uint16
Constants ¶
const ( LayerNetwork = 0 LayerNetworkForward = 1 )
WinDivert layer constants.
const ( ShutdownRecv = 0x1 ShutdownSend = 0x2 ShutdownBoth = 0x3 )
WinDivert shutdown constants.
const ( ParamQueueLen = 0 ParamQueueTime = 1 ParamQueueSize = 2 )
WinDivert parameter constants.
const Localhost127 = 0x7F000001
Localhost127 is 127.0.0.1 in network byte order.
Variables ¶
This section is empty.
Functions ¶
func CalcChecksums ¶
CalcChecksums recalculates IP/TCP/UDP checksums for a packet.
func IsBroadcastOrMulticast ¶
IsBroadcastOrMulticast returns true if the IP (network byte order) is a broadcast, multicast, or APIPA address.
func Localhost127BE ¶
func Localhost127BE() uint32
Localhost127BE returns 127.0.0.1 in big-endian (network byte order).
func ParsePacket ¶
func ParsePacket(buf []byte) (*IPv4Header, *TCPHeader, bool)
ParsePacket attempts to parse buf as an IPv4+TCP packet. Returns the parsed headers and true on success, or nil, nil, false if the packet is not a valid IPv4 TCP packet.
func SetDLLPath ¶
func SetDLLPath(dllPath string)
SetDLLPath sets the path to WinDivert.dll. Must be called before Start(). The WinDivert64.sys driver file must be in the same directory as the DLL.
Types ¶
type Address ¶
type Address struct {
Timestamp int64 // 8 bytes: packet timestamp
Layer uint8 // 1 byte: layer
Event uint8 // 1 byte: event
Flags uint8 // 1 byte: bit flags (Sniffed:1, Outbound:1, Loopback:1, Impostor:1, IPv6:1, IPChecksum:1, TCPChecksum:1, UDPChecksum:1)
DataLen uint32 // 4 bytes: data length
// contains filtered or unexported fields
}
Address matches the WINDIVERT_ADDRESS structure (64 bytes). We access fields through methods using unsafe pointer arithmetic to match the C bit-field layout.
func Recv ¶
Recv receives a packet from a WinDivert handle. Returns the number of bytes read and the packet address.
func (*Address) SetOutbound ¶
SetOutbound sets or clears the outbound flag.
type ConnEntry ¶
type ConnEntry struct {
SrcPort uint16
SrcIP uint32 // network byte order
OrigDestIP uint32 // network byte order
OrigDestPort uint16
LastActivity time.Time
// contains filtered or unexported fields
}
ConnEntry represents a tracked NAT connection.
type ConnectionCallback ¶
type ConnectionCallback func(pid uint32, processName string, srcIP, dstIP uint32, srcPort, dstPort uint16, action Action)
ConnectionCallback is called when a new connection is intercepted.
type IPv4Header ¶
type IPv4Header struct {
// contains filtered or unexported fields
}
IPv4Header provides read/write access to IPv4 header fields in a raw packet buffer.
func (*IPv4Header) DstAddr ¶
func (h *IPv4Header) DstAddr() uint32
DstAddr returns the destination IP address as a 4-byte network-order uint32.
func (*IPv4Header) DstIP ¶
func (h *IPv4Header) DstIP() net.IP
DstIP returns the destination IP as net.IP.
func (*IPv4Header) HeaderLen ¶
func (h *IPv4Header) HeaderLen() int
HeaderLen returns the IPv4 header length in bytes.
func (*IPv4Header) IsLoopbackDst ¶
func (h *IPv4Header) IsLoopbackDst() bool
IsLoopbackDst returns true if the destination IP is 127.x.x.x.
func (*IPv4Header) IsLoopbackSrc ¶
func (h *IPv4Header) IsLoopbackSrc() bool
IsLoopbackSrc returns true if the source IP is 127.x.x.x.
func (*IPv4Header) Protocol ¶
func (h *IPv4Header) Protocol() uint8
Protocol returns the IP protocol number (6 = TCP, 17 = UDP).
func (*IPv4Header) SetDstAddr ¶
func (h *IPv4Header) SetDstAddr(ip uint32)
SetDstAddr sets the destination IP address (network byte order).
func (*IPv4Header) SetSrcAddr ¶
func (h *IPv4Header) SetSrcAddr(ip uint32)
SetSrcAddr sets the source IP address (network byte order).
func (*IPv4Header) SrcAddr ¶
func (h *IPv4Header) SrcAddr() uint32
SrcAddr returns the source IP address as a 4-byte network-order uint32.
func (*IPv4Header) SrcIP ¶
func (h *IPv4Header) SrcIP() net.IP
SrcIP returns the source IP as net.IP.
func (*IPv4Header) TotalLen ¶
func (h *IPv4Header) TotalLen() uint16
TotalLen returns the total packet length from the IPv4 header.
type Interceptor ¶
type Interceptor struct {
// contains filtered or unexported fields
}
Interceptor orchestrates the WinDivert-based per-process transparent proxy. It intercepts outbound TCP traffic from specified processes and redirects it through the local Relay server to the Echo HTTP proxy.
func NewInterceptor ¶
func NewInterceptor(echoAddr string) *Interceptor
NewInterceptor creates a new Windows transparent proxy interceptor. echoAddr is the address of the Echo proxy (e.g., "127.0.0.1:8899").
func (*Interceptor) AddRule ¶
func (i *Interceptor) AddRule(rule *ProcessRule) uint32
AddRule adds a process interception rule and returns its ID.
func (*Interceptor) DisableRule ¶
func (i *Interceptor) DisableRule(ruleID uint32) error
DisableRule disables a rule by ID.
func (*Interceptor) EnableRule ¶
func (i *Interceptor) EnableRule(ruleID uint32) error
EnableRule enables a rule by ID.
func (*Interceptor) RemoveRule ¶
func (i *Interceptor) RemoveRule(ruleID uint32) error
RemoveRule removes a rule by ID.
func (*Interceptor) SetConnectionCallback ¶
func (i *Interceptor) SetConnectionCallback(cb ConnectionCallback)
SetConnectionCallback sets an optional callback invoked when a new connection is intercepted and a rule decision is made.
func (*Interceptor) SetDNSViaProxy ¶
func (i *Interceptor) SetDNSViaProxy(enabled bool)
SetDNSViaProxy controls whether DNS (port 53) traffic is also proxied. By default, DNS traffic is allowed to pass through directly.
func (*Interceptor) Start ¶
func (i *Interceptor) Start() error
Start begins intercepting traffic. Requires administrator privileges.
func (*Interceptor) Stop ¶
func (i *Interceptor) Stop() error
Stop shuts down the interceptor and waits for all goroutines to finish.
type NATTable ¶
type NATTable struct {
// contains filtered or unexported fields
}
NATTable is a hash table tracking NAT-rewritten connections. It uses 256 buckets keyed by source port, with per-bucket locking.
func NewNATTable ¶
func NewNATTable() *NATTable
NewNATTable creates a new NAT connection tracking table.
func (*NATTable) Cleanup ¶
func (t *NATTable) Cleanup()
Cleanup removes stale entries older than connStaleTimeout. Should be called periodically from a background goroutine.
type ProcessResolver ¶
type ProcessResolver struct {
// contains filtered or unexported fields
}
ProcessResolver resolves connection source ports to process names.
func NewProcessResolver ¶
func NewProcessResolver() *ProcessResolver
NewProcessResolver creates a new process resolver.
func (*ProcessResolver) CleanupCache ¶
func (pr *ProcessResolver) CleanupCache()
CleanupCache removes expired entries from the PID cache.
func (*ProcessResolver) ClearCache ¶
func (pr *ProcessResolver) ClearCache()
ClearCache removes all entries from the PID cache.
func (*ProcessResolver) GetPIDFromConnection ¶
func (pr *ProcessResolver) GetPIDFromConnection(srcIP uint32, srcPort uint16) uint32
GetPIDFromConnection looks up the PID that owns the given TCP connection.
func (*ProcessResolver) GetProcessName ¶
func (pr *ProcessResolver) GetProcessName(pid uint32) (string, bool)
GetProcessName returns the full executable path for the given PID.
func (*ProcessResolver) IsSelf ¶
func (pr *ProcessResolver) IsSelf(pid uint32) bool
IsSelf returns true if the PID matches this process.
type ProcessRule ¶
type ProcessRule struct {
ID uint32
ProcessName string // "chrome.exe", "fire*", "*"
TargetHosts string // "*", "192.168.*.*", "10.0.0.1;172.16.0.0"
TargetPorts string // "*", "80;443", "8000-9000"
Action Action
Enabled bool
}
ProcessRule defines a rule for matching process traffic.
type Relay ¶
type Relay struct {
// contains filtered or unexported fields
}
Relay accepts transparent TCP connections on a local port, looks up the original destination via the NAT table, and forwards the traffic to the Echo HTTP proxy using standard HTTP proxy protocol (CONNECT or absolute URL).
type RuleManager ¶
type RuleManager struct {
// contains filtered or unexported fields
}
RuleManager manages an ordered list of process rules.
func (*RuleManager) AddRule ¶
func (rm *RuleManager) AddRule(rule *ProcessRule) uint32
AddRule adds a rule and returns its assigned ID.
func (*RuleManager) BuildPortFilter ¶
func (rm *RuleManager) BuildPortFilter() (string, bool)
BuildPortFilter returns a WinDivert filter expression covering all target ports from enabled rules. Returns ("", false) if any enabled rule uses wildcard ports ("*" or empty), meaning the filter cannot be narrowed. Rules must be added before calling Start() for port-specific filtering.
func (*RuleManager) DisableRule ¶
func (rm *RuleManager) DisableRule(id uint32) error
DisableRule disables a rule by ID.
func (*RuleManager) EnableRule ¶
func (rm *RuleManager) EnableRule(id uint32) error
EnableRule enables a rule by ID.
func (*RuleManager) HasActiveRules ¶
func (rm *RuleManager) HasActiveRules() bool
HasActiveRules returns true if there is at least one enabled rule.
func (*RuleManager) Match ¶
func (rm *RuleManager) Match(processPath string, destIP uint32, destPort uint16) Action
Match evaluates rules in order and returns the action for the given process/dest. processPath is the full path of the process executable. destIP is in network byte order, destPort is in host byte order.
func (*RuleManager) RemoveRule ¶
func (rm *RuleManager) RemoveRule(id uint32) error
RemoveRule removes a rule by ID.
type TCPHeader ¶
type TCPHeader struct {
// contains filtered or unexported fields
}
TCPHeader provides read/write access to TCP header fields in a raw packet buffer.
func (*TCPHeader) DataOffset ¶
DataOffset returns the TCP header length in bytes.
func (*TCPHeader) SetDstPort ¶
SetDstPort sets the TCP destination port.
func (*TCPHeader) SetSrcPort ¶
SetSrcPort sets the TCP source port.