Export std net bind

This commit is contained in:
世界 2023-12-15 18:44:05 +08:00
parent a71256d250
commit 73f8c6542b
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
5 changed files with 129 additions and 39 deletions

View file

@ -16,6 +16,9 @@ import (
"sync" "sync"
"syscall" "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/ipv4"
"golang.org/x/net/ipv6" "golang.org/x/net/ipv6"
) )
@ -28,6 +31,9 @@ var _ Bind = (*StdNetBind)(nil)
// methods for sending and receiving multiple datagrams per-syscall. See the // methods for sending and receiving multiple datagrams per-syscall. See the
// proposal in https://github.com/golang/go/issues/45886#issuecomment-1218301564. // proposal in https://github.com/golang/go/issues/45886#issuecomment-1218301564.
type StdNetBind struct { type StdNetBind struct {
externalControl control.Func
reservedForEndpoint map[netip.AddrPort][3]uint8
mu sync.Mutex // protects all fields except as specified mu sync.Mutex // protects all fields except as specified
ipv4 *net.UDPConn ipv4 *net.UDPConn
ipv6 *net.UDPConn ipv6 *net.UDPConn
@ -46,8 +52,11 @@ type StdNetBind struct {
blackhole6 bool blackhole6 bool
} }
func NewStdNetBind() Bind { func NewStdNetBind(externalControl control.Func) Bind {
return &StdNetBind{ return &StdNetBind{
externalControl: externalControl,
reservedForEndpoint: make(map[netip.AddrPort][3]uint8),
udpAddrPool: sync.Pool{ udpAddrPool: sync.Pool{
New: func() any { New: func() any {
return &net.UDPAddr{ return &net.UDPAddr{
@ -117,8 +126,29 @@ func (e *StdNetEndpoint) DstToString() string {
return e.AddrPort.String() return e.AddrPort.String()
} }
func listenNet(network string, port int) (*net.UDPConn, int, error) { func listenNet(externalControl control.Func, network string, port int) (*net.UDPConn, int, error) {
conn, err := listenConfig().ListenPacket(context.Background(), network, ":"+strconv.Itoa(port)) 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 { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -154,13 +184,13 @@ again:
var v4pc *ipv4.PacketConn var v4pc *ipv4.PacketConn
var v6pc *ipv6.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) { if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
return nil, 0, err return nil, 0, err
} }
// Listen on the same port as we're using for ipv4. // 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, syscall.EADDRINUSE) && tries < 100 { if uport == 0 && errors.Is(err, syscall.EADDRINUSE) && tries < 100 {
v4conn.Close() v4conn.Close()
tries++ tries++
@ -265,8 +295,10 @@ func (s *StdNetBind) receiveIP(
if sizes[i] == 0 { if sizes[i] == 0 {
continue continue
} }
addrPort := msg.Addr.(*net.UDPAddr).AddrPort() if msg.N > 3 {
ep := &StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation common.ClearArray(bufs[i][1:4])
}
ep := &StdNetEndpoint{AddrPort: M.AddrPortFromNet(msg.Addr)} // TODO: remove allocation
getSrcFromControl(msg.OOB[:msg.NN], ep) getSrcFromControl(msg.OOB[:msg.NN], ep)
eps[i] = ep eps[i] = ep
} }
@ -375,6 +407,14 @@ func (s *StdNetBind) Send(bufs [][]byte, endpoint Endpoint, offset int) error {
retried bool retried bool
err error 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: retry:
if offload { if offload {
n := coalesceMessages(ua, endpoint.(*StdNetEndpoint), bufs, offset, *msgs, setGSOSize) n := coalesceMessages(ua, endpoint.(*StdNetEndpoint), bufs, offset, *msgs, setGSOSize)
@ -405,6 +445,10 @@ retry:
return err 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 { func (s *StdNetBind) send(conn *net.UDPConn, pc batchWriter, msgs []ipv6.Message) error {
var ( var (
n int n int

View file

@ -15,6 +15,10 @@ import (
"sync/atomic" "sync/atomic"
"unsafe" "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" "github.com/sagernet/wireguard-go/conn/winrio"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
@ -71,18 +75,26 @@ type afWinRingBind struct {
// WinRingBind uses Windows registered I/O for fast ring buffered networking. // WinRingBind uses Windows registered I/O for fast ring buffered networking.
type WinRingBind struct { type WinRingBind struct {
externalControl control.Func
reservedForEndpoint map[WinRingEndpoint][3]uint8
v4, v6 afWinRingBind v4, v6 afWinRingBind
mu sync.RWMutex mu sync.RWMutex
isOpen atomic.Uint32 // 0, 1, or 2 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() { if !winrio.Initialize() {
return NewStdNetBind() return NewStdNetBind(externalControl)
}
return &WinRingBind{
externalControl: externalControl,
reservedForEndpoint: make(map[WinRingEndpoint][3]uint8),
} }
return new(WinRingBind)
} }
type WinRingEndpoint struct { type WinRingEndpoint struct {
@ -238,7 +250,7 @@ func (ring *ringBuffer) Open() error {
return nil 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 var err error
bind.sock, err = winrio.Socket(family, windows.SOCK_DGRAM, windows.IPPROTO_UDP) bind.sock, err = winrio.Socket(family, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
if err != nil { if err != nil {
@ -256,6 +268,19 @@ func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sock
if err != nil { if err != nil {
return nil, err 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) err = windows.Bind(bind.sock, sa)
if err != nil { if err != nil {
return nil, err return nil, err
@ -267,6 +292,23 @@ func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sock
return sa, nil 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) { func (bind *WinRingBind) Open(port uint16) (recvFns []ReceiveFunc, selectedPort uint16, err error) {
bind.mu.Lock() bind.mu.Lock()
defer bind.mu.Unlock() defer bind.mu.Unlock()
@ -279,11 +321,11 @@ func (bind *WinRingBind) Open(port uint16) (recvFns []ReceiveFunc, selectedPort
return nil, 0, ErrBindAlreadyOpen return nil, 0, ErrBindAlreadyOpen
} }
var sa windows.Sockaddr 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 { if err != nil {
return nil, 0, err 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 { if err != nil {
return nil, 0, err return nil, 0, err
} }
@ -419,6 +461,9 @@ func (bind *WinRingBind) receiveIPv4(bufs [][]byte, sizes []int, eps []Endpoint)
bind.mu.RLock() bind.mu.RLock()
defer bind.mu.RUnlock() defer bind.mu.RUnlock()
n, ep, err := bind.v4.Receive(bufs[0], &bind.isOpen) n, ep, err := bind.v4.Receive(bufs[0], &bind.isOpen)
if n > 3 {
common.ClearArray(bufs[0][1:4])
}
sizes[0] = n sizes[0] = n
eps[0] = ep eps[0] = ep
return 1, err return 1, err
@ -428,6 +473,9 @@ func (bind *WinRingBind) receiveIPv6(bufs [][]byte, sizes []int, eps []Endpoint)
bind.mu.RLock() bind.mu.RLock()
defer bind.mu.RUnlock() defer bind.mu.RUnlock()
n, ep, err := bind.v6.Receive(bufs[0], &bind.isOpen) n, ep, err := bind.v6.Receive(bufs[0], &bind.isOpen)
if n > 3 {
common.ClearArray(bufs[0][1:4])
}
sizes[0] = n sizes[0] = n
eps[0] = ep eps[0] = ep
return 1, err return 1, err
@ -494,6 +542,12 @@ func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint, offset int) erro
defer bind.mu.RUnlock() defer bind.mu.RUnlock()
for _, buf := range bufs { for _, buf := range bufs {
buf = buf[offset:] buf = buf[offset:]
if len(buf) > 3 {
reserved, loaded := bind.reservedForEndpoint[*endpoint.(*WinRingEndpoint)]
if loaded {
copy(buf[1:4], reserved[:])
}
}
switch nend.family { switch nend.family {
case windows.AF_INET: case windows.AF_INET:
if bind.v4.blackhole { if bind.v4.blackhole {
@ -514,6 +568,14 @@ func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint, offset int) erro
return nil 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 { func (s *StdNetBind) BindSocketToInterface4(interfaceIndex uint32, blackhole bool) error {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()

View file

@ -57,6 +57,8 @@ type Bind interface {
// BatchSize is the number of buffers expected to be passed to // BatchSize is the number of buffers expected to be passed to
// the ReceiveFuncs, and the maximum expected to be passed to SendBatch. // the ReceiveFuncs, and the maximum expected to be passed to SendBatch.
BatchSize() int BatchSize() int
SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte)
} }
// BindSocketToInterface is implemented by Bind objects that support being // BindSocketToInterface is implemented by Bind objects that support being

View file

@ -6,8 +6,7 @@
package conn package conn
import ( import (
"net" "github.com/sagernet/sing/common/control"
"syscall"
) )
// UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it is // UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it is
@ -17,27 +16,6 @@ import (
// around this limitation) // around this limitation)
const socketBufferSize = 7 << 20 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 // controlFns is a list of functions that are called from the listen config
// that can apply socket options. // that can apply socket options.
var controlFns = []controlFn{} var controlFns []control.Func
// 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
},
}
}

View file

@ -7,4 +7,8 @@
package conn package conn
func NewDefaultBind() Bind { return NewStdNetBind() } import "github.com/sagernet/sing/common/control"
func NewDefaultBind(externalControl control.Func) Bind {
return NewStdNetBind(externalControl)
}