Skip to content

Commit 99114d2

Browse files
committed
Improve vectorised writer
1 parent 0dd22b3 commit 99114d2

13 files changed

Lines changed: 432 additions & 55 deletions

File tree

.github/workflows/debug.yml

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,17 @@ on:
1414

1515
jobs:
1616
build:
17-
name: Debug build
17+
name: Linux Debug build
1818
runs-on: ubuntu-latest
1919
steps:
2020
- name: Checkout
2121
uses: actions/checkout@v4
2222
with:
2323
fetch-depth: 0
24-
- name: Get latest go version
25-
id: version
26-
run: |
27-
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')
2824
- name: Setup Go
2925
uses: actions/setup-go@v4
3026
with:
31-
go-version: ${{ steps.version.outputs.go_version }}
27+
go-version: ">=1.21.0 <1.22.0"
3228
- name: Add cache to Go proxy
3329
run: |
3430
version=`git rev-parse HEAD`
@@ -41,3 +37,83 @@ jobs:
4137
- name: Build
4238
run: |
4339
make test
40+
build_go118:
41+
name: Linux Debug build (Go 1.18)
42+
runs-on: ubuntu-latest
43+
steps:
44+
- name: Checkout
45+
uses: actions/checkout@v4
46+
with:
47+
fetch-depth: 0
48+
- name: Setup Go
49+
uses: actions/setup-go@v4
50+
with:
51+
go-version: ">=1.18.0 <1.19.0"
52+
continue-on-error: true
53+
- name: Build
54+
run: |
55+
make test
56+
build_go119:
57+
name: Linux Debug build (Go 1.19)
58+
runs-on: ubuntu-latest
59+
steps:
60+
- name: Checkout
61+
uses: actions/checkout@v4
62+
with:
63+
fetch-depth: 0
64+
- name: Setup Go
65+
uses: actions/setup-go@v4
66+
with:
67+
go-version: ">=1.19.0 <1.20.0"
68+
continue-on-error: true
69+
- name: Build
70+
run: |
71+
make test
72+
build_go120:
73+
name: Linux Debug build (Go 1.20)
74+
runs-on: ubuntu-latest
75+
steps:
76+
- name: Checkout
77+
uses: actions/checkout@v4
78+
with:
79+
fetch-depth: 0
80+
- name: Setup Go
81+
uses: actions/setup-go@v4
82+
with:
83+
go-version: ">=1.20.0 <1.21.0"
84+
continue-on-error: true
85+
- name: Build
86+
run: |
87+
make test
88+
build__windows:
89+
name: Windows Debug build
90+
runs-on: windows-latest
91+
steps:
92+
- name: Checkout
93+
uses: actions/checkout@v4
94+
with:
95+
fetch-depth: 0
96+
- name: Setup Go
97+
uses: actions/setup-go@v4
98+
with:
99+
go-version: ">=1.21.0 <1.22.0"
100+
continue-on-error: true
101+
- name: Build
102+
run: |
103+
make test
104+
build_darwin:
105+
name: macOS Debug build
106+
runs-on: macos-latest
107+
steps:
108+
- name: Checkout
109+
uses: actions/checkout@v4
110+
with:
111+
fetch-depth: 0
112+
- name: Setup Go
113+
uses: actions/setup-go@v4
114+
with:
115+
go-version: ">=1.21.0 <1.22.0"
116+
continue-on-error: true
117+
- name: Build
118+
run: |
119+
make test

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ lint_install:
1818
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
1919

2020
test:
21-
go test -v ./...
21+
go test $(go list ./... | grep -v /internal/)

common/bufio/addr_bsd.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
2+
3+
package bufio
4+
5+
import (
6+
"encoding/binary"
7+
"net/netip"
8+
"unsafe"
9+
10+
"golang.org/x/sys/unix"
11+
)
12+
13+
func ToSockaddr(destination netip.AddrPort) (name unsafe.Pointer, nameLen uint32) {
14+
if destination.Addr().Is4() {
15+
sa := unix.RawSockaddrInet4{
16+
Len: unix.SizeofSockaddrInet4,
17+
Family: unix.AF_INET,
18+
Addr: destination.Addr().As4(),
19+
}
20+
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
21+
name = unsafe.Pointer(&sa)
22+
nameLen = unix.SizeofSockaddrInet4
23+
} else {
24+
sa := unix.RawSockaddrInet6{
25+
Len: unix.SizeofSockaddrInet6,
26+
Family: unix.AF_INET6,
27+
Addr: destination.Addr().As16(),
28+
}
29+
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
30+
name = unsafe.Pointer(&sa)
31+
nameLen = unix.SizeofSockaddrInet6
32+
}
33+
return
34+
}

common/bufio/addr_linux.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package bufio
2+
3+
import (
4+
"encoding/binary"
5+
"net/netip"
6+
"unsafe"
7+
8+
"golang.org/x/sys/unix"
9+
)
10+
11+
func ToSockaddr(destination netip.AddrPort) (name unsafe.Pointer, nameLen uint32) {
12+
if destination.Addr().Is4() {
13+
sa := unix.RawSockaddrInet4{
14+
Family: unix.AF_INET,
15+
Addr: destination.Addr().As4(),
16+
}
17+
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
18+
name = unsafe.Pointer(&sa)
19+
nameLen = unix.SizeofSockaddrInet4
20+
} else {
21+
sa := unix.RawSockaddrInet6{
22+
Family: unix.AF_INET6,
23+
Addr: destination.Addr().As16(),
24+
}
25+
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
26+
name = unsafe.Pointer(&sa)
27+
nameLen = unix.SizeofSockaddrInet6
28+
}
29+
return
30+
}

common/bufio/addr_windows.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package bufio
2+
3+
import (
4+
"encoding/binary"
5+
"net/netip"
6+
"unsafe"
7+
8+
"golang.org/x/sys/windows"
9+
)
10+
11+
func ToSockaddr(destination netip.AddrPort) (name unsafe.Pointer, nameLen int32) {
12+
if destination.Addr().Is4() {
13+
sa := windows.RawSockaddrInet4{
14+
Family: windows.AF_INET,
15+
Addr: destination.Addr().As4(),
16+
}
17+
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
18+
name = unsafe.Pointer(&sa)
19+
nameLen = int32(unsafe.Sizeof(sa))
20+
} else {
21+
sa := windows.RawSockaddrInet6{
22+
Family: windows.AF_INET6,
23+
Addr: destination.Addr().As16(),
24+
}
25+
binary.BigEndian.PutUint16((*[2]byte)(unsafe.Pointer(&sa.Port))[:], destination.Port())
26+
name = unsafe.Pointer(&sa)
27+
nameLen = int32(unsafe.Sizeof(sa))
28+
}
29+
return
30+
}

common/bufio/net_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package bufio
2+
3+
import (
4+
"context"
5+
"net"
6+
"testing"
7+
"time"
8+
9+
M "github.com/sagernet/sing/common/metadata"
10+
"github.com/sagernet/sing/common/task"
11+
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TCPPipe(t *testing.T) (net.Conn, net.Conn) {
16+
listener, err := net.Listen("tcp", "127.0.0.1:0")
17+
require.NoError(t, err)
18+
var (
19+
group task.Group
20+
serverConn net.Conn
21+
clientConn net.Conn
22+
)
23+
group.Append0(func(ctx context.Context) error {
24+
var serverErr error
25+
serverConn, serverErr = listener.Accept()
26+
require.NoError(t, serverErr)
27+
return nil
28+
})
29+
group.Append0(func(ctx context.Context) error {
30+
var clientErr error
31+
clientConn, clientErr = net.Dial("tcp", listener.Addr().String())
32+
require.NoError(t, clientErr)
33+
return nil
34+
})
35+
err = group.Run()
36+
require.NoError(t, err)
37+
listener.Close()
38+
return serverConn, clientConn
39+
}
40+
41+
func UDPPipe(t *testing.T) (net.PacketConn, net.PacketConn, M.Socksaddr) {
42+
serverConn, err := net.ListenPacket("udp", "127.0.0.1:0")
43+
require.NoError(t, err)
44+
clientConn, err := net.ListenPacket("udp", "127.0.0.1:0")
45+
require.NoError(t, err)
46+
return serverConn, clientConn, M.SocksaddrFromNet(clientConn.LocalAddr())
47+
}
48+
49+
func Timeout(t *testing.T) context.CancelFunc {
50+
ctx, cancel := context.WithCancel(context.Background())
51+
go func() {
52+
select {
53+
case <-ctx.Done():
54+
return
55+
case <-time.After(5 * time.Second):
56+
t.Fatal("timeout")
57+
}
58+
}()
59+
return cancel
60+
}

common/bufio/vectorised.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ func CreateVectorisedWriter(writer any) (N.VectorisedWriter, bool) {
3333
case syscall.Conn:
3434
rawConn, err := w.SyscallConn()
3535
if err == nil {
36-
return &SyscallVectorisedWriter{writer, rawConn}, true
36+
return &SyscallVectorisedWriter{upstream: writer, rawConn: rawConn}, true
3737
}
3838
case syscall.RawConn:
39-
return &SyscallVectorisedWriter{writer, w}, true
39+
return &SyscallVectorisedWriter{upstream: writer, rawConn: w}, true
4040
}
4141
return nil, false
4242
}
@@ -48,10 +48,10 @@ func CreateVectorisedPacketWriter(writer any) (N.VectorisedPacketWriter, bool) {
4848
case syscall.Conn:
4949
rawConn, err := w.SyscallConn()
5050
if err == nil {
51-
return &SyscallVectorisedPacketWriter{writer, rawConn}, true
51+
return &SyscallVectorisedPacketWriter{upstream: writer, rawConn: rawConn}, true
5252
}
5353
case syscall.RawConn:
54-
return &SyscallVectorisedPacketWriter{writer, w}, true
54+
return &SyscallVectorisedPacketWriter{upstream: writer, rawConn: w}, true
5555
}
5656
return nil, false
5757
}
@@ -111,6 +111,7 @@ var _ N.VectorisedWriter = (*SyscallVectorisedWriter)(nil)
111111
type SyscallVectorisedWriter struct {
112112
upstream any
113113
rawConn syscall.RawConn
114+
syscallVectorisedWriterFields
114115
}
115116

116117
func (w *SyscallVectorisedWriter) Upstream() any {
@@ -126,6 +127,7 @@ var _ N.VectorisedPacketWriter = (*SyscallVectorisedPacketWriter)(nil)
126127
type SyscallVectorisedPacketWriter struct {
127128
upstream any
128129
rawConn syscall.RawConn
130+
syscallVectorisedWriterFields
129131
}
130132

131133
func (w *SyscallVectorisedPacketWriter) Upstream() any {

common/bufio/vectorised_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package bufio
2+
3+
import (
4+
"crypto/rand"
5+
"io"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestWriteVectorised(t *testing.T) {
12+
t.Parallel()
13+
inputConn, outputConn := TCPPipe(t)
14+
defer inputConn.Close()
15+
defer outputConn.Close()
16+
vectorisedWriter, created := CreateVectorisedWriter(inputConn)
17+
require.True(t, created)
18+
require.NotNil(t, vectorisedWriter)
19+
var bufA [1024]byte
20+
var bufB [1024]byte
21+
var bufC [2048]byte
22+
_, err := io.ReadFull(rand.Reader, bufA[:])
23+
require.NoError(t, err)
24+
_, err = io.ReadFull(rand.Reader, bufB[:])
25+
require.NoError(t, err)
26+
copy(bufC[:], bufA[:])
27+
copy(bufC[1024:], bufB[:])
28+
finish := Timeout(t)
29+
_, err = WriteVectorised(vectorisedWriter, [][]byte{bufA[:], bufB[:]})
30+
require.NoError(t, err)
31+
output := make([]byte, 2048)
32+
_, err = io.ReadFull(outputConn, output)
33+
finish()
34+
require.NoError(t, err)
35+
require.Equal(t, bufC[:], output)
36+
}
37+
38+
func TestWriteVectorisedPacket(t *testing.T) {
39+
inputConn, outputConn, outputAddr := UDPPipe(t)
40+
defer inputConn.Close()
41+
defer outputConn.Close()
42+
vectorisedWriter, created := CreateVectorisedPacketWriter(inputConn)
43+
require.True(t, created)
44+
require.NotNil(t, vectorisedWriter)
45+
var bufA [1024]byte
46+
var bufB [1024]byte
47+
var bufC [2048]byte
48+
_, err := io.ReadFull(rand.Reader, bufA[:])
49+
require.NoError(t, err)
50+
_, err = io.ReadFull(rand.Reader, bufB[:])
51+
require.NoError(t, err)
52+
copy(bufC[:], bufA[:])
53+
copy(bufC[1024:], bufB[:])
54+
finish := Timeout(t)
55+
_, err = WriteVectorisedPacket(vectorisedWriter, [][]byte{bufA[:], bufB[:]}, outputAddr)
56+
require.NoError(t, err)
57+
output := make([]byte, 2048)
58+
n, _, err := outputConn.ReadFrom(output)
59+
finish()
60+
require.NoError(t, err)
61+
require.Equal(t, 2048, n)
62+
require.Equal(t, bufC[:], output)
63+
}

0 commit comments

Comments
 (0)