filter.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. package windivert
  2. import (
  3. "encoding/binary"
  4. "net/netip"
  5. E "github.com/sagernet/sing/common/exceptions"
  6. )
  7. // WINDIVERT_FILTER VM instruction layout (24 bytes, #pragma pack(1)):
  8. //
  9. // word 0 (LE): field:11 | test:5 | success:16
  10. // word 1 (LE): failure:16 | neg:1 | reserved:15
  11. // words 2..5: arg[4] (native-endian uint32 each)
  12. //
  13. // The driver walks this as a decision tree: evaluate the test at inst i;
  14. // on success jump to success; on failure jump to failure. Continuations
  15. // 0x7FFE and 0x7FFF are ACCEPT and REJECT terminals.
  16. const (
  17. filterInstBytes = 24
  18. filterMaxInsts = 256
  19. fieldZero = 0
  20. fieldOutbound = 2
  21. fieldIP = 5
  22. fieldIPv6 = 6
  23. fieldTCP = 8
  24. fieldIPSrcAddr = 21
  25. fieldIPDstAddr = 22
  26. fieldIPv6SrcAddr = 28
  27. fieldIPv6DstAddr = 29
  28. fieldTCPSrcPort = 38
  29. fieldTCPDstPort = 39
  30. testEQ = 0
  31. resultAccept uint16 = 0x7FFE
  32. resultReject uint16 = 0x7FFF
  33. )
  34. // Filter flags passed to IOCTL_WINDIVERT_STARTUP alongside the compiled
  35. // filter. These tell the driver what *kinds* of packets the filter might
  36. // match, used as a kernel-side fast-reject.
  37. const (
  38. filterFlagOutbound uint64 = 0x0020
  39. filterFlagIP uint64 = 0x0040
  40. filterFlagIPv6 uint64 = 0x0080
  41. )
  42. type filterInst struct {
  43. field uint16 // 11 bits used
  44. test uint8 // 5 bits used
  45. success uint16
  46. failure uint16
  47. neg bool
  48. arg [4]uint32
  49. }
  50. // Filter is a typed specification of packets to capture. It replaces
  51. // WinDivert's filter string language.
  52. //
  53. // Zero value = "reject all" (match nothing), suitable for send-only handles.
  54. type Filter struct {
  55. insts []filterInst
  56. flags uint64 // filter flags for STARTUP ioctl
  57. }
  58. // reject returns a filter that matches no packet. The empty insts slice
  59. // is encoded as a single rejecting instruction by encode().
  60. func reject() *Filter {
  61. return &Filter{}
  62. }
  63. // OutboundTCP returns a filter matching outbound TCP packets on the given
  64. // 5-tuple. Both addresses must share an address family (IPv4 or IPv6).
  65. func OutboundTCP(src, dst netip.AddrPort) (*Filter, error) {
  66. if !src.IsValid() || !dst.IsValid() {
  67. return nil, E.New("windivert: filter: invalid address port")
  68. }
  69. if src.Addr().Is4() != dst.Addr().Is4() {
  70. return nil, E.New("windivert: filter: mixed IPv4/IPv6")
  71. }
  72. f := &Filter{
  73. flags: filterFlagOutbound,
  74. }
  75. // Insts chain as AND: each test's failure = REJECT, success = next inst.
  76. // The final inst's success = ACCEPT.
  77. f.add(fieldOutbound, testEQ, argUint32(1))
  78. if src.Addr().Is4() {
  79. f.flags |= filterFlagIP
  80. f.add(fieldIP, testEQ, argUint32(1))
  81. f.add(fieldTCP, testEQ, argUint32(1))
  82. f.add(fieldIPSrcAddr, testEQ, argIPv4(src.Addr()))
  83. f.add(fieldIPDstAddr, testEQ, argIPv4(dst.Addr()))
  84. } else {
  85. f.flags |= filterFlagIPv6
  86. f.add(fieldIPv6, testEQ, argUint32(1))
  87. f.add(fieldTCP, testEQ, argUint32(1))
  88. f.add(fieldIPv6SrcAddr, testEQ, argIPv6(src.Addr()))
  89. f.add(fieldIPv6DstAddr, testEQ, argIPv6(dst.Addr()))
  90. }
  91. f.add(fieldTCPSrcPort, testEQ, argUint32(uint32(src.Port())))
  92. f.add(fieldTCPDstPort, testEQ, argUint32(uint32(dst.Port())))
  93. return f, nil
  94. }
  95. func (f *Filter) add(field uint16, test uint8, arg [4]uint32) {
  96. f.insts = append(f.insts, filterInst{field: field, test: test, arg: arg})
  97. }
  98. func argUint32(v uint32) [4]uint32 { return [4]uint32{v, 0, 0, 0} }
  99. // argIPv4 encodes an IPv4 address for IP_SRCADDR/IP_DSTADDR. The driver
  100. // compares against an IPv4-mapped-IPv6 form: {host_order_u32, 0x0000FFFF,
  101. // 0, 0} (see sys/windivert.c windivert_get_ipv4_addr and the IPv4_SRCADDR
  102. // val-word construction). Omitting the 0x0000FFFF marker causes the EQ
  103. // test to fail for every packet.
  104. func argIPv4(addr netip.Addr) [4]uint32 {
  105. b := addr.As4()
  106. return [4]uint32{binary.BigEndian.Uint32(b[:]), 0x0000FFFF, 0, 0}
  107. }
  108. // argIPv6 encodes an IPv6 address for IPV6_SRCADDR/IPV6_DSTADDR. The
  109. // driver stores the address as four host-order uint32s in REVERSED word
  110. // order: val[0]=low (bytes 12..15), val[3]=high (bytes 0..3). See
  111. // sys/windivert.c windivert_outbound_network_v6_classify val-word
  112. // construction.
  113. func argIPv6(addr netip.Addr) [4]uint32 {
  114. b := addr.As16()
  115. return [4]uint32{
  116. binary.BigEndian.Uint32(b[12:16]),
  117. binary.BigEndian.Uint32(b[8:12]),
  118. binary.BigEndian.Uint32(b[4:8]),
  119. binary.BigEndian.Uint32(b[0:4]),
  120. }
  121. }
  122. // encode serializes the Filter to the on-wire WINDIVERT_FILTER[] format
  123. // plus the filter_flags for STARTUP ioctl.
  124. func (f *Filter) encode() ([]byte, uint64, error) {
  125. if len(f.insts) == 0 {
  126. // "Reject all" — one instruction, ZERO == 0 is always true, but we
  127. // invert by setting both success and failure to REJECT.
  128. return encodeInst(filterInst{
  129. field: fieldZero,
  130. test: testEQ,
  131. success: resultReject,
  132. failure: resultReject,
  133. }), 0, nil
  134. }
  135. if len(f.insts) > filterMaxInsts-1 {
  136. return nil, 0, E.New("windivert: filter too long")
  137. }
  138. buf := make([]byte, 0, filterInstBytes*len(f.insts))
  139. for i, inst := range f.insts {
  140. if i == len(f.insts)-1 {
  141. inst.success = resultAccept
  142. } else {
  143. inst.success = uint16(i + 1)
  144. }
  145. inst.failure = resultReject
  146. buf = append(buf, encodeInst(inst)...)
  147. }
  148. return buf, f.flags, nil
  149. }
  150. func encodeInst(inst filterInst) []byte {
  151. out := make([]byte, filterInstBytes)
  152. word0 := uint32(inst.field&0x7FF) | uint32(inst.test&0x1F)<<11 |
  153. uint32(inst.success)<<16
  154. word1 := uint32(inst.failure)
  155. if inst.neg {
  156. word1 |= 1 << 16
  157. }
  158. binary.LittleEndian.PutUint32(out[0:4], word0)
  159. binary.LittleEndian.PutUint32(out[4:8], word1)
  160. binary.LittleEndian.PutUint32(out[8:12], inst.arg[0])
  161. binary.LittleEndian.PutUint32(out[12:16], inst.arg[1])
  162. binary.LittleEndian.PutUint32(out[16:20], inst.arg[2])
  163. binary.LittleEndian.PutUint32(out[20:24], inst.arg[3])
  164. return out
  165. }