Skip to content

Commit

Permalink
Use our own implementation of Unpack for UDP
Browse files Browse the repository at this point in the history
This avoids allocating 2 MB of memory for the go-shadowsocks2 replay
detection Bloom Filter Ring.
  • Loading branch information
Ben Schwartz committed Oct 2, 2020
1 parent fdfc52a commit 616163e
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 5 deletions.
5 changes: 2 additions & 3 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,8 @@ func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
if err != nil {
return 0, nil, err
}
// Avoid partially overlapping the plaintext and cipher slices since `Unpack` skips the salt
// when calling `AEAD.Open` (see https://golang.org/pkg/crypto/cipher/#AEAD).
buf, err := shadowaead.Unpack(cipherBuf[c.cipher.SaltSize():], cipherBuf[:n], c.cipher)
// Decrypt in-place.
buf, err := ss.Unpack(nil, cipherBuf[:n], c.cipher)
if err != nil {
return 0, nil, err
}
Expand Down
5 changes: 3 additions & 2 deletions service/udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

onet "github.com/Jigsaw-Code/outline-ss-server/net"
"github.com/Jigsaw-Code/outline-ss-server/service/metrics"
ss "github.com/Jigsaw-Code/outline-ss-server/shadowsocks"
logging "github.com/op/go-logging"
"github.com/shadowsocks/go-shadowsocks2/shadowaead"
"github.com/shadowsocks/go-shadowsocks2/socks"
Expand Down Expand Up @@ -56,7 +57,7 @@ func findAccessKeyUDP(clientIP net.IP, dst, src []byte, cipherList CipherList) (
_, snapshot := cipherList.SnapshotForClientIP(clientIP)
for ci, entry := range snapshot {
id, cipher := entry.Value.(*CipherEntry).ID, entry.Value.(*CipherEntry).Cipher
buf, err := shadowaead.Unpack(dst, src, cipher)
buf, err := ss.Unpack(dst, src, cipher)
if err != nil {
debugUDP(id, "Failed to unpack: %v", err)
continue
Expand Down Expand Up @@ -195,7 +196,7 @@ func (s *udpService) Serve(clientConn net.PacketConn) error {
targetConn = nm.Add(clientAddr, clientConn, cipher, udpConn, clientLocation, keyID)
} else {
unpackStart := time.Now()
textData, err = shadowaead.Unpack(textBuf, cipherData, targetConn.cipher)
textData, err = ss.Unpack(nil, cipherData, targetConn.cipher)
timeToCipher = time.Now().Sub(unpackStart)
if err != nil {
return onet.NewConnectionError("ERR_CIPHER", "Failed to unpack data from client", err)
Expand Down
51 changes: 51 additions & 0 deletions shadowsocks/packet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2020 Jigsaw Operations LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.import "github.com/shadowsocks/go-shadowsocks2/shadowaead"

package shadowsocks

import (
"io"

"github.com/shadowsocks/go-shadowsocks2/shadowaead"
)

// This array must be at least service.maxNonceSize bytes.
var zeroNonce [12]byte

// Unpack decrypts a Shadowsocks-UDP packet and returns a slice containing the decrypted payload or an error.
// If dst is present, it is used to store the plaintext, and must have enough capacity.
// If dst is nil, decryption proceeds in-place.
// This function is needed because shadowaead.Unpack() embeds its own replay detection,
// which we do not always want, especially on memory-constrained clients.
func Unpack(dst, pkt []byte, cipher shadowaead.Cipher) ([]byte, error) {
saltSize := cipher.SaltSize()
if len(pkt) < saltSize {
return nil, shadowaead.ErrShortPacket
}
salt := pkt[:saltSize]
aead, err := cipher.Decrypter(salt)
if err != nil {
return nil, err
}
msg := pkt[saltSize:]
if len(msg) < aead.Overhead() {
return nil, shadowaead.ErrShortPacket
}
if dst == nil {
dst = msg
} else if len(dst)+aead.Overhead() < len(msg) {
return nil, io.ErrShortBuffer
}
return aead.Open(dst[:0], zeroNonce[:aead.NonceSize()], msg, nil)
}

0 comments on commit 616163e

Please sign in to comment.