Skip to content
This repository was archived by the owner on Jun 12, 2022. It is now read-only.

Commit c499c64

Browse files
committed
🛒 Rework TCP response header processing so it's zero-copy as well
1 parent e57f9a1 commit c499c64

File tree

4 files changed

+47
-27
lines changed

4 files changed

+47
-27
lines changed

client/client.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,45 +124,64 @@ type duplexConnAdaptor struct {
124124
saltPool *service.SaltPool
125125
}
126126

127-
func (dc *duplexConnAdaptor) readHeader() error {
128-
err := ss.ParseTCPRespHeader(dc.r, dc.w.Salt(), dc.cipherConfig)
127+
func (dc *duplexConnAdaptor) readHeader() ([]byte, error) {
128+
initPayload, err := ss.ParseTCPRespHeader(dc.r, dc.w.Salt(), dc.cipherConfig)
129129
if err != nil {
130130
dc.Close()
131-
return err
131+
return nil, err
132132
}
133133

134134
// 2022 spec: check salt
135135
if dc.cipherConfig.IsSpec2022 && !dc.saltPool.Add(*(*[32]byte)(dc.r.Salt())) {
136136
io.Copy(io.Discard, dc.r)
137137
dc.Close()
138-
return ErrRepeatedSalt
138+
return nil, ErrRepeatedSalt
139139
}
140140

141-
return nil
141+
return initPayload, nil
142142
}
143143

144-
func (dc *duplexConnAdaptor) Read(b []byte) (int, error) {
144+
func (dc *duplexConnAdaptor) Read(b []byte) (n int, err error) {
145145
if !dc.isHeaderProcessed {
146-
err := dc.readHeader()
146+
var initPayload []byte
147+
initPayload, err = dc.readHeader()
147148
if err != nil {
148149
return 0, err
149150
}
150151
dc.isHeaderProcessed = true
152+
153+
if len(initPayload) > 0 {
154+
n = copy(b, initPayload)
155+
if n < len(initPayload) {
156+
err = io.ErrShortBuffer
157+
}
158+
return
159+
}
151160
}
152161

153162
return dc.r.Read(b)
154163
}
155164

156-
func (dc *duplexConnAdaptor) WriteTo(w io.Writer) (int64, error) {
165+
func (dc *duplexConnAdaptor) WriteTo(w io.Writer) (n int64, err error) {
157166
if !dc.isHeaderProcessed {
158-
err := dc.readHeader()
167+
initPayload, err := dc.readHeader()
159168
if err != nil {
160169
return 0, err
161170
}
162171
dc.isHeaderProcessed = true
172+
173+
if len(initPayload) > 0 {
174+
wn, err := w.Write(initPayload)
175+
n = int64(wn)
176+
if err != nil {
177+
return n, err
178+
}
179+
}
163180
}
164181

165-
return io.Copy(w, dc.r)
182+
cn, err := io.Copy(w, dc.r)
183+
n += cn
184+
return
166185
}
167186

168187
func (dc *duplexConnAdaptor) CloseRead() error {

service/tcp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ func (s *tcpService) handleConnection(clientTCPConn tfo.Conn) {
278278
}
279279

280280
ssr := ss.NewShadowsocksReader(clientReader, cipherEntry.Cipher)
281-
tgtAddr, initPayload, err := ss.ParseTCPReqHeader(ssr, cipherEntry.Cipher.Config(), ss.HeaderTypeClientStream)
281+
tgtAddr, initPayload, err := ss.ParseTCPReqHeader(ssr, cipherEntry.Cipher.Config())
282282
if err != nil {
283283
logger.Warn("Failed to parse header",
284284
zap.Stringer("clientConnLocalAddress", clientLocalAddr),

shadowsocks/header.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"encoding/binary"
66
"errors"
77
"fmt"
8-
"io"
98
"math/rand"
109
"net"
1110
"strconv"
@@ -50,7 +49,7 @@ var (
5049
// For Shadowsocks 2022, the first payload chunk MUST contain
5150
// either a non-zero-length padding or initial payload, or both (not recommended client behavior but allowed).
5251
// If the padding length is 0 and there's no initial payload, an error is returned.
53-
func ParseTCPReqHeader(r Reader, cipherConfig CipherConfig, htype byte) (string, []byte, error) {
52+
func ParseTCPReqHeader(r Reader, cipherConfig CipherConfig) (string, []byte, error) {
5453
// Read first payload chunk.
5554
if err := r.EnsureLeftover(); err != nil {
5655
return "", nil, err
@@ -65,7 +64,7 @@ func ParseTCPReqHeader(r Reader, cipherConfig CipherConfig, htype byte) (string,
6564
}
6665

6766
// Verify type
68-
if b[0] != htype {
67+
if b[0] != HeaderTypeClientStream {
6968
return "", nil, ErrTypeMismatch
7069
}
7170

@@ -139,39 +138,42 @@ func WriteTCPReqHeader(dst, socksaddr []byte, addPadding bool, cipherConfig Ciph
139138
return
140139
}
141140

142-
func ParseTCPRespHeader(r io.Reader, clientSalt []byte, cipherConfig CipherConfig) error {
141+
func ParseTCPRespHeader(r Reader, clientSalt []byte, cipherConfig CipherConfig) ([]byte, error) {
143142
if !cipherConfig.IsSpec2022 {
144-
return nil
143+
return nil, nil
145144
}
146145

147-
b := make([]byte, 1+8+len(clientSalt))
146+
// Read first payload chunk.
147+
if err := r.EnsureLeftover(); err != nil {
148+
return nil, err
149+
}
150+
b := r.LeftoverZeroCopy()
148151

149-
// Read response header
150-
_, err := io.ReadFull(r, b)
151-
if err != nil {
152-
return fmt.Errorf("failed to read response header: %w", err)
152+
headerLen := 1 + 8 + len(clientSalt)
153+
if len(b) < headerLen {
154+
return nil, ErrIncompleteHeaderInFirstChunk
153155
}
154156

155157
// Verify type
156158
if b[0] != HeaderTypeServerStream {
157-
return ErrTypeMismatch
159+
return nil, ErrTypeMismatch
158160
}
159161

160162
// Verify timestamp
161163
epoch := int64(binary.BigEndian.Uint64(b[1 : 1+8]))
162164
nowEpoch := time.Now().Unix()
163165
diff := epoch - nowEpoch
164166
if diff < -30 || diff > 30 {
165-
return ErrBadTimestamp
167+
return nil, ErrBadTimestamp
166168
}
167169

168170
// Verify client salt
169-
n := bytes.Compare(clientSalt, b[1+8:])
171+
n := bytes.Compare(clientSalt, b[1+8:headerLen])
170172
if n != 0 {
171-
return ErrClientSaltMismatch
173+
return nil, ErrClientSaltMismatch
172174
}
173175

174-
return nil
176+
return b[headerLen:], nil
175177
}
176178

177179
func WriteTCPRespHeader(dst, clientSalt []byte, cipherConfig CipherConfig) (n int) {

shadowsocks/packet.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
wgreplay "golang.zx2c4.com/wireguard/replay"
2828
)
2929

30-
// ErrShortPacket is identical to shadowaead.ErrShortPacket
3130
var ErrShortPacket = errors.New("short packet")
3231

3332
// Pack encrypts a Shadowsocks-UDP packet and returns a slice containing the encrypted packet.

0 commit comments

Comments
 (0)