ss: use internal pkg instead

This commit is contained in:
nadoo 2020-10-26 21:43:56 +08:00
parent 32990ebf77
commit dad45afb17
19 changed files with 1012 additions and 155 deletions

View File

@ -88,7 +88,7 @@ glider -h
<summary>click to see details</summary>
```bash
glider 0.12.1 usage:
glider 0.12.2 usage:
-checkdisabledonly
check disabled fowarders only
-checkinterval int
@ -159,7 +159,7 @@ Available methods for ss:
AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5
Alias:
chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305
Plain: DUMMY
Plain: NONE
SSR scheme:
ssr://method:pass@host:port?protocol=xxx&protocol_param=yyy&obfs=zzz&obfs_param=xyz

View File

@ -150,7 +150,7 @@ func usage() {
fmt.Fprintf(w, " AES-128-CFB AES-128-CTR AES-192-CFB AES-192-CTR AES-256-CFB AES-256-CTR CHACHA20-IETF XCHACHA20 CHACHA20 RC4-MD5\n")
fmt.Fprintf(w, " Alias:\n")
fmt.Fprintf(w, " chacha20-ietf-poly1305 = AEAD_CHACHA20_POLY1305, xchacha20-ietf-poly1305 = AEAD_XCHACHA20_POLY1305\n")
fmt.Fprintf(w, " Plain: DUMMY\n")
fmt.Fprintf(w, " Plain: NONE\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, "SSR scheme:\n")

6
go.mod
View File

@ -3,16 +3,16 @@ module github.com/nadoo/glider
go 1.15
require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
github.com/insomniacslk/dhcp v0.0.0-20200922210017-67c425063dca
github.com/mzz2017/shadowsocksR v1.0.0
github.com/nadoo/conflag v0.2.3
github.com/nadoo/go-shadowsocks2 v0.1.2
github.com/nadoo/ipset v0.3.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/xtaci/kcp-go/v5 v5.6.1
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127 // indirect
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd // indirect
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 // indirect
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 // indirect
golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
)

8
go.sum
View File

@ -141,8 +141,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgN
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127 h1:pZPp9+iYUqwYKLjht0SDBbRCRK/9gAXDy7pz5fRDpjo=
golang.org/x/net v0.0.0-20201024042810-be3efd7ff127/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -168,8 +168,8 @@ golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd h1:WgqgiQvkiZWz7XLhphjt2GI2GcGCTIZs9jqXMWmH+oc=
golang.org/x/sys v0.0.0-20201022201747-fb209a7c41cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 h1:iCaAy5bMeEvwANu3YnJfWwI0kWAGkEa2RXPdweI/ysk=
golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -18,7 +18,7 @@ import (
)
var (
version = "0.12.1"
version = "0.12.2"
config = parseConfig()
)

View File

@ -37,10 +37,12 @@ func NewConn(c net.Conn) *Conn {
// Reader returns the internal bufio.Reader.
func (c *Conn) Reader() *bufio.Reader { return c.r }
func (c *Conn) Read(p []byte) (int, error) { return c.r.Read(p) }
// Peek returns the next n bytes without advancing the reader.
func (c *Conn) Peek(n int) ([]byte, error) { return c.r.Peek(n) }
func (c *Conn) Read(p []byte) (int, error) { return c.r.Read(p) }
// WriteTo implements io.WriterTo.
func (c *Conn) WriteTo(w io.Writer) (n int64, err error) { return c.r.WriteTo(w) }
// Relay relays between left and right.
@ -71,6 +73,30 @@ func Relay(left, right net.Conn) error {
return nil
}
// Copy copies from src to dst.
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
dst = underlyingWriter(dst)
switch runtime.GOOS {
case "linux", "windows", "dragonfly", "freebsd", "solaris":
if _, ok := dst.(*net.TCPConn); ok && worthTry(src) {
if wt, ok := src.(io.WriterTo); ok {
return wt.WriteTo(dst)
}
if rt, ok := dst.(io.ReaderFrom); ok {
return rt.ReadFrom(src)
}
}
}
return CopyBuffer(dst, src)
}
func underlyingWriter(c io.Writer) io.Writer {
if wrap, ok := c.(*Conn); ok {
return wrap.Conn
}
return c
}
func worthTry(src io.Reader) bool {
switch v := src.(type) {
case *net.TCPConn, *net.UnixConn:
@ -90,30 +116,6 @@ func worthTry(src io.Reader) bool {
}
}
func underlyingWriter(c io.Writer) io.Writer {
if wrap, ok := c.(*Conn); ok {
return wrap.Conn
}
return c
}
// Copy copies from src to dst.
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
dst = underlyingWriter(dst)
switch runtime.GOOS {
case "linux", "windows", "dragonfly", "freebsd", "solaris":
if _, ok := dst.(*net.TCPConn); ok && worthTry(src) {
if wt, ok := src.(io.WriterTo); ok {
return wt.WriteTo(dst)
}
if rt, ok := dst.(io.ReaderFrom); ok {
return rt.ReadFrom(src)
}
}
}
return CopyBuffer(dst, src)
}
// CopyN copies n bytes (or until an error) from src to dst.
func CopyN(dst io.Writer, src io.Reader, n int64) (written int64, err error) {
written, err = Copy(dst, io.LimitReader(src, n))

View File

@ -42,7 +42,6 @@ func (s *SS) Dial(network, addr string) (net.Conn, error) {
}
return c, err
}
// DialUDP connects to the given address via the proxy.

140
proxy/ss/internal/cipher.go Normal file
View File

@ -0,0 +1,140 @@
package internal
import (
"crypto/md5"
"errors"
"net"
"strings"
"github.com/nadoo/glider/proxy/ss/internal/shadowaead"
"github.com/nadoo/glider/proxy/ss/internal/shadowstream"
)
type Cipher interface {
StreamConnCipher
PacketConnCipher
}
type StreamConnCipher interface {
StreamConn(net.Conn) net.Conn
}
type PacketConnCipher interface {
PacketConn(net.PacketConn) net.PacketConn
}
// ErrCipherNotSupported occurs when a cipher is not supported (likely because of security concerns).
var ErrCipherNotSupported = errors.New("cipher not supported")
// List of AEAD ciphers: key size in bytes and constructor
var aeadList = map[string]struct {
KeySize int
New func([]byte) (shadowaead.Cipher, error)
}{
"AEAD_AES_128_GCM": {16, shadowaead.AESGCM},
"AEAD_AES_192_GCM": {24, shadowaead.AESGCM},
"AEAD_AES_256_GCM": {32, shadowaead.AESGCM},
"AEAD_CHACHA20_POLY1305": {32, shadowaead.Chacha20Poly1305},
// http://shadowsocks.org/en/spec/AEAD-Ciphers.html
// not listed in spec
"AEAD_XCHACHA20_POLY1305": {32, shadowaead.XChacha20Poly1305},
}
// List of stream ciphers: key size in bytes and constructor
var streamList = map[string]struct {
KeySize int
New func(key []byte) (shadowstream.Cipher, error)
}{
"AES-128-CTR": {16, shadowstream.AESCTR},
"AES-192-CTR": {24, shadowstream.AESCTR},
"AES-256-CTR": {32, shadowstream.AESCTR},
"AES-128-CFB": {16, shadowstream.AESCFB},
"AES-192-CFB": {24, shadowstream.AESCFB},
"AES-256-CFB": {32, shadowstream.AESCFB},
"CHACHA20-IETF": {32, shadowstream.Chacha20IETF},
"XCHACHA20": {32, shadowstream.Xchacha20},
// http://shadowsocks.org/en/spec/Stream-Ciphers.html
// marked as "DO NOT USE"
"CHACHA20": {32, shadowstream.ChaCha20},
"RC4-MD5": {16, shadowstream.RC4MD5},
}
// PickCipher returns a Cipher of the given name. Derive key from password if given key is empty.
func PickCipher(name string, key []byte, password string) (Cipher, error) {
name = strings.ToUpper(name)
switch name {
case "DUMMY", "NONE":
return &dummy{}, nil
case "CHACHA20-IETF-POLY1305":
name = "AEAD_CHACHA20_POLY1305"
case "XCHACHA20-IETF-POLY1305":
name = "AEAD_XCHACHA20_POLY1305"
case "AES-128-GCM":
name = "AEAD_AES_128_GCM"
case "AES-192-GCM":
name = "AEAD_AES_192_GCM"
case "AES-256-GCM":
name = "AEAD_AES_256_GCM"
}
if choice, ok := aeadList[name]; ok {
if len(key) == 0 {
key = kdf(password, choice.KeySize)
}
if len(key) != choice.KeySize {
return nil, shadowaead.KeySizeError(choice.KeySize)
}
aead, err := choice.New(key)
return &aeadCipher{aead}, err
}
if choice, ok := streamList[name]; ok {
if len(key) == 0 {
key = kdf(password, choice.KeySize)
}
if len(key) != choice.KeySize {
return nil, shadowstream.KeySizeError(choice.KeySize)
}
ciph, err := choice.New(key)
return &streamCipher{ciph}, err
}
return nil, ErrCipherNotSupported
}
type aeadCipher struct{ shadowaead.Cipher }
func (aead *aeadCipher) StreamConn(c net.Conn) net.Conn { return shadowaead.NewConn(c, aead) }
func (aead *aeadCipher) PacketConn(c net.PacketConn) net.PacketConn {
return shadowaead.NewPacketConn(c, aead)
}
type streamCipher struct{ shadowstream.Cipher }
func (ciph *streamCipher) StreamConn(c net.Conn) net.Conn { return shadowstream.NewConn(c, ciph) }
func (ciph *streamCipher) PacketConn(c net.PacketConn) net.PacketConn {
return shadowstream.NewPacketConn(c, ciph)
}
// dummy cipher does not encrypt
type dummy struct{}
func (dummy) StreamConn(c net.Conn) net.Conn { return c }
func (dummy) PacketConn(c net.PacketConn) net.PacketConn { return c }
// key-derivation function from original Shadowsocks
func kdf(password string, keyLen int) []byte {
var b, prev []byte
h := md5.New()
for len(b) < keyLen {
h.Write(prev)
h.Write([]byte(password))
b = h.Sum(b)
prev = b[len(b)-h.Size():]
h.Reset()
}
return b[:keyLen]
}

View File

@ -0,0 +1,92 @@
package shadowaead
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"io"
"strconv"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
)
type Cipher interface {
KeySize() int
SaltSize() int
Encrypter(salt []byte) (cipher.AEAD, error)
Decrypter(salt []byte) (cipher.AEAD, error)
}
type KeySizeError int
func (e KeySizeError) Error() string {
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
}
func hkdfSHA1(secret, salt, info, outkey []byte) {
r := hkdf.New(sha1.New, secret, salt, info)
if _, err := io.ReadFull(r, outkey); err != nil {
panic(err) // should never happen
}
}
type metaCipher struct {
psk []byte
makeAEAD func(key []byte) (cipher.AEAD, error)
}
func (a *metaCipher) KeySize() int { return len(a.psk) }
func (a *metaCipher) SaltSize() int {
if ks := a.KeySize(); ks > 16 {
return ks
}
return 16
}
func (a *metaCipher) Encrypter(salt []byte) (cipher.AEAD, error) {
subkey := make([]byte, a.KeySize())
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
return a.makeAEAD(subkey)
}
func (a *metaCipher) Decrypter(salt []byte) (cipher.AEAD, error) {
subkey := make([]byte, a.KeySize())
hkdfSHA1(a.psk, salt, []byte("ss-subkey"), subkey)
return a.makeAEAD(subkey)
}
func aesGCM(key []byte) (cipher.AEAD, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(blk)
}
// AESGCM creates a new Cipher with a pre-shared key. len(psk) must be
// one of 16, 24, or 32 to select AES-128/196/256-GCM.
func AESGCM(psk []byte) (Cipher, error) {
switch l := len(psk); l {
case 16, 24, 32: // AES 128/196/256
default:
return nil, aes.KeySizeError(l)
}
return &metaCipher{psk: psk, makeAEAD: aesGCM}, nil
}
// Chacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
// must be 32.
func Chacha20Poly1305(psk []byte) (Cipher, error) {
if len(psk) != chacha20poly1305.KeySize {
return nil, KeySizeError(chacha20poly1305.KeySize)
}
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.New}, nil
}
// XChacha20Poly1305 creates a new Cipher with a pre-shared key. len(psk)
// must be 32.
func XChacha20Poly1305(psk []byte) (Cipher, error) {
if len(psk) != chacha20poly1305.KeySize {
return nil, KeySizeError(chacha20poly1305.KeySize)
}
return &metaCipher{psk: psk, makeAEAD: chacha20poly1305.NewX}, nil
}

View File

@ -0,0 +1,67 @@
package shadowaead
import (
"crypto/rand"
"io"
"net"
)
type streamConn struct {
net.Conn
Cipher
r *reader
w *writer
}
// NewConn wraps a stream-oriented net.Conn with cipher.
func NewConn(c net.Conn, ciph Cipher) net.Conn { return &streamConn{Conn: c, Cipher: ciph} }
func (c *streamConn) initReader() error {
salt := make([]byte, c.SaltSize())
if _, err := io.ReadFull(c.Conn, salt); err != nil {
return err
}
aead, err := c.Decrypter(salt)
if err != nil {
return err
}
c.r = newReader(c.Conn, aead)
return nil
}
func (c *streamConn) Read(b []byte) (int, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.Read(b)
}
func (c *streamConn) initWriter() error {
salt := make([]byte, c.SaltSize())
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return err
}
aead, err := c.Encrypter(salt)
if err != nil {
return err
}
_, err = c.Conn.Write(salt)
if err != nil {
return err
}
c.w = newWriter(c.Conn, aead)
return nil
}
func (c *streamConn) Write(b []byte) (int, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.Write(b)
}

View File

@ -0,0 +1,97 @@
package shadowaead
import (
"crypto/rand"
"errors"
"io"
"net"
"sync"
)
// ErrShortPacket means that the packet is too short for a valid encrypted packet.
var ErrShortPacket = errors.New("short packet")
var _zerononce [128]byte // read-only. 128 bytes is more than enough.
// Pack encrypts plaintext using Cipher with a randomly generated salt and
// returns a slice of dst containing the encrypted packet and any error occurred.
// Ensure len(dst) >= ciph.SaltSize() + len(plaintext) + aead.Overhead().
func Pack(dst, plaintext []byte, ciph Cipher) ([]byte, error) {
saltSize := ciph.SaltSize()
salt := dst[:saltSize]
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return nil, err
}
aead, err := ciph.Encrypter(salt)
if err != nil {
return nil, err
}
if len(dst) < saltSize+len(plaintext)+aead.Overhead() {
return nil, io.ErrShortBuffer
}
b := aead.Seal(dst[saltSize:saltSize], _zerononce[:aead.NonceSize()], plaintext, nil)
return dst[:saltSize+len(b)], nil
}
// Unpack decrypts pkt using Cipher and returns a slice of dst containing the decrypted payload and any error occurred.
// Ensure len(dst) >= len(pkt) - aead.SaltSize() - aead.Overhead().
func Unpack(dst, pkt []byte, ciph Cipher) ([]byte, error) {
saltSize := ciph.SaltSize()
if len(pkt) < saltSize {
return nil, ErrShortPacket
}
salt := pkt[:saltSize]
aead, err := ciph.Decrypter(salt)
if err != nil {
return nil, err
}
if len(pkt) < saltSize+aead.Overhead() {
return nil, ErrShortPacket
}
if saltSize+len(dst)+aead.Overhead() < len(pkt) {
return nil, io.ErrShortBuffer
}
b, err := aead.Open(dst[:0], _zerononce[:aead.NonceSize()], pkt[saltSize:], nil)
return b, err
}
type packetConn struct {
net.PacketConn
Cipher
sync.Mutex
buf []byte // write lock
}
// NewPacketConn wraps a net.PacketConn with cipher
func NewPacketConn(c net.PacketConn, ciph Cipher) net.PacketConn {
const maxPacketSize = 64 * 1024
return &packetConn{PacketConn: c, Cipher: ciph, buf: make([]byte, maxPacketSize)}
}
// WriteTo encrypts b and write to addr using the embedded PacketConn.
func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
c.Lock()
defer c.Unlock()
buf, err := Pack(c.buf, b, c)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(buf, addr)
return len(b), err
}
// ReadFrom reads from the embedded PacketConn and decrypts into b.
func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := c.PacketConn.ReadFrom(b)
if err != nil {
return n, addr, err
}
bb, err := Unpack(b[c.Cipher.SaltSize():], b[:n], c)
if err != nil {
return n, addr, err
}
copy(b, bb)
return len(bb), addr, err
}

View File

@ -0,0 +1,151 @@
// protocol:
// format: [encrypted payload length] [Overhead] [encrypted payload] [Overhead]
// sizes: 2 bytes, aead.Overhead() bytes, n bytes, aead.Overhead() bytes
// max(n): 0x3FFF
package shadowaead
import (
"crypto/cipher"
"encoding/binary"
"io"
"github.com/nadoo/glider/pool"
)
const (
lenSize = 2
// max payload size: 16383 from ss-libev aead.c: CHUNK_SIZE_MASK
maxPayload = 0x3FFF
// buf size, shoud enough to save lenSize + aead.Overhead() + maxPayload + aead.Overhead()
bufSize = 17 << 10
)
type writer struct {
io.Writer
cipher.AEAD
nonce [32]byte
buf []byte
}
// NewWriter wraps an io.Writer with AEAD encryption.
func NewWriter(w io.Writer, aead cipher.AEAD) io.Writer { return newWriter(w, aead) }
func newWriter(w io.Writer, aead cipher.AEAD) *writer {
return &writer{Writer: w, AEAD: aead}
}
// Write encrypts p and writes to the embedded io.Writer.
func (w *writer) Write(p []byte) (n int, err error) {
buf := pool.GetBuffer(bufSize)
defer pool.PutBuffer(buf)
nonce := w.nonce[:w.NonceSize()]
encLenSize := lenSize + w.Overhead()
for nw := maxPayload; n < len(p); n += nw {
if left := len(p) - n; left < maxPayload {
nw = left
}
binary.BigEndian.PutUint16(buf[:lenSize], uint16(nw))
w.Seal(buf[:0], nonce, buf[:lenSize], nil)
increment(nonce)
w.Seal(buf[:encLenSize], nonce, p[n:n+nw], nil)
increment(nonce)
if _, err = w.Writer.Write(buf[:encLenSize+nw+w.Overhead()]); err != nil {
return
}
}
return
}
type reader struct {
io.Reader
cipher.AEAD
nonce [32]byte
buf []byte
offset int
}
// NewReader wraps an io.Reader with AEAD decryption.
func NewReader(r io.Reader, aead cipher.AEAD) io.Reader { return newReader(r, aead) }
func newReader(r io.Reader, aead cipher.AEAD) *reader {
return &reader{Reader: r, AEAD: aead}
}
// NOTE: len(p) MUST >= max payload size + AEAD overhead.
func (r *reader) read(p []byte) (int, error) {
nonce := r.nonce[:r.NonceSize()]
// read encrypted lenSize + overhead
p = p[:lenSize+r.Overhead()]
if _, err := io.ReadFull(r.Reader, p); err != nil {
return 0, err
}
// decrypt lenSize
_, err := r.Open(p[:0], nonce, p, nil)
increment(nonce)
if err != nil {
return 0, err
}
// get payload size
size := int(binary.BigEndian.Uint16(p[:lenSize]))
// read encrypted payload + overhead
p = p[:size+r.Overhead()]
if _, err := io.ReadFull(r.Reader, p); err != nil {
return 0, err
}
// decrypt payload
_, err = r.Open(p[:0], nonce, p, nil)
increment(nonce)
if err != nil {
return 0, err
}
return size, nil
}
func (r *reader) Read(p []byte) (int, error) {
if r.buf == nil {
if len(p) >= maxPayload+r.Overhead() {
return r.read(p)
}
buf := pool.GetBuffer(bufSize)
n, err := r.read(buf)
if err != nil {
pool.PutBuffer(buf)
return 0, err
}
r.buf = buf[:n]
r.offset = 0
}
n := copy(p, r.buf[r.offset:])
r.offset += n
if r.offset == len(r.buf) {
pool.PutBuffer(r.buf)
r.buf = nil
}
return n, nil
}
// increment little-endian encoded unsigned integer b. Wrap around on overflow.
func increment(b []byte) {
for i := range b {
b[i]++
if b[i] != 0 {
return
}
}
}

View File

@ -0,0 +1,134 @@
package shadowstream
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rc4"
"strconv"
"github.com/aead/chacha20"
"github.com/aead/chacha20/chacha"
)
// Cipher generates a pair of stream ciphers for encryption and decryption.
type Cipher interface {
IVSize() int
Encrypter(iv []byte) cipher.Stream
Decrypter(iv []byte) cipher.Stream
}
type KeySizeError int
func (e KeySizeError) Error() string {
return "key size error: need " + strconv.Itoa(int(e)) + " bytes"
}
// CTR mode
type ctrStream struct{ cipher.Block }
func (b *ctrStream) IVSize() int { return b.BlockSize() }
func (b *ctrStream) Decrypter(iv []byte) cipher.Stream { return b.Encrypter(iv) }
func (b *ctrStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCTR(b, iv) }
func AESCTR(key []byte) (Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &ctrStream{blk}, nil
}
// CFB mode
type cfbStream struct{ cipher.Block }
func (b *cfbStream) IVSize() int { return b.BlockSize() }
func (b *cfbStream) Decrypter(iv []byte) cipher.Stream { return cipher.NewCFBDecrypter(b, iv) }
func (b *cfbStream) Encrypter(iv []byte) cipher.Stream { return cipher.NewCFBEncrypter(b, iv) }
func AESCFB(key []byte) (Cipher, error) {
blk, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return &cfbStream{blk}, nil
}
// IETF-variant of chacha20
type chacha20ietfkey []byte
func (k chacha20ietfkey) IVSize() int { return chacha.INonceSize }
func (k chacha20ietfkey) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k chacha20ietfkey) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewCipher(iv, k)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func Chacha20IETF(key []byte) (Cipher, error) {
if len(key) != chacha.KeySize {
return nil, KeySizeError(chacha.KeySize)
}
return chacha20ietfkey(key), nil
}
type xchacha20key []byte
func (k xchacha20key) IVSize() int { return chacha.XNonceSize }
func (k xchacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k xchacha20key) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewCipher(iv, k)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func Xchacha20(key []byte) (Cipher, error) {
if len(key) != chacha.KeySize {
return nil, KeySizeError(chacha.KeySize)
}
return xchacha20key(key), nil
}
// reference: https://github.com/shadowsocks/shadowsocks-go/blob/master/shadowsocks/encrypt.go
type chacha20key []byte
func (k chacha20key) IVSize() int { return chacha.NonceSize }
func (k chacha20key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k chacha20key) Encrypter(iv []byte) cipher.Stream {
ciph, err := chacha20.NewCipher(iv, k)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func ChaCha20(key []byte) (Cipher, error) {
if len(key) != chacha.KeySize {
return nil, KeySizeError(chacha.KeySize)
}
return chacha20key(key), nil
}
type rc4Md5Key []byte
func (k rc4Md5Key) IVSize() int { return 16 }
func (k rc4Md5Key) Decrypter(iv []byte) cipher.Stream { return k.Encrypter(iv) }
func (k rc4Md5Key) Encrypter(iv []byte) cipher.Stream {
h := md5.New()
h.Write([]byte(k))
h.Write(iv)
rc4key := h.Sum(nil)
ciph, err := rc4.NewCipher(rc4key)
if err != nil {
panic(err) // should never happen
}
return ciph
}
func RC4MD5(key []byte) (Cipher, error) {
return rc4Md5Key(key), nil
}

View File

@ -0,0 +1,62 @@
package shadowstream
import (
"crypto/rand"
"io"
"net"
)
type conn struct {
net.Conn
Cipher
r *reader
w *writer
}
// NewConn wraps a stream-oriented net.Conn with stream cipher encryption/decryption.
func NewConn(c net.Conn, ciph Cipher) net.Conn {
return &conn{Conn: c, Cipher: ciph}
}
func (c *conn) initReader() error {
if c.r == nil {
iv := make([]byte, c.IVSize())
if _, err := io.ReadFull(c.Conn, iv); err != nil {
return err
}
c.r = &reader{Reader: c.Conn, Stream: c.Decrypter(iv)}
}
return nil
}
func (c *conn) Read(b []byte) (int, error) {
if c.r == nil {
if err := c.initReader(); err != nil {
return 0, err
}
}
return c.r.Read(b)
}
func (c *conn) initWriter() error {
if c.w == nil {
iv := make([]byte, c.IVSize())
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return err
}
if _, err := c.Conn.Write(iv); err != nil {
return err
}
c.w = &writer{Writer: c.Conn, Stream: c.Encrypter(iv)}
}
return nil
}
func (c *conn) Write(b []byte) (int, error) {
if c.w == nil {
if err := c.initWriter(); err != nil {
return 0, err
}
}
return c.w.Write(b)
}

View File

@ -0,0 +1,80 @@
package shadowstream
import (
"crypto/rand"
"errors"
"io"
"net"
"sync"
)
// ErrShortPacket means the packet is too short to be a valid encrypted packet.
var ErrShortPacket = errors.New("short packet")
// Pack encrypts plaintext using stream cipher s and a random IV.
// Returns a slice of dst containing random IV and ciphertext.
// Ensure len(dst) >= s.IVSize() + len(plaintext).
func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) {
if len(dst) < s.IVSize()+len(plaintext) {
return nil, io.ErrShortBuffer
}
iv := dst[:s.IVSize()]
_, err := io.ReadFull(rand.Reader, iv)
if err != nil {
return nil, err
}
s.Encrypter(iv).XORKeyStream(dst[len(iv):], plaintext)
return dst[:len(iv)+len(plaintext)], nil
}
// Unpack decrypts pkt using stream cipher s.
// Returns a slice of dst containing decrypted plaintext.
func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) {
if len(pkt) < s.IVSize() {
return nil, ErrShortPacket
}
if len(dst) < len(pkt)-s.IVSize() {
return nil, io.ErrShortBuffer
}
iv := pkt[:s.IVSize()]
s.Decrypter(iv).XORKeyStream(dst, pkt[len(iv):])
return dst[:len(pkt)-len(iv)], nil
}
type packetConn struct {
net.PacketConn
Cipher
buf []byte
sync.Mutex // write lock
}
// NewPacketConn wraps a net.PacketConn with stream cipher encryption/decryption.
func NewPacketConn(c net.PacketConn, ciph Cipher) net.PacketConn {
return &packetConn{PacketConn: c, Cipher: ciph, buf: make([]byte, 64*1024)}
}
func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
c.Lock()
defer c.Unlock()
buf, err := Pack(c.buf, b, c.Cipher)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(buf, addr)
return len(b), err
}
func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, addr, err := c.PacketConn.ReadFrom(b)
if err != nil {
return n, addr, err
}
bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher)
if err != nil {
return n, addr, err
}
copy(b, bb)
return len(bb), addr, err
}

View File

@ -0,0 +1,55 @@
package shadowstream
import (
"crypto/cipher"
"io"
"github.com/nadoo/glider/pool"
)
const bufSize = 32 * 1024
type writer struct {
io.Writer
cipher.Stream
}
// NewWriter wraps an io.Writer with stream cipher encryption.
func NewWriter(w io.Writer, s cipher.Stream) io.Writer {
return &writer{Writer: w, Stream: s}
}
func (w *writer) Write(p []byte) (n int, err error) {
buf := pool.GetBuffer(bufSize)
defer pool.PutBuffer(buf)
for nw := 0; n < len(p) && err == nil; n += nw {
end := n + len(buf)
if end > len(p) {
end = len(p)
}
w.XORKeyStream(buf, p[n:end])
nw, err = w.Writer.Write(buf[:end-n])
}
return
}
type reader struct {
io.Reader
cipher.Stream
}
// NewReader wraps an io.Reader with stream cipher decryption.
func NewReader(r io.Reader, s cipher.Stream) io.Reader {
return &reader{Reader: r, Stream: s}
}
func (r *reader) Read(p []byte) (int, error) {
n, err := r.Reader.Read(p)
if err != nil {
return 0, err
}
p = p[:n]
r.XORKeyStream(p, p)
return n, nil
}

View File

@ -2,12 +2,10 @@ package ss
import (
"net/url"
"strings"
"github.com/nadoo/go-shadowsocks2/core"
"github.com/nadoo/glider/log"
"github.com/nadoo/glider/proxy"
"github.com/nadoo/glider/proxy/ss/internal"
)
// SS is a base ss struct.
@ -16,7 +14,7 @@ type SS struct {
proxy proxy.Proxy
addr string
core.Cipher
internal.Cipher
}
func init() {
@ -36,7 +34,7 @@ func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
method := u.User.Username()
pass, _ := u.User.Password()
ciph, err := core.PickCipher(method, nil, pass)
ciph, err := internal.PickCipher(method, nil, pass)
if err != nil {
log.Fatalf("[ss] PickCipher for '%s', error: %s", method, err)
}
@ -50,8 +48,3 @@ func NewSS(s string, d proxy.Dialer, p proxy.Proxy) (*SS, error) {
return ss, nil
}
// ListCipher returns all the ciphers supported.
func ListCipher() string {
return strings.Join(core.ListCipher(), " ")
}

View File

@ -1,136 +1,127 @@
// protocol:
// format: [data length] [data]
// sizes: 2 bytes, n bytes
// max(n): 2^14 bytes
// [data]: [encrypted payload] + [Overhead]
package vmess
import (
"bytes"
"crypto/cipher"
"encoding/binary"
"io"
"net"
"github.com/nadoo/glider/pool"
)
type aeadWriter struct {
io.Writer
cipher.AEAD
nonce []byte
buf []byte
nonce [32]byte
count uint16
iv []byte
}
// AEADWriter returns a aead writer.
func AEADWriter(w io.Writer, aead cipher.AEAD, iv []byte) io.Writer {
return &aeadWriter{
Writer: w,
AEAD: aead,
buf: make([]byte, lenSize+chunkSize),
nonce: make([]byte, aead.NonceSize()),
count: 0,
iv: iv,
}
aw := &aeadWriter{Writer: w, AEAD: aead}
copy(aw.nonce[2:], iv[2:12])
return aw
}
func (w *aeadWriter) Write(b []byte) (int, error) {
n, err := w.ReadFrom(bytes.NewBuffer(b))
return int(n), err
func (w *aeadWriter) Write(b []byte) (n int, err error) {
buf := pool.GetBuffer(chunkSize)
defer pool.PutBuffer(buf)
var lenBuf [lenSize]byte
var writeLen, dataLen int
nonce := w.nonce[:w.NonceSize()]
for left := len(b); left != 0; {
writeLen = left + w.Overhead()
if writeLen > chunkSize {
writeLen = chunkSize
}
dataLen = writeLen - w.Overhead()
func (w *aeadWriter) ReadFrom(r io.Reader) (n int64, err error) {
for {
buf := w.buf
payloadBuf := buf[lenSize : lenSize+chunkSize-w.Overhead()]
binary.BigEndian.PutUint16(lenBuf[:], uint16(writeLen))
binary.BigEndian.PutUint16(nonce[:2], w.count)
nr, er := r.Read(payloadBuf)
if nr > 0 {
n += int64(nr)
buf = buf[:lenSize+nr+w.Overhead()]
payloadBuf = payloadBuf[:nr]
binary.BigEndian.PutUint16(buf[:lenSize], uint16(nr+w.Overhead()))
binary.BigEndian.PutUint16(w.nonce[:2], w.count)
copy(w.nonce[2:], w.iv[2:12])
w.Seal(payloadBuf[:0], w.nonce, payloadBuf, nil)
w.Seal(buf[:0], nonce, b[n:n+dataLen], nil)
w.count++
_, ew := w.Writer.Write(buf)
if ew != nil {
err = ew
if _, err = (&net.Buffers{lenBuf[:], buf[:writeLen]}).WriteTo(w.Writer); err != nil {
break
}
n += dataLen
left -= dataLen
}
if er != nil {
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
err = er
}
break
}
}
return n, err
return
}
type aeadReader struct {
io.Reader
cipher.AEAD
nonce []byte
buf []byte
leftover []byte
nonce [32]byte
count uint16
iv []byte
buf []byte
offset int
}
// AEADReader returns a aead reader.
func AEADReader(r io.Reader, aead cipher.AEAD, iv []byte) io.Reader {
return &aeadReader{
Reader: r,
AEAD: aead,
buf: make([]byte, lenSize+chunkSize),
nonce: make([]byte, aead.NonceSize()),
count: 0,
iv: iv,
}
ar := &aeadReader{Reader: r, AEAD: aead}
copy(ar.nonce[2:], iv[2:12])
return ar
}
func (r *aeadReader) Read(b []byte) (int, error) {
if len(r.leftover) > 0 {
n := copy(b, r.leftover)
r.leftover = r.leftover[n:]
return n, nil
}
// get length
_, err := io.ReadFull(r.Reader, r.buf[:lenSize])
if err != nil {
func (r *aeadReader) read(p []byte) (int, error) {
if _, err := io.ReadFull(r.Reader, p[:lenSize]); err != nil {
return 0, err
}
// if length == 0, then this is the end
l := binary.BigEndian.Uint16(r.buf[:lenSize])
if l == 0 {
return 0, nil
}
// get payload
buf := r.buf[:l]
_, err = io.ReadFull(r.Reader, buf)
if err != nil {
size := int(binary.BigEndian.Uint16(p[:lenSize]))
p = p[:size]
if _, err := io.ReadFull(r.Reader, p); err != nil {
return 0, err
}
binary.BigEndian.PutUint16(r.nonce[:2], r.count)
copy(r.nonce[2:], r.iv[2:12])
_, err = r.Open(buf[:0], r.nonce, buf, nil)
_, err := r.Open(p[:0], r.nonce[:r.NonceSize()], p, nil)
r.count++
if err != nil {
return 0, err
}
dataLen := int(l) - r.Overhead()
m := copy(b, r.buf[:dataLen])
if m < int(dataLen) {
r.leftover = r.buf[m:dataLen]
return size - r.Overhead(), nil
}
return m, err
func (r *aeadReader) Read(p []byte) (int, error) {
if r.buf == nil {
if len(p) >= chunkSize {
return r.read(p)
}
buf := pool.GetBuffer(chunkSize)
n, err := r.read(buf)
if err != nil || n == 0 {
pool.PutBuffer(buf)
return 0, err
}
r.buf = buf[:n]
r.offset = 0
}
n := copy(p, r.buf[r.offset:])
r.offset += n
if r.offset == len(r.buf) {
pool.PutBuffer(r.buf)
r.buf = nil
}
return n, nil
}

View File

@ -3,17 +3,17 @@ package vmess
import (
"encoding/binary"
"io"
"github.com/nadoo/glider/pool"
"net"
)
const (
lenSize = 2
chunkSize = 1 << 14 // 16384
chunkSize = 16 << 10
)
type chunkedWriter struct {
io.Writer
buf [lenSize]byte
}
// ChunkedWriter returns a chunked writer.
@ -21,28 +21,22 @@ func ChunkedWriter(w io.Writer) io.Writer {
return &chunkedWriter{Writer: w}
}
func (w *chunkedWriter) Write(b []byte) (n int, err error) {
buf := pool.GetBuffer(lenSize + chunkSize)
defer pool.PutBuffer(buf)
for left := len(b); left != 0; {
writeLen := left
if writeLen > chunkSize {
writeLen = chunkSize
func (w *chunkedWriter) Write(p []byte) (n int, err error) {
var dataLen int
for left := len(p); left != 0; {
dataLen = left
if dataLen > chunkSize {
dataLen = chunkSize
}
copy(buf[lenSize:], b[n:n+writeLen])
binary.BigEndian.PutUint16(buf[:lenSize], uint16(writeLen))
_, err = w.Writer.Write(buf[:lenSize+writeLen])
if err != nil {
binary.BigEndian.PutUint16(w.buf[:], uint16(dataLen))
if _, err = (&net.Buffers{w.buf[:], p[n : n+dataLen]}).WriteTo(w.Writer); err != nil {
break
}
n += writeLen
left -= writeLen
n += dataLen
left -= dataLen
}
return
}
@ -57,7 +51,7 @@ func ChunkedReader(r io.Reader) io.Reader {
return &chunkedReader{Reader: r}
}
func (r *chunkedReader) Read(b []byte) (int, error) {
func (r *chunkedReader) Read(p []byte) (int, error) {
if r.left == 0 {
// get length
_, err := io.ReadFull(r.Reader, r.buf[:lenSize])
@ -72,12 +66,12 @@ func (r *chunkedReader) Read(b []byte) (int, error) {
}
}
readLen := len(b)
readLen := len(p)
if readLen > r.left {
readLen = r.left
}
n, err := r.Reader.Read(b[:readLen])
n, err := r.Reader.Read(p[:readLen])
if err != nil {
return 0, err
}