diff --git a/conn/bind_std.go b/conn/bind_std.go index 249f53d..a1d4d9e 100644 --- a/conn/bind_std.go +++ b/conn/bind_std.go @@ -16,6 +16,9 @@ import ( "sync" "syscall" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" + M "github.com/sagernet/sing/common/metadata" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) @@ -31,6 +34,9 @@ var ( // methods for sending and receiving multiple datagrams per-syscall. See the // proposal in https://github.com/golang/go/issues/45886#issuecomment-1218301564. type StdNetBind struct { + externalControl control.Func + reservedForEndpoint map[netip.AddrPort][3]uint8 + mu sync.Mutex // protects all fields except as specified ipv4 *net.UDPConn ipv6 *net.UDPConn @@ -49,8 +55,11 @@ type StdNetBind struct { blackhole6 bool } -func NewStdNetBind() Bind { +func NewStdNetBind(externalControl control.Func) Bind { return &StdNetBind{ + externalControl: externalControl, + reservedForEndpoint: make(map[netip.AddrPort][3]uint8), + udpAddrPool: sync.Pool{ New: func() any { return &net.UDPAddr{ @@ -118,8 +127,29 @@ func (e *StdNetEndpoint) DstToString() string { return e.AddrPort.String() } -func listenNet(network string, port int) (*net.UDPConn, int, error) { - conn, err := listenConfig().ListenPacket(context.Background(), network, ":"+strconv.Itoa(port)) +func listenNet(externalControl control.Func, network string, port int) (*net.UDPConn, int, error) { + var listenerAddr string + if network == "udp6" { + listenerAddr = "[::]:" + strconv.Itoa(port) + } else { + listenerAddr = ":" + strconv.Itoa(port) + } + + var listener net.ListenConfig + listener.Control = func(network, address string, conn syscall.RawConn) error { + for _, wgControlFn := range controlFns { + err := wgControlFn(network, address, conn) + if err != nil { + return err + } + } + if externalControl != nil { + return externalControl(network, address, conn) + } else { + return nil + } + } + conn, err := listener.ListenPacket(context.Background(), network, listenerAddr) if err != nil { return nil, 0, err } @@ -160,13 +190,13 @@ again: var v4pc *ipv4.PacketConn var v6pc *ipv6.PacketConn - v4conn, port, err = listenNet("udp4", port) + v4conn, port, err = listenNet(s.externalControl, "udp4", port) if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) { return nil, 0, err } // Listen on the same port as we're using for ipv4. - v6conn, port, err = listenNet("udp6", port) + v6conn, port, err = listenNet(s.externalControl, "udp6", port) if uport == 0 && errors.Is(err, errEADDRINUSE) && tries < 100 { v4conn.Close() tries++ @@ -270,8 +300,10 @@ func (s *StdNetBind) receiveIP( if sizes[i] == 0 { continue } - addrPort := msg.Addr.(*net.UDPAddr).AddrPort() - ep := &StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation + if msg.N > 3 { + common.ClearArray(bufs[i][1:4]) + } + ep := &StdNetEndpoint{AddrPort: M.AddrPortFromNet(msg.Addr)} // TODO: remove allocation getSrcFromControl(msg.OOB[:msg.NN], ep) eps[i] = ep } @@ -380,6 +412,14 @@ func (s *StdNetBind) Send(bufs [][]byte, endpoint Endpoint, offset int) error { retried bool err error ) + for _, buf := range bufs { + if len(buf) > 3 { + reserved, loaded := s.reservedForEndpoint[endpoint.(*StdNetEndpoint).AddrPort] + if loaded { + copy(buf[1:4], reserved[:]) + } + } + } retry: if offload { n := coalesceMessages(ua, endpoint.(*StdNetEndpoint), bufs, offset, *msgs, setGSOSize) @@ -410,6 +450,10 @@ retry: return err } +func (s *StdNetBind) SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte) { + s.reservedForEndpoint[destination] = reserved +} + func (s *StdNetBind) send(conn *net.UDPConn, pc batchWriter, msgs []ipv6.Message) error { var ( n int diff --git a/conn/bind_windows.go b/conn/bind_windows.go index 81a820b..51c0974 100644 --- a/conn/bind_windows.go +++ b/conn/bind_windows.go @@ -15,6 +15,10 @@ import ( "sync/atomic" "unsafe" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/wireguard-go/conn/winrio" "golang.org/x/sys/windows" ) @@ -71,18 +75,26 @@ type afWinRingBind struct { // WinRingBind uses Windows registered I/O for fast ring buffered networking. type WinRingBind struct { + externalControl control.Func + reservedForEndpoint map[WinRingEndpoint][3]uint8 + v4, v6 afWinRingBind mu sync.RWMutex isOpen atomic.Uint32 // 0, 1, or 2 } -func NewDefaultBind() Bind { return NewWinRingBind() } +func NewDefaultBind(externalControl control.Func) Bind { + return NewWinRingBind(externalControl) +} -func NewWinRingBind() Bind { +func NewWinRingBind(externalControl control.Func) Bind { if !winrio.Initialize() { - return NewStdNetBind() + return NewStdNetBind(externalControl) + } + return &WinRingBind{ + externalControl: externalControl, + reservedForEndpoint: make(map[WinRingEndpoint][3]uint8), } - return new(WinRingBind) } type WinRingEndpoint struct { @@ -238,7 +250,7 @@ func (ring *ringBuffer) Open() error { return nil } -func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sockaddr, error) { +func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr, externalControl control.Func) (windows.Sockaddr, error) { var err error bind.sock, err = winrio.Socket(family, windows.SOCK_DGRAM, windows.IPPROTO_UDP) if err != nil { @@ -256,6 +268,19 @@ func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sock if err != nil { return nil, err } + var network string + switch family { + case windows.AF_INET: + network = "udp4" + case windows.AF_INET6: + network = "udp6" + } + if externalControl != nil { + err = externalControl(network, M.AddrPortFromSockaddr(sa).String(), &fakeRawConn{bind.sock}) + if err != nil { + return nil, err + } + } err = windows.Bind(bind.sock, sa) if err != nil { return nil, err @@ -267,6 +292,23 @@ func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sock return sa, nil } +type fakeRawConn struct { + socket windows.Handle +} + +func (c *fakeRawConn) Control(f func(fd uintptr)) error { + f(uintptr(c.socket)) + return nil +} + +func (c *fakeRawConn) Read(f func(fd uintptr) (done bool)) error { + panic("not implemented") +} + +func (c *fakeRawConn) Write(f func(fd uintptr) (done bool)) error { + panic("not implemented") +} + func (bind *WinRingBind) Open(port uint16) (recvFns []ReceiveFunc, selectedPort uint16, err error) { bind.mu.Lock() defer bind.mu.Unlock() @@ -279,11 +321,11 @@ func (bind *WinRingBind) Open(port uint16) (recvFns []ReceiveFunc, selectedPort return nil, 0, ErrBindAlreadyOpen } var sa windows.Sockaddr - sa, err = bind.v4.Open(windows.AF_INET, &windows.SockaddrInet4{Port: int(port)}) + sa, err = bind.v4.Open(windows.AF_INET, &windows.SockaddrInet4{Port: int(port)}, bind.externalControl) if err != nil { return nil, 0, err } - sa, err = bind.v6.Open(windows.AF_INET6, &windows.SockaddrInet6{Port: sa.(*windows.SockaddrInet4).Port}) + sa, err = bind.v6.Open(windows.AF_INET6, &windows.SockaddrInet6{Port: sa.(*windows.SockaddrInet4).Port}, bind.externalControl) if err != nil { return nil, 0, err } @@ -419,6 +461,9 @@ func (bind *WinRingBind) receiveIPv4(bufs [][]byte, sizes []int, eps []Endpoint) bind.mu.RLock() defer bind.mu.RUnlock() n, ep, err := bind.v4.Receive(bufs[0], &bind.isOpen) + if n > 3 { + common.ClearArray(bufs[0][1:4]) + } sizes[0] = n eps[0] = ep return 1, err @@ -428,6 +473,9 @@ func (bind *WinRingBind) receiveIPv6(bufs [][]byte, sizes []int, eps []Endpoint) bind.mu.RLock() defer bind.mu.RUnlock() n, ep, err := bind.v6.Receive(bufs[0], &bind.isOpen) + if n > 3 { + common.ClearArray(bufs[0][1:4]) + } sizes[0] = n eps[0] = ep return 1, err @@ -494,6 +542,12 @@ func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint, offset int) erro defer bind.mu.RUnlock() for _, buf := range bufs { buf = buf[offset:] + if len(buf) > 3 { + reserved, loaded := bind.reservedForEndpoint[*endpoint.(*WinRingEndpoint)] + if loaded { + copy(buf[1:4], reserved[:]) + } + } switch nend.family { case windows.AF_INET: if bind.v4.blackhole { @@ -514,6 +568,14 @@ func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint, offset int) erro return nil } +func (bind *WinRingBind) SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte) { + endpoint, err := bind.ParseEndpoint(destination.String()) + if err != nil { + panic(E.Cause(err, "parse destination as WinRingEndpoint")) + } + bind.reservedForEndpoint[*endpoint.(*WinRingEndpoint)] = reserved +} + func (s *StdNetBind) BindSocketToInterface4(interfaceIndex uint32, blackhole bool) error { s.mu.Lock() defer s.mu.Unlock() diff --git a/conn/conn.go b/conn/conn.go index f178161..4c5c194 100644 --- a/conn/conn.go +++ b/conn/conn.go @@ -57,6 +57,8 @@ type Bind interface { // BatchSize is the number of buffers expected to be passed to // the ReceiveFuncs, and the maximum expected to be passed to SendBatch. BatchSize() int + + SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte) } // BindSocketToInterface is implemented by Bind objects that support being diff --git a/conn/controlfns.go b/conn/controlfns.go index 4f7d90f..44b52fc 100644 --- a/conn/controlfns.go +++ b/conn/controlfns.go @@ -6,8 +6,7 @@ package conn import ( - "net" - "syscall" + "github.com/sagernet/sing/common/control" ) // UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it is @@ -17,27 +16,6 @@ import ( // 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{} - -// 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() *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 - } - } - return nil - }, - } -} +var controlFns []control.Func diff --git a/conn/default.go b/conn/default.go index b6f761b..1892c3f 100644 --- a/conn/default.go +++ b/conn/default.go @@ -7,4 +7,8 @@ package conn -func NewDefaultBind() Bind { return NewStdNetBind() } +import "github.com/sagernet/sing/common/control" + +func NewDefaultBind(externalControl control.Func) Bind { + return NewStdNetBind(externalControl) +} diff --git a/device/device.go b/device/device.go index e491e53..14cd080 100644 --- a/device/device.go +++ b/device/device.go @@ -14,7 +14,6 @@ import ( "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" - "github.com/sagernet/wireguard-go/conn" "github.com/sagernet/wireguard-go/ratelimiter" "github.com/sagernet/wireguard-go/rwcancel"