controlfns.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // SPDX-License-Identifier: MIT
  2. //
  3. // Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
  4. package conn
  5. import (
  6. "fmt"
  7. "net"
  8. "syscall"
  9. "github.com/cilium/ebpf"
  10. "github.com/cilium/ebpf/asm"
  11. )
  12. // UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it is
  13. // the max supported by a default configuration of macOS. Some platforms will
  14. // silently clamp the value to other maximums, such as linux clamping to
  15. // net.core.{r,w}mem_max (see _linux.go for additional implementation that works
  16. // around this limitation)
  17. const socketBufferSize = 7 << 20
  18. // controlFn is the callback function signature from net.ListenConfig.Control.
  19. // It is used to apply platform specific configuration to the socket prior to
  20. // bind.
  21. type controlFn func(network, address string, c syscall.RawConn) error
  22. // controlFns is a list of functions that are called from the listen config
  23. // that can apply socket options.
  24. var controlFns = []controlFn{}
  25. const SO_ATTACH_REUSEPORT_EBPF = 52
  26. //Create eBPF program that returns a hash to distribute packets
  27. func createReuseportProgram() (*ebpf.Program, error) {
  28. // This program uses the packet's hash and returns it modulo number of sockets
  29. // Simple version: just return a counter-based distribution
  30. //instructions := asm.Instructions{
  31. // // Load the skb->hash value (already computed by kernel)
  32. // asm.LoadMem(asm.R0, asm.R1, int16(unsafe.Offsetof(unix.XDPMd{}.RxQueueIndex)), asm.Word),
  33. // asm.Return(),
  34. //}
  35. //
  36. //// Alternative: simpler round-robin approach
  37. //// This returns the CPU number, effectively round-robin
  38. //instructions := asm.Instructions{
  39. // asm.Mov.Reg(asm.R0, asm.R1), // Move ctx to R0
  40. // asm.LoadMem(asm.R0, asm.R1, 0, asm.Word), // Load some field
  41. // asm.Return(),
  42. //}
  43. // Better: Use BPF helper to get random/hash value
  44. //instructions := asm.Instructions{
  45. // // Call get_prandom_u32() to get random value for distribution
  46. // asm.Mov.Imm(asm.R0, 0),
  47. // asm.Call.Label("get_prandom_u32"),
  48. // asm.Return(),
  49. //}
  50. //
  51. //prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
  52. // Type: ebpf.SocketFilter,
  53. // Instructions: instructions,
  54. // License: "GPL",
  55. //})
  56. //instructions := asm.Instructions{
  57. // // R1 contains pointer to skb
  58. // // Load skb->hash at offset 0x20 (may vary by kernel, but 0x20 is common)
  59. // asm.LoadMem(asm.R0, asm.R1, 0x20, asm.Word),
  60. //
  61. // // If hash is 0, use rxhash instead (fallback)
  62. // asm.JEq.Imm(asm.R0, 0, "use_rxhash"),
  63. // asm.Return().Sym("return"),
  64. //
  65. // // Fallback: load rxhash
  66. // asm.LoadMem(asm.R0, asm.R1, 0x24, asm.Word).Sym("use_rxhash"),
  67. // asm.Return(),
  68. //}
  69. //
  70. //prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
  71. // Type: ebpf.SkReuseport,
  72. // Instructions: instructions,
  73. // License: "GPL",
  74. //})
  75. //instructions := asm.Instructions{
  76. // // R1 = ctx (sk_reuseport_md)
  77. // // R2 = sk_reuseport map (we'll use NULL/0 for default behavior)
  78. // // R3 = key (select socket index)
  79. // // R4 = flags
  80. //
  81. // // Simple approach: use the hash field from sk_reuseport_md
  82. // // struct sk_reuseport_md { ... __u32 hash; ... } at offset 24
  83. // asm.Mov.Reg(asm.R6, asm.R1), // Save ctx
  84. //
  85. // // Load the hash value at offset 24
  86. // asm.LoadMem(asm.R2, asm.R6, 24, asm.Word),
  87. //
  88. // // Call bpf_sk_select_reuseport(ctx, map, key, flags)
  89. // asm.Mov.Reg(asm.R1, asm.R6), // ctx
  90. // asm.Mov.Imm(asm.R2, 0), // map (NULL = use default)
  91. // asm.Mov.Reg(asm.R3, asm.R2), // key = hash we loaded (in R2)
  92. // asm.Mov.Imm(asm.R4, 0), // flags
  93. // asm.Call.Label("sk_select_reuseport"),
  94. //
  95. // // Return 0
  96. // asm.Mov.Imm(asm.R0, 0),
  97. // asm.Return(),
  98. //}
  99. //
  100. //prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
  101. // Type: ebpf.SkReuseport,
  102. // Instructions: instructions,
  103. // License: "GPL",
  104. //})
  105. instructions := asm.Instructions{
  106. // R1 = ctx (sk_reuseport_md pointer)
  107. // Load hash from sk_reuseport_md at offset 24
  108. //asm.LoadMem(asm.R0, asm.R1, 20, asm.Word),
  109. // R1 = ctx (save it)
  110. asm.Mov.Reg(asm.R6, asm.R1),
  111. // Prepare string on stack: "BPF called!\n"
  112. // We need to build the format string on the stack
  113. asm.Mov.Reg(asm.R1, asm.R10), // R1 = frame pointer
  114. asm.Add.Imm(asm.R1, -16), // R1 = stack location for string
  115. // Write "BPF called!\n" to stack (we'll use a simpler version)
  116. // Store immediate 64-bit values
  117. asm.StoreImm(asm.R1, 0, 0x2066706220, asm.DWord), // "bpf "
  118. asm.StoreImm(asm.R1, 8, 0x0a21, asm.DWord), // "!\n"
  119. // Call bpf_trace_printk(fmt, fmt_size)
  120. // R1 already points to format string
  121. asm.Mov.Imm(asm.R2, 16), // R2 = format size
  122. asm.Call.Label("bpf_printk"),
  123. // Return 0 (send to socket 0 for testing)
  124. asm.Mov.Imm(asm.R0, 0),
  125. asm.Return(),
  126. //asm.Mov.Imm(asm.R0, 0),
  127. //// Just return the hash directly
  128. //// The kernel will automatically modulo by number of sockets
  129. //asm.Return(),
  130. }
  131. prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
  132. Type: ebpf.SkReuseport,
  133. Instructions: instructions,
  134. License: "GPL",
  135. })
  136. return prog, err
  137. }
  138. //func createReuseportProgram() (*ebpf.Program, error) {
  139. // // Try offset 20 (common in newer kernels)
  140. // instructions := asm.Instructions{
  141. // asm.LoadMem(asm.R0, asm.R1, 20, asm.Word),
  142. // asm.Return(),
  143. // }
  144. //
  145. // prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
  146. // Type: ebpf.SkReuseport,
  147. // Instructions: instructions,
  148. // License: "GPL",
  149. // })
  150. //
  151. // return prog, err
  152. //}
  153. func reusePortHax(fd uintptr) error {
  154. prog, err := createReuseportProgram()
  155. if err != nil {
  156. return fmt.Errorf("failed to create eBPF program: %w", err)
  157. }
  158. //defer prog.Close()
  159. sockErr := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, prog.FD())
  160. if sockErr != nil {
  161. return sockErr
  162. }
  163. return nil
  164. }
  165. var EvilFdZero uintptr
  166. // listenConfig returns a net.ListenConfig that applies the controlFns to the
  167. // socket prior to bind. This is used to apply socket buffer sizing and packet
  168. // information OOB configuration for sticky sockets.
  169. func listenConfig(q int) *net.ListenConfig {
  170. return &net.ListenConfig{
  171. Control: func(network, address string, c syscall.RawConn) error {
  172. for _, fn := range controlFns {
  173. if err := fn(network, address, c); err != nil {
  174. return err
  175. }
  176. }
  177. if q == 0 {
  178. c.Control(func(fd uintptr) {
  179. EvilFdZero = fd
  180. })
  181. // var e error
  182. // err := c.Control(func(fd uintptr) {
  183. // e = reusePortHax(fd)
  184. // })
  185. // if err != nil {
  186. // return err
  187. // }
  188. // if e != nil {
  189. // return e
  190. // }
  191. }
  192. return nil
  193. },
  194. }
  195. }