mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-04 04:17:38 +03:00
Improve vectorised writer
This commit is contained in:
parent
edd320c3a8
commit
2e36fa6849
13 changed files with 432 additions and 55 deletions
88
.github/workflows/debug.yml
vendored
88
.github/workflows/debug.yml
vendored
|
@ -14,21 +14,17 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Debug build
|
name: Linux Debug build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Get latest go version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ">=1.21.0 <1.22.0"
|
||||||
- name: Add cache to Go proxy
|
- name: Add cache to Go proxy
|
||||||
run: |
|
run: |
|
||||||
version=`git rev-parse HEAD`
|
version=`git rev-parse HEAD`
|
||||||
|
@ -41,3 +37,83 @@ jobs:
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
make test
|
make test
|
||||||
|
build_go118:
|
||||||
|
name: Linux Debug build (Go 1.18)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ">=1.18.0 <1.19.0"
|
||||||
|
continue-on-error: true
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
make test
|
||||||
|
build_go119:
|
||||||
|
name: Linux Debug build (Go 1.19)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ">=1.19.0 <1.20.0"
|
||||||
|
continue-on-error: true
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
make test
|
||||||
|
build_go120:
|
||||||
|
name: Linux Debug build (Go 1.20)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ">=1.20.0 <1.21.0"
|
||||||
|
continue-on-error: true
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
make test
|
||||||
|
build__windows:
|
||||||
|
name: Windows Debug build
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ">=1.21.0 <1.22.0"
|
||||||
|
continue-on-error: true
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
make test
|
||||||
|
build_darwin:
|
||||||
|
name: macOS Debug build
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ">=1.21.0 <1.22.0"
|
||||||
|
continue-on-error: true
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
make test
|
2
Makefile
2
Makefile
|
@ -18,4 +18,4 @@ lint_install:
|
||||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v ./...
|
go test $(shell go list ./... | grep -v /internal/)
|
||||||
|
|
34
common/bufio/addr_bsd.go
Normal file
34
common/bufio/addr_bsd.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||||
|
|
||||||
|
package bufio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net/netip"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToSockaddr(destination netip.AddrPort) (name unsafe.Pointer, nameLen uint32) {
|
||||||
|
if destination.Addr().Is4() {
|
||||||
|
sa := unix.RawSockaddrInet4{
|
||||||
|
Len: unix.SizeofSockaddrInet4,
|
||||||
|
Family: unix.AF_INET,
|
||||||
|
Addr: destination.Addr().As4(),
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
|
||||||
|
name = unsafe.Pointer(&sa)
|
||||||
|
nameLen = unix.SizeofSockaddrInet4
|
||||||
|
} else {
|
||||||
|
sa := unix.RawSockaddrInet6{
|
||||||
|
Len: unix.SizeofSockaddrInet6,
|
||||||
|
Family: unix.AF_INET6,
|
||||||
|
Addr: destination.Addr().As16(),
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
|
||||||
|
name = unsafe.Pointer(&sa)
|
||||||
|
nameLen = unix.SizeofSockaddrInet6
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
30
common/bufio/addr_linux.go
Normal file
30
common/bufio/addr_linux.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package bufio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net/netip"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToSockaddr(destination netip.AddrPort) (name unsafe.Pointer, nameLen uint32) {
|
||||||
|
if destination.Addr().Is4() {
|
||||||
|
sa := unix.RawSockaddrInet4{
|
||||||
|
Family: unix.AF_INET,
|
||||||
|
Addr: destination.Addr().As4(),
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
|
||||||
|
name = unsafe.Pointer(&sa)
|
||||||
|
nameLen = unix.SizeofSockaddrInet4
|
||||||
|
} else {
|
||||||
|
sa := unix.RawSockaddrInet6{
|
||||||
|
Family: unix.AF_INET6,
|
||||||
|
Addr: destination.Addr().As16(),
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
|
||||||
|
name = unsafe.Pointer(&sa)
|
||||||
|
nameLen = unix.SizeofSockaddrInet6
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
30
common/bufio/addr_windows.go
Normal file
30
common/bufio/addr_windows.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package bufio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net/netip"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToSockaddr(destination netip.AddrPort) (name unsafe.Pointer, nameLen int32) {
|
||||||
|
if destination.Addr().Is4() {
|
||||||
|
sa := windows.RawSockaddrInet4{
|
||||||
|
Family: windows.AF_INET,
|
||||||
|
Addr: destination.Addr().As4(),
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
|
||||||
|
name = unsafe.Pointer(&sa)
|
||||||
|
nameLen = int32(unsafe.Sizeof(sa))
|
||||||
|
} else {
|
||||||
|
sa := windows.RawSockaddrInet6{
|
||||||
|
Family: windows.AF_INET6,
|
||||||
|
Addr: destination.Addr().As16(),
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
|
||||||
|
name = unsafe.Pointer(&sa)
|
||||||
|
nameLen = int32(unsafe.Sizeof(sa))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
60
common/bufio/net_test.go
Normal file
60
common/bufio/net_test.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package bufio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TCPPipe(t *testing.T) (net.Conn, net.Conn) {
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var (
|
||||||
|
group task.Group
|
||||||
|
serverConn net.Conn
|
||||||
|
clientConn net.Conn
|
||||||
|
)
|
||||||
|
group.Append0(func(ctx context.Context) error {
|
||||||
|
var serverErr error
|
||||||
|
serverConn, serverErr = listener.Accept()
|
||||||
|
require.NoError(t, serverErr)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
group.Append0(func(ctx context.Context) error {
|
||||||
|
var clientErr error
|
||||||
|
clientConn, clientErr = net.Dial("tcp", listener.Addr().String())
|
||||||
|
require.NoError(t, clientErr)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
err = group.Run()
|
||||||
|
require.NoError(t, err)
|
||||||
|
listener.Close()
|
||||||
|
return serverConn, clientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func UDPPipe(t *testing.T) (net.PacketConn, net.PacketConn, M.Socksaddr) {
|
||||||
|
serverConn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
clientConn, err := net.ListenPacket("udp", "127.0.0.1:0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
return serverConn, clientConn, M.SocksaddrFromNet(clientConn.LocalAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Timeout(t *testing.T) context.CancelFunc {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("timeout")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return cancel
|
||||||
|
}
|
|
@ -33,10 +33,10 @@ func CreateVectorisedWriter(writer any) (N.VectorisedWriter, bool) {
|
||||||
case syscall.Conn:
|
case syscall.Conn:
|
||||||
rawConn, err := w.SyscallConn()
|
rawConn, err := w.SyscallConn()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &SyscallVectorisedWriter{writer, rawConn}, true
|
return &SyscallVectorisedWriter{upstream: writer, rawConn: rawConn}, true
|
||||||
}
|
}
|
||||||
case syscall.RawConn:
|
case syscall.RawConn:
|
||||||
return &SyscallVectorisedWriter{writer, w}, true
|
return &SyscallVectorisedWriter{upstream: writer, rawConn: w}, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@ -48,10 +48,10 @@ func CreateVectorisedPacketWriter(writer any) (N.VectorisedPacketWriter, bool) {
|
||||||
case syscall.Conn:
|
case syscall.Conn:
|
||||||
rawConn, err := w.SyscallConn()
|
rawConn, err := w.SyscallConn()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &SyscallVectorisedPacketWriter{writer, rawConn}, true
|
return &SyscallVectorisedPacketWriter{upstream: writer, rawConn: rawConn}, true
|
||||||
}
|
}
|
||||||
case syscall.RawConn:
|
case syscall.RawConn:
|
||||||
return &SyscallVectorisedPacketWriter{writer, w}, true
|
return &SyscallVectorisedPacketWriter{upstream: writer, rawConn: w}, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@ -111,6 +111,7 @@ var _ N.VectorisedWriter = (*SyscallVectorisedWriter)(nil)
|
||||||
type SyscallVectorisedWriter struct {
|
type SyscallVectorisedWriter struct {
|
||||||
upstream any
|
upstream any
|
||||||
rawConn syscall.RawConn
|
rawConn syscall.RawConn
|
||||||
|
syscallVectorisedWriterFields
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SyscallVectorisedWriter) Upstream() any {
|
func (w *SyscallVectorisedWriter) Upstream() any {
|
||||||
|
@ -126,6 +127,7 @@ var _ N.VectorisedPacketWriter = (*SyscallVectorisedPacketWriter)(nil)
|
||||||
type SyscallVectorisedPacketWriter struct {
|
type SyscallVectorisedPacketWriter struct {
|
||||||
upstream any
|
upstream any
|
||||||
rawConn syscall.RawConn
|
rawConn syscall.RawConn
|
||||||
|
syscallVectorisedWriterFields
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SyscallVectorisedPacketWriter) Upstream() any {
|
func (w *SyscallVectorisedPacketWriter) Upstream() any {
|
||||||
|
|
63
common/bufio/vectorised_test.go
Normal file
63
common/bufio/vectorised_test.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package bufio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriteVectorised(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
inputConn, outputConn := TCPPipe(t)
|
||||||
|
defer inputConn.Close()
|
||||||
|
defer outputConn.Close()
|
||||||
|
vectorisedWriter, created := CreateVectorisedWriter(inputConn)
|
||||||
|
require.True(t, created)
|
||||||
|
require.NotNil(t, vectorisedWriter)
|
||||||
|
var bufA [1024]byte
|
||||||
|
var bufB [1024]byte
|
||||||
|
var bufC [2048]byte
|
||||||
|
_, err := io.ReadFull(rand.Reader, bufA[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = io.ReadFull(rand.Reader, bufB[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
copy(bufC[:], bufA[:])
|
||||||
|
copy(bufC[1024:], bufB[:])
|
||||||
|
finish := Timeout(t)
|
||||||
|
_, err = WriteVectorised(vectorisedWriter, [][]byte{bufA[:], bufB[:]})
|
||||||
|
require.NoError(t, err)
|
||||||
|
output := make([]byte, 2048)
|
||||||
|
_, err = io.ReadFull(outputConn, output)
|
||||||
|
finish()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, bufC[:], output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteVectorisedPacket(t *testing.T) {
|
||||||
|
inputConn, outputConn, outputAddr := UDPPipe(t)
|
||||||
|
defer inputConn.Close()
|
||||||
|
defer outputConn.Close()
|
||||||
|
vectorisedWriter, created := CreateVectorisedPacketWriter(inputConn)
|
||||||
|
require.True(t, created)
|
||||||
|
require.NotNil(t, vectorisedWriter)
|
||||||
|
var bufA [1024]byte
|
||||||
|
var bufB [1024]byte
|
||||||
|
var bufC [2048]byte
|
||||||
|
_, err := io.ReadFull(rand.Reader, bufA[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = io.ReadFull(rand.Reader, bufB[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
copy(bufC[:], bufA[:])
|
||||||
|
copy(bufC[1024:], bufB[:])
|
||||||
|
finish := Timeout(t)
|
||||||
|
_, err = WriteVectorisedPacket(vectorisedWriter, [][]byte{bufA[:], bufB[:]}, outputAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
output := make([]byte, 2048)
|
||||||
|
n, _, err := outputConn.ReadFrom(output)
|
||||||
|
finish()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2048, n)
|
||||||
|
require.Equal(t, bufC[:], output)
|
||||||
|
}
|
|
@ -3,6 +3,8 @@
|
||||||
package bufio
|
package bufio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
@ -11,15 +13,28 @@ import (
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type syscallVectorisedWriterFields struct {
|
||||||
|
access sync.Mutex
|
||||||
|
iovecList *[]unix.Iovec
|
||||||
|
}
|
||||||
|
|
||||||
func (w *SyscallVectorisedWriter) WriteVectorised(buffers []*buf.Buffer) error {
|
func (w *SyscallVectorisedWriter) WriteVectorised(buffers []*buf.Buffer) error {
|
||||||
|
w.access.Lock()
|
||||||
|
defer w.access.Unlock()
|
||||||
defer buf.ReleaseMulti(buffers)
|
defer buf.ReleaseMulti(buffers)
|
||||||
iovecList := make([]unix.Iovec, 0, len(buffers))
|
var iovecList []unix.Iovec
|
||||||
for _, buffer := range buffers {
|
if w.iovecList != nil {
|
||||||
var iovec unix.Iovec
|
iovecList = *w.iovecList
|
||||||
iovec.Base = &buffer.Bytes()[0]
|
|
||||||
iovec.SetLen(buffer.Len())
|
|
||||||
iovecList = append(iovecList, iovec)
|
|
||||||
}
|
}
|
||||||
|
iovecList = iovecList[:0]
|
||||||
|
for index, buffer := range buffers {
|
||||||
|
iovecList = append(iovecList, unix.Iovec{Base: &buffer.Bytes()[0]})
|
||||||
|
iovecList[index].SetLen(buffer.Len())
|
||||||
|
}
|
||||||
|
if w.iovecList == nil {
|
||||||
|
w.iovecList = new([]unix.Iovec)
|
||||||
|
}
|
||||||
|
*w.iovecList = iovecList // cache
|
||||||
var innerErr unix.Errno
|
var innerErr unix.Errno
|
||||||
err := w.rawConn.Write(func(fd uintptr) (done bool) {
|
err := w.rawConn.Write(func(fd uintptr) (done bool) {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
|
@ -28,28 +43,42 @@ func (w *SyscallVectorisedWriter) WriteVectorised(buffers []*buf.Buffer) error {
|
||||||
return innerErr != unix.EAGAIN && innerErr != unix.EWOULDBLOCK
|
return innerErr != unix.EAGAIN && innerErr != unix.EWOULDBLOCK
|
||||||
})
|
})
|
||||||
if innerErr != 0 {
|
if innerErr != 0 {
|
||||||
err = innerErr
|
err = os.NewSyscallError("SYS_WRITEV", innerErr)
|
||||||
|
}
|
||||||
|
for index := range iovecList {
|
||||||
|
iovecList[index] = unix.Iovec{}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SyscallVectorisedPacketWriter) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error {
|
func (w *SyscallVectorisedPacketWriter) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
w.access.Lock()
|
||||||
|
defer w.access.Unlock()
|
||||||
defer buf.ReleaseMulti(buffers)
|
defer buf.ReleaseMulti(buffers)
|
||||||
var sockaddr unix.Sockaddr
|
var iovecList []unix.Iovec
|
||||||
if destination.IsIPv4() {
|
if w.iovecList != nil {
|
||||||
sockaddr = &unix.SockaddrInet4{
|
iovecList = *w.iovecList
|
||||||
Port: int(destination.Port),
|
|
||||||
Addr: destination.Addr.As4(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sockaddr = &unix.SockaddrInet6{
|
|
||||||
Port: int(destination.Port),
|
|
||||||
Addr: destination.Addr.As16(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
iovecList = iovecList[:0]
|
||||||
|
for index, buffer := range buffers {
|
||||||
|
iovecList = append(iovecList, unix.Iovec{Base: &buffer.Bytes()[0]})
|
||||||
|
iovecList[index].SetLen(buffer.Len())
|
||||||
|
}
|
||||||
|
if w.iovecList == nil {
|
||||||
|
w.iovecList = new([]unix.Iovec)
|
||||||
|
}
|
||||||
|
*w.iovecList = iovecList // cache
|
||||||
var innerErr error
|
var innerErr error
|
||||||
err := w.rawConn.Write(func(fd uintptr) (done bool) {
|
err := w.rawConn.Write(func(fd uintptr) (done bool) {
|
||||||
_, innerErr = unix.SendmsgBuffers(int(fd), buf.ToSliceMulti(buffers), nil, sockaddr, 0)
|
var msg unix.Msghdr
|
||||||
|
name, nameLen := ToSockaddr(destination.AddrPort())
|
||||||
|
msg.Name = (*byte)(name)
|
||||||
|
msg.Namelen = nameLen
|
||||||
|
if len(iovecList) > 0 {
|
||||||
|
msg.Iov = &iovecList[0]
|
||||||
|
msg.SetIovlen(len(iovecList))
|
||||||
|
}
|
||||||
|
_, innerErr = sendmsg(int(fd), &msg, 0)
|
||||||
return innerErr != unix.EAGAIN && innerErr != unix.EWOULDBLOCK
|
return innerErr != unix.EAGAIN && innerErr != unix.EWOULDBLOCK
|
||||||
})
|
})
|
||||||
if innerErr != nil {
|
if innerErr != nil {
|
||||||
|
@ -57,3 +86,6 @@ func (w *SyscallVectorisedPacketWriter) WriteVectorisedPacket(buffers []*buf.Buf
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname sendmsg golang.org/x/sys/unix.sendmsg
|
||||||
|
func sendmsg(s int, msg *unix.Msghdr, flags int) (n int, err error)
|
||||||
|
|
|
@ -1,62 +1,93 @@
|
||||||
package bufio
|
package bufio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type syscallVectorisedWriterFields struct {
|
||||||
|
access sync.Mutex
|
||||||
|
iovecList *[]windows.WSABuf
|
||||||
|
}
|
||||||
|
|
||||||
func (w *SyscallVectorisedWriter) WriteVectorised(buffers []*buf.Buffer) error {
|
func (w *SyscallVectorisedWriter) WriteVectorised(buffers []*buf.Buffer) error {
|
||||||
|
w.access.Lock()
|
||||||
|
defer w.access.Unlock()
|
||||||
defer buf.ReleaseMulti(buffers)
|
defer buf.ReleaseMulti(buffers)
|
||||||
iovecList := make([]*windows.WSABuf, len(buffers))
|
var iovecList []windows.WSABuf
|
||||||
for i, buffer := range buffers {
|
if w.iovecList != nil {
|
||||||
iovecList[i] = &windows.WSABuf{
|
iovecList = *w.iovecList
|
||||||
Len: uint32(buffer.Len()),
|
|
||||||
Buf: &buffer.Bytes()[0],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
iovecList = iovecList[:0]
|
||||||
|
for _, buffer := range buffers {
|
||||||
|
iovecList = append(iovecList, windows.WSABuf{
|
||||||
|
Buf: &buffer.Bytes()[0],
|
||||||
|
Len: uint32(buffer.Len()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if w.iovecList == nil {
|
||||||
|
w.iovecList = new([]windows.WSABuf)
|
||||||
|
}
|
||||||
|
*w.iovecList = iovecList // cache
|
||||||
var n uint32
|
var n uint32
|
||||||
var innerErr error
|
var innerErr error
|
||||||
err := w.rawConn.Write(func(fd uintptr) (done bool) {
|
err := w.rawConn.Write(func(fd uintptr) (done bool) {
|
||||||
innerErr = windows.WSASend(windows.Handle(fd), iovecList[0], uint32(len(iovecList)), &n, 0, nil, nil)
|
innerErr = windows.WSASend(windows.Handle(fd), &iovecList[0], uint32(len(iovecList)), &n, 0, nil, nil)
|
||||||
return innerErr != windows.WSAEWOULDBLOCK
|
return innerErr != windows.WSAEWOULDBLOCK
|
||||||
})
|
})
|
||||||
if innerErr != nil {
|
if innerErr != nil {
|
||||||
err = innerErr
|
err = innerErr
|
||||||
}
|
}
|
||||||
|
for index := range iovecList {
|
||||||
|
iovecList[index] = windows.WSABuf{}
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SyscallVectorisedPacketWriter) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error {
|
func (w *SyscallVectorisedPacketWriter) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
w.access.Lock()
|
||||||
|
defer w.access.Unlock()
|
||||||
defer buf.ReleaseMulti(buffers)
|
defer buf.ReleaseMulti(buffers)
|
||||||
iovecList := make([]*windows.WSABuf, len(buffers))
|
var iovecList []windows.WSABuf
|
||||||
for i, buffer := range buffers {
|
if w.iovecList != nil {
|
||||||
iovecList[i] = &windows.WSABuf{
|
iovecList = *w.iovecList
|
||||||
Len: uint32(buffer.Len()),
|
}
|
||||||
|
iovecList = iovecList[:0]
|
||||||
|
for _, buffer := range buffers {
|
||||||
|
iovecList = append(iovecList, windows.WSABuf{
|
||||||
Buf: &buffer.Bytes()[0],
|
Buf: &buffer.Bytes()[0],
|
||||||
}
|
Len: uint32(buffer.Len()),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
var sockaddr windows.Sockaddr
|
if w.iovecList == nil {
|
||||||
if destination.IsIPv4() {
|
w.iovecList = new([]windows.WSABuf)
|
||||||
sockaddr = &windows.SockaddrInet4{
|
|
||||||
Port: int(destination.Port),
|
|
||||||
Addr: destination.Addr.As4(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sockaddr = &windows.SockaddrInet6{
|
|
||||||
Port: int(destination.Port),
|
|
||||||
Addr: destination.Addr.As16(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
*w.iovecList = iovecList // cache
|
||||||
var n uint32
|
var n uint32
|
||||||
var innerErr error
|
var innerErr error
|
||||||
err := w.rawConn.Write(func(fd uintptr) (done bool) {
|
err := w.rawConn.Write(func(fd uintptr) (done bool) {
|
||||||
innerErr = windows.WSASendto(windows.Handle(fd), iovecList[0], uint32(len(iovecList)), &n, 0, sockaddr, nil, nil)
|
name, nameLen := ToSockaddr(destination.AddrPort())
|
||||||
|
innerErr = windows.WSASendTo(
|
||||||
|
windows.Handle(fd),
|
||||||
|
&iovecList[0],
|
||||||
|
uint32(len(iovecList)),
|
||||||
|
&n,
|
||||||
|
0,
|
||||||
|
(*windows.RawSockaddrAny)(name),
|
||||||
|
nameLen,
|
||||||
|
nil,
|
||||||
|
nil)
|
||||||
return innerErr != windows.WSAEWOULDBLOCK
|
return innerErr != windows.WSAEWOULDBLOCK
|
||||||
})
|
})
|
||||||
if innerErr != nil {
|
if innerErr != nil {
|
||||||
err = innerErr
|
err = innerErr
|
||||||
}
|
}
|
||||||
|
for index := range iovecList {
|
||||||
|
iovecList[index] = windows.WSABuf{}
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -2,4 +2,13 @@ module github.com/sagernet/sing
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require golang.org/x/sys v0.15.0
|
require (
|
||||||
|
github.com/stretchr/testify v1.8.4
|
||||||
|
golang.org/x/sys v0.15.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -1,2 +1,12 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue