| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- // SPDX-License-Identifier: MIT
- //
- // Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
- package conn
- import (
- "fmt"
- "net"
- "syscall"
- "github.com/cilium/ebpf"
- "github.com/cilium/ebpf/asm"
- )
- // UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it is
- // the max supported by a default configuration of macOS. Some platforms will
- // silently clamp the value to other maximums, such as linux clamping to
- // net.core.{r,w}mem_max (see _linux.go for additional implementation that works
- // around this limitation)
- const socketBufferSize = 7 << 20
- // controlFn is the callback function signature from net.ListenConfig.Control.
- // It is used to apply platform specific configuration to the socket prior to
- // bind.
- type controlFn func(network, address string, c syscall.RawConn) error
- // controlFns is a list of functions that are called from the listen config
- // that can apply socket options.
- var controlFns = []controlFn{}
- const SO_ATTACH_REUSEPORT_EBPF = 52
- //Create eBPF program that returns a hash to distribute packets
- func createReuseportProgram() (*ebpf.Program, error) {
- // This program uses the packet's hash and returns it modulo number of sockets
- // Simple version: just return a counter-based distribution
- //instructions := asm.Instructions{
- // // Load the skb->hash value (already computed by kernel)
- // asm.LoadMem(asm.R0, asm.R1, int16(unsafe.Offsetof(unix.XDPMd{}.RxQueueIndex)), asm.Word),
- // asm.Return(),
- //}
- //
- //// Alternative: simpler round-robin approach
- //// This returns the CPU number, effectively round-robin
- //instructions := asm.Instructions{
- // asm.Mov.Reg(asm.R0, asm.R1), // Move ctx to R0
- // asm.LoadMem(asm.R0, asm.R1, 0, asm.Word), // Load some field
- // asm.Return(),
- //}
- // Better: Use BPF helper to get random/hash value
- //instructions := asm.Instructions{
- // // Call get_prandom_u32() to get random value for distribution
- // asm.Mov.Imm(asm.R0, 0),
- // asm.Call.Label("get_prandom_u32"),
- // asm.Return(),
- //}
- //
- //prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
- // Type: ebpf.SocketFilter,
- // Instructions: instructions,
- // License: "GPL",
- //})
- //instructions := asm.Instructions{
- // // R1 contains pointer to skb
- // // Load skb->hash at offset 0x20 (may vary by kernel, but 0x20 is common)
- // asm.LoadMem(asm.R0, asm.R1, 0x20, asm.Word),
- //
- // // If hash is 0, use rxhash instead (fallback)
- // asm.JEq.Imm(asm.R0, 0, "use_rxhash"),
- // asm.Return().Sym("return"),
- //
- // // Fallback: load rxhash
- // asm.LoadMem(asm.R0, asm.R1, 0x24, asm.Word).Sym("use_rxhash"),
- // asm.Return(),
- //}
- //
- //prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
- // Type: ebpf.SkReuseport,
- // Instructions: instructions,
- // License: "GPL",
- //})
- //instructions := asm.Instructions{
- // // R1 = ctx (sk_reuseport_md)
- // // R2 = sk_reuseport map (we'll use NULL/0 for default behavior)
- // // R3 = key (select socket index)
- // // R4 = flags
- //
- // // Simple approach: use the hash field from sk_reuseport_md
- // // struct sk_reuseport_md { ... __u32 hash; ... } at offset 24
- // asm.Mov.Reg(asm.R6, asm.R1), // Save ctx
- //
- // // Load the hash value at offset 24
- // asm.LoadMem(asm.R2, asm.R6, 24, asm.Word),
- //
- // // Call bpf_sk_select_reuseport(ctx, map, key, flags)
- // asm.Mov.Reg(asm.R1, asm.R6), // ctx
- // asm.Mov.Imm(asm.R2, 0), // map (NULL = use default)
- // asm.Mov.Reg(asm.R3, asm.R2), // key = hash we loaded (in R2)
- // asm.Mov.Imm(asm.R4, 0), // flags
- // asm.Call.Label("sk_select_reuseport"),
- //
- // // Return 0
- // asm.Mov.Imm(asm.R0, 0),
- // asm.Return(),
- //}
- //
- //prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
- // Type: ebpf.SkReuseport,
- // Instructions: instructions,
- // License: "GPL",
- //})
- instructions := asm.Instructions{
- // R1 = ctx (sk_reuseport_md pointer)
- // Load hash from sk_reuseport_md at offset 24
- //asm.LoadMem(asm.R0, asm.R1, 20, asm.Word),
- // R1 = ctx (save it)
- asm.Mov.Reg(asm.R6, asm.R1),
- // Prepare string on stack: "BPF called!\n"
- // We need to build the format string on the stack
- asm.Mov.Reg(asm.R1, asm.R10), // R1 = frame pointer
- asm.Add.Imm(asm.R1, -16), // R1 = stack location for string
- // Write "BPF called!\n" to stack (we'll use a simpler version)
- // Store immediate 64-bit values
- asm.StoreImm(asm.R1, 0, 0x2066706220, asm.DWord), // "bpf "
- asm.StoreImm(asm.R1, 8, 0x0a21, asm.DWord), // "!\n"
- // Call bpf_trace_printk(fmt, fmt_size)
- // R1 already points to format string
- asm.Mov.Imm(asm.R2, 16), // R2 = format size
- asm.Call.Label("bpf_printk"),
- // Return 0 (send to socket 0 for testing)
- asm.Mov.Imm(asm.R0, 0),
- asm.Return(),
- //asm.Mov.Imm(asm.R0, 0),
- //// Just return the hash directly
- //// The kernel will automatically modulo by number of sockets
- //asm.Return(),
- }
- prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
- Type: ebpf.SkReuseport,
- Instructions: instructions,
- License: "GPL",
- })
- return prog, err
- }
- //func createReuseportProgram() (*ebpf.Program, error) {
- // // Try offset 20 (common in newer kernels)
- // instructions := asm.Instructions{
- // asm.LoadMem(asm.R0, asm.R1, 20, asm.Word),
- // asm.Return(),
- // }
- //
- // prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
- // Type: ebpf.SkReuseport,
- // Instructions: instructions,
- // License: "GPL",
- // })
- //
- // return prog, err
- //}
- func reusePortHax(fd uintptr) error {
- prog, err := createReuseportProgram()
- if err != nil {
- return fmt.Errorf("failed to create eBPF program: %w", err)
- }
- //defer prog.Close()
- sockErr := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, prog.FD())
- if sockErr != nil {
- return sockErr
- }
- return nil
- }
- var EvilFdZero uintptr
- // listenConfig returns a net.ListenConfig that applies the controlFns to the
- // socket prior to bind. This is used to apply socket buffer sizing and packet
- // information OOB configuration for sticky sockets.
- func listenConfig(q int) *net.ListenConfig {
- return &net.ListenConfig{
- Control: func(network, address string, c syscall.RawConn) error {
- for _, fn := range controlFns {
- if err := fn(network, address, c); err != nil {
- return err
- }
- }
- if q == 0 {
- c.Control(func(fd uintptr) {
- EvilFdZero = fd
- })
- // var e error
- // err := c.Control(func(fd uintptr) {
- // e = reusePortHax(fd)
- // })
- // if err != nil {
- // return err
- // }
- // if e != nil {
- // return e
- // }
- }
- return nil
- },
- }
- }
|