mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 09:25:41 +08:00
ss: use internal pkg instead
This commit is contained in:
parent
32990ebf77
commit
dad45afb17
@ -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
|
||||
|
@ -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
6
go.mod
@ -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
8
go.sum
@ -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=
|
||||
|
2
main.go
2
main.go
@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
version = "0.12.1"
|
||||
version = "0.12.2"
|
||||
config = parseConfig()
|
||||
)
|
||||
|
||||
|
@ -36,11 +36,13 @@ func NewConn(c net.Conn) *Conn {
|
||||
}
|
||||
|
||||
// Reader returns the internal bufio.Reader.
|
||||
func (c *Conn) Reader() *bufio.Reader { return c.r }
|
||||
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) }
|
||||
func (c *Conn) Peek(n int) ([]byte, error) { return c.r.Peek(n) }
|
||||
|
||||
// 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))
|
||||
|
@ -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
140
proxy/ss/internal/cipher.go
Normal 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]
|
||||
}
|
92
proxy/ss/internal/shadowaead/cipher.go
Normal file
92
proxy/ss/internal/shadowaead/cipher.go
Normal 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
|
||||
}
|
67
proxy/ss/internal/shadowaead/conn.go
Normal file
67
proxy/ss/internal/shadowaead/conn.go
Normal 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)
|
||||
}
|
97
proxy/ss/internal/shadowaead/packet.go
Normal file
97
proxy/ss/internal/shadowaead/packet.go
Normal 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
|
||||
}
|
151
proxy/ss/internal/shadowaead/stream.go
Normal file
151
proxy/ss/internal/shadowaead/stream.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
134
proxy/ss/internal/shadowstream/cipher.go
Normal file
134
proxy/ss/internal/shadowstream/cipher.go
Normal 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
|
||||
}
|
62
proxy/ss/internal/shadowstream/conn.go
Normal file
62
proxy/ss/internal/shadowstream/conn.go
Normal 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)
|
||||
}
|
80
proxy/ss/internal/shadowstream/packet.go
Normal file
80
proxy/ss/internal/shadowstream/packet.go
Normal 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
|
||||
}
|
55
proxy/ss/internal/shadowstream/stream.go
Normal file
55
proxy/ss/internal/shadowstream/stream.go
Normal 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
|
||||
}
|
@ -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(), " ")
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
func (w *aeadWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
for {
|
||||
buf := w.buf
|
||||
payloadBuf := buf[lenSize : lenSize+chunkSize-w.Overhead()]
|
||||
var lenBuf [lenSize]byte
|
||||
var writeLen, dataLen int
|
||||
|
||||
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.count++
|
||||
|
||||
_, ew := w.Writer.Write(buf)
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
nonce := w.nonce[:w.NonceSize()]
|
||||
for left := len(b); left != 0; {
|
||||
writeLen = left + w.Overhead()
|
||||
if writeLen > chunkSize {
|
||||
writeLen = chunkSize
|
||||
}
|
||||
dataLen = writeLen - w.Overhead()
|
||||
|
||||
if er != nil {
|
||||
if er != io.EOF { // ignore EOF as per io.ReaderFrom contract
|
||||
err = er
|
||||
}
|
||||
binary.BigEndian.PutUint16(lenBuf[:], uint16(writeLen))
|
||||
binary.BigEndian.PutUint16(nonce[:2], w.count)
|
||||
|
||||
w.Seal(buf[:0], nonce, b[n:n+dataLen], nil)
|
||||
w.count++
|
||||
|
||||
if _, err = (&net.Buffers{lenBuf[:], buf[:writeLen]}).WriteTo(w.Writer); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
n += dataLen
|
||||
left -= dataLen
|
||||
}
|
||||
|
||||
return n, err
|
||||
return
|
||||
}
|
||||
|
||||
type aeadReader struct {
|
||||
io.Reader
|
||||
cipher.AEAD
|
||||
nonce []byte
|
||||
buf []byte
|
||||
leftover []byte
|
||||
count uint16
|
||||
iv []byte
|
||||
nonce [32]byte
|
||||
count uint16
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return m, err
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user