Add windows bridge

This commit is contained in:
世界
2026-07-08 17:38:33 +08:00
parent 9fc7621596
commit c85b64ecad
17 changed files with 1519 additions and 213 deletions
+89 -3
View File
@@ -21,27 +21,39 @@ const (
filterMaxInsts = 256
fieldZero = 0
fieldInbound = 1
fieldOutbound = 2
fieldIP = 5
fieldIPv6 = 6
fieldICMP = 7
fieldTCP = 8
fieldUDP = 9
fieldICMPv6 = 10
fieldIPSrcAddr = 21
fieldIPDstAddr = 22
fieldIPv6SrcAddr = 28
fieldIPv6DstAddr = 29
fieldICMPType = 30
fieldICMPv6Type = 34
fieldTCPSrcPort = 38
fieldTCPDstPort = 39
fieldUDPSrcPort = 53
fieldUDPDstPort = 54
testEQ = 0
testEQ = 0
testLEQ = 3
testGEQ = 5
resultAccept uint16 = 0x7FFE
resultReject uint16 = 0x7FFF
)
// Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled
// filter. These tell the driver what *kinds* of packets the filter might
// match, used as a kernel-side fast-reject.
// filter. The driver installs WFP callouts only for the directions and
// address families named here (windivert_install_callouts), so a filter
// missing its direction flag never sees a packet.
const (
filterFlagInbound uint64 = 0x0010
filterFlagOutbound uint64 = 0x0020
filterFlagIP uint64 = 0x0040
filterFlagIPv6 uint64 = 0x0080
@@ -104,6 +116,80 @@ func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) {
return f, nil
}
func inboundTo(destination netip.Addr) (*Filter, error) {
if !destination.IsValid() {
return nil, E.New("windivert: filter: invalid address")
}
f := &Filter{
flags: filterFlagInbound,
}
f.add(fieldInbound, testEQ, argUint32(1))
if destination.Is4() {
f.flags |= filterFlagIP
f.add(fieldIP, testEQ, argUint32(1))
f.add(fieldIPDstAddr, testEQ, argIPv4(destination))
} else {
f.flags |= filterFlagIPv6
f.add(fieldIPv6, testEQ, argUint32(1))
f.add(fieldIPv6DstAddr, testEQ, argIPv6(destination))
}
return f, nil
}
func InboundTCPPortRange(destination netip.Addr, portLow, portHigh uint16) (*Filter, error) {
f, err := inboundTo(destination)
if err != nil {
return nil, err
}
f.add(fieldTCP, testEQ, argUint32(1))
f.add(fieldTCPDstPort, testGEQ, argUint32(uint32(portLow)))
f.add(fieldTCPDstPort, testLEQ, argUint32(uint32(portHigh)))
return f, nil
}
func InboundUDPPortRange(destination netip.Addr, portLow, portHigh uint16) (*Filter, error) {
f, err := inboundTo(destination)
if err != nil {
return nil, err
}
f.add(fieldUDP, testEQ, argUint32(1))
f.add(fieldUDPDstPort, testGEQ, argUint32(uint32(portLow)))
f.add(fieldUDPDstPort, testLEQ, argUint32(uint32(portHigh)))
return f, nil
}
func InboundICMPEchoReply(destination netip.Addr) (*Filter, error) {
f, err := inboundTo(destination)
if err != nil {
return nil, err
}
if destination.Is4() {
f.add(fieldICMP, testEQ, argUint32(1))
f.add(fieldICMPType, testEQ, argUint32(0))
} else {
f.add(fieldICMPv6, testEQ, argUint32(1))
f.add(fieldICMPv6Type, testEQ, argUint32(129))
}
return f, nil
}
func InboundICMPError(destination netip.Addr) (*Filter, error) {
f, err := inboundTo(destination)
if err != nil {
return nil, err
}
if destination.Is4() {
f.add(fieldICMP, testEQ, argUint32(1))
f.add(fieldICMPType, testGEQ, argUint32(3))
f.add(fieldICMPType, testLEQ, argUint32(12))
} else {
f.add(fieldICMPv6, testEQ, argUint32(1))
f.add(fieldICMPv6Type, testGEQ, argUint32(1))
f.add(fieldICMPv6Type, testLEQ, argUint32(4))
}
return f, nil
}
func (f *Filter) add(field uint16, test uint8, arg [4]uint32) {
f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg})
}
+75 -11
View File
@@ -26,11 +26,14 @@ import (
// because Go's escape analysis does not see the pointer through the
// unsafe.Pointer → uintptr → bytes conversion.
type Handle struct {
device windows.Handle
event windows.Handle
closing sync.Once
closeErr error
addr Address
device windows.Handle
event windows.Handle
closing sync.Once
closeErr error
addr Address
recvAddrs []Address
recvAddrsLen uint32
sendAddrs []Address
}
// Filter may be nil for "reject all", suitable for send-only handles.
@@ -169,10 +172,60 @@ func (h *Handle) Recv(buf []byte) (int, Address, error) {
return int(n), h.addr, nil
}
// BatchMax is WINDIVERT_BATCH_MAX: the driver caps both directions at 255
// packets per ioctl.
const BatchMax = 255
const addressSize = uint32(unsafe.Sizeof(Address{}))
// RecvBatch receives up to BatchMax packets in one ioctl. The driver packs
// packets back-to-back into buf with no padding and copies exactly each
// packet's IP total length, so boundaries are recovered by walking the IP
// length fields. It returns as soon as at least one packet is available;
// it never waits to fill the batch. The returned Address slice is owned by
// the Handle and is overwritten by the next RecvBatch.
func (h *Handle) RecvBatch(buf []byte) (int, []Address, error) {
if len(buf) < MTUMax {
return 0, nil, E.New("windivert: recv batch: buffer smaller than MTUMax")
}
if h.recvAddrs == nil {
h.recvAddrs = make([]Address, BatchMax)
}
h.recvAddrsLen = uint32(len(h.recvAddrs)) * addressSize
in := buildIoctlRecvBatch(&h.recvAddrs[0], &h.recvAddrsLen)
n, err := doIoctl(h.device, ioctlRecv, in[:], buf, h.event)
runtime.KeepAlive(h)
if err != nil {
return 0, nil, err
}
return int(n), h.recvAddrs[:h.recvAddrsLen/addressSize], nil
}
// SendBatch injects the packets packed back-to-back in buf, one Address per
// packet. The driver recovers packet boundaries from the IP total-length
// fields and rejects the whole batch if they do not add up to len(buf).
func (h *Handle) SendBatch(buf []byte, addrs []Address) (int, error) {
if len(addrs) == 0 || len(addrs) > BatchMax {
return 0, E.New("windivert: send batch: invalid packet count ", len(addrs))
}
if len(buf) == 0 {
return 0, E.New("windivert: send batch: empty buffer")
}
if h.sendAddrs == nil {
h.sendAddrs = make([]Address, BatchMax)
}
copy(h.sendAddrs, addrs)
in := buildIoctlSend(&h.sendAddrs[0], uint32(len(addrs))*addressSize)
n, err := doIoctl(h.device, ioctlSend, in[:], buf, h.event)
runtime.KeepAlive(h)
if err != nil {
return 0, err
}
return int(n), nil
}
// The address's Outbound flag controls whether the packet is sent toward
// the wire (outbound=true) or delivered up the stack (outbound=false).
// IfIdx and SubIfIdx can stay zero — the driver uses the routing table
// when IfIdx=0.
func (h *Handle) Send(packet []byte, addr *Address) (int, error) {
if len(packet) == 0 {
return 0, E.New("windivert: send: empty packet")
@@ -181,7 +234,7 @@ func (h *Handle) Send(packet []byte, addr *Address) (int, error) {
return 0, E.New("windivert: send: nil address")
}
h.addr = *addr
in := buildIoctlSend(&h.addr)
in := buildIoctlSend(&h.addr, addressSize)
n, err := doIoctl(h.device, ioctlSend, in[:], packet, h.event)
runtime.KeepAlive(h)
if err != nil {
@@ -316,9 +369,20 @@ func buildIoctlRecv(addr *Address) [ioctlSize]byte {
return buf
}
func buildIoctlSend(addr *Address) [ioctlSize]byte {
// buildIoctlRecvBatch additionally passes addr_len_ptr, a pointer to the
// Address array capacity in bytes; the driver overwrites it with the bytes
// actually written (packet count × 80). Caller must keep both pointees
// alive via runtime.KeepAlive.
func buildIoctlRecvBatch(addrs *Address, addrsLen *uint32) [ioctlSize]byte {
var buf [ioctlSize]byte
binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addr))))
binary.LittleEndian.PutUint64(buf[8:16], uint64(unsafe.Sizeof(Address{})))
binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addrs))))
binary.LittleEndian.PutUint64(buf[8:16], uint64(uintptr(unsafe.Pointer(addrsLen))))
return buf
}
func buildIoctlSend(addrs *Address, addrsLen uint32) [ioctlSize]byte {
var buf [ioctlSize]byte
binary.LittleEndian.PutUint64(buf[0:8], uint64(uintptr(unsafe.Pointer(addrs))))
binary.LittleEndian.PutUint64(buf[8:16], uint64(addrsLen))
return buf
}
+1 -1
View File
@@ -72,7 +72,7 @@ func TestBuildIoctlRecvEmbedsAddressPointer(t *testing.T) {
func TestBuildIoctlSendEmbedsAddressPointerAndSize(t *testing.T) {
t.Parallel()
addr := &Address{}
buf := buildIoctlSend(addr)
buf := buildIoctlSend(addr, addressSize)
require.Equal(t, uint64(uintptr(unsafe.Pointer(addr))),
binary.LittleEndian.Uint64(buf[0:8]))
require.Equal(t, uint64(unsafe.Sizeof(Address{})),
+18
View File
@@ -55,9 +55,11 @@ var _ [80]byte = [unsafe.Sizeof(Address{})]byte{}
// Bit positions inside the Address's packed flags word.
const (
addrBitOutbound = 17
addrBitIPv6 = 20
addrBitIPChecksum = 21
addrBitTCPChecksum = 22
addrBitUDPChecksum = 23
)
func getFlagBit(bits uint32, pos uint) bool { return bits&(1<<pos) != 0 }
@@ -69,6 +71,18 @@ func setFlagBit(bits uint32, pos uint, v bool) uint32 {
}
func (a *Address) IPv6() bool { return getFlagBit(a.bits, addrBitIPv6) }
// SetIPv6 declares the address family of a packet built for injection. The
// driver reads it to select the IPv6 network layer; a received address
// already carries it, but a from-scratch injection address must set it.
func (a *Address) SetIPv6(v bool) {
a.bits = setFlagBit(a.bits, addrBitIPv6, v)
}
func (a *Address) SetOutbound(v bool) {
a.bits = setFlagBit(a.bits, addrBitOutbound, v)
}
func (a *Address) SetIPChecksum(v bool) {
a.bits = setFlagBit(a.bits, addrBitIPChecksum, v)
}
@@ -76,3 +90,7 @@ func (a *Address) SetIPChecksum(v bool) {
func (a *Address) SetTCPChecksum(v bool) {
a.bits = setFlagBit(a.bits, addrBitTCPChecksum, v)
}
func (a *Address) SetUDPChecksum(v bool) {
a.bits = setFlagBit(a.bits, addrBitUDPChecksum, v)
}