Export std net bind
This commit is contained in:
parent
b2a20cdd77
commit
c63bc19bc9
6 changed files with 129 additions and 40 deletions
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
@ -31,6 +34,9 @@ var (
|
||||||
// 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
|
||||||
|
|
@ -49,8 +55,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{
|
||||||
|
|
@ -118,8 +127,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
|
||||||
}
|
}
|
||||||
|
|
@ -160,13 +190,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, errEADDRINUSE) && tries < 100 {
|
if uport == 0 && errors.Is(err, errEADDRINUSE) && tries < 100 {
|
||||||
v4conn.Close()
|
v4conn.Close()
|
||||||
tries++
|
tries++
|
||||||
|
|
@ -270,8 +300,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
|
||||||
}
|
}
|
||||||
|
|
@ -380,6 +412,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)
|
||||||
|
|
@ -410,6 +450,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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import (
|
||||||
|
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
|
|
||||||
"github.com/sagernet/wireguard-go/conn"
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
"github.com/sagernet/wireguard-go/ratelimiter"
|
"github.com/sagernet/wireguard-go/ratelimiter"
|
||||||
"github.com/sagernet/wireguard-go/rwcancel"
|
"github.com/sagernet/wireguard-go/rwcancel"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue