Signed-off-by: arraykeys@gmail.com <arraykeys@gmail.com>

This commit is contained in:
arraykeys@gmail.com
2018-05-22 11:26:34 +08:00
parent f559fb1cae
commit 81ff3dadd5
6 changed files with 1 additions and 664 deletions

View File

@ -245,13 +245,8 @@ func initConfig() (err error) {
spsArgs.ParentKey = sps.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String()
spsArgs.LocalCompress = sps.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool()
spsArgs.ParentCompress = sps.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool()
spsArgs.SSMethod = sps.Flag("ss-method", "").Hidden().Short('h').Default("aes-256-cfb").String()
spsArgs.SSKey = sps.Flag("ss-key", "").Hidden().Short('j').Default("sspassword").String()
spsArgs.ParentSSMethod = sps.Flag("parent-ss-method", "").Hidden().Short('H').Default("aes-256-cfb").String()
spsArgs.ParentSSKey = sps.Flag("parent-ss-key", "").Hidden().Short('J').Default("sspassword").String()
spsArgs.DisableHTTP = sps.Flag("disable-http", "disable http(s) proxy").Default("false").Bool()
spsArgs.DisableSocks5 = sps.Flag("disable-socks", "disable socks proxy").Default("false").Bool()
spsArgs.DisableSS = sps.Flag("disable-ss", "").Hidden().Default("false").Bool()
//parse args
serviceName := kingpin.MustParse(app.Parse(os.Args[1:]))

View File

@ -232,13 +232,8 @@ type SPSArgs struct {
ParentKey *string
LocalCompress *bool
ParentCompress *bool
SSMethod *string
SSKey *string
ParentSSMethod *string
ParentSSKey *string
DisableHTTP *bool
DisableSocks5 *bool
DisableSS *bool
}
func (a *SPSArgs) Protocol() string {

View File

@ -16,7 +16,6 @@ import (
"github.com/snail007/goproxy/utils"
"github.com/snail007/goproxy/utils/conncrypt"
"github.com/snail007/goproxy/utils/socks"
"github.com/snail007/goproxy/utils/ss"
)
type SPS struct {
@ -27,8 +26,6 @@ type SPS struct {
serverChannels []*utils.ServerChannel
userConns utils.ConcurrentMap
log *logger.Logger
localCipher *ss.Cipher
parentCipher *ss.Cipher
}
func NewSPS() Service {
@ -49,10 +46,6 @@ func (s *SPS) CheckArgs() (err error) {
err = fmt.Errorf("parent type unkown,use -T <tls|tcp|kcp>")
return
}
if *s.cfg.ParentType == "ss" && (*s.cfg.ParentSSKey == "" || *s.cfg.ParentSSMethod == "") {
err = fmt.Errorf("ss parent need a ss key, set it by : -J <sskey>")
return
}
if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS {
s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile)
if err != nil {
@ -69,28 +62,11 @@ func (s *SPS) CheckArgs() (err error) {
return
}
func (s *SPS) InitService() (err error) {
if *s.cfg.DNSAddress != "" {
(*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL, s.log)
}
s.InitOutConnPool()
err = s.InitBasicAuth()
if *s.cfg.SSMethod != "" && *s.cfg.SSKey != "" {
s.localCipher, err = ss.NewCipher(*s.cfg.SSMethod, *s.cfg.SSKey)
if err != nil {
s.log.Printf("error generating cipher : %s", err)
return
}
}
if *s.cfg.ParentServiceType == "ss" {
s.parentCipher, err = ss.NewCipher(*s.cfg.ParentSSMethod, *s.cfg.ParentSSKey)
if err != nil {
s.log.Printf("error generating cipher : %s", err)
return
}
}
return
}
func (s *SPS) InitOutConnPool() {
@ -281,25 +257,6 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) {
auth = socks.Auth{User: userpassA[0], Password: userpassA[1]}
}
}
} else {
//ss
if *s.cfg.DisableSS {
(*inConn).Close()
return
}
(*inConn).SetDeadline(time.Now().Add(time.Second * 5))
ssConn := ss.NewConn(*inConn, s.localCipher.Copy())
address, err = ss.GetRequest(ssConn)
(*inConn).SetDeadline(time.Time{})
if err != nil {
return
}
// ensure the host does not contain some illegal characters, NUL may panic on Win32
if strings.ContainsRune(address, 0x00) {
err = errors.New("invalid domain name")
return
}
*inConn = ssConn
}
if err != nil || address == "" {
s.log.Printf("unknown request from: %s,%s", (*inConn).RemoteAddr(), string(h))
@ -325,7 +282,7 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) {
})
}
if *s.cfg.ParentAuth != "" || *s.cfg.ParentSSKey != "" || s.IsBasicAuth() {
if *s.cfg.ParentAuth != "" || s.IsBasicAuth() {
forwardBytes = utils.RemoveProxyHeaders(forwardBytes)
}
@ -411,18 +368,6 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) {
if err = clientConn.Handshake(); err != nil {
return
}
} else if *s.cfg.ParentServiceType == "ss" {
ra, e := ss.RawAddr(address)
if e != nil {
err = fmt.Errorf("build ss raw addr fail, err: %s", e)
return
}
outConn, err = ss.DialWithRawAddr(&outConn, ra, "", s.parentCipher.Copy())
if err != nil {
err = fmt.Errorf("dial ss parent fail, err : %s", err)
return
}
}
//forward client data to target,if necessary.

View File

@ -1,193 +0,0 @@
package ss
import (
"encoding/binary"
"fmt"
"io"
"net"
"strconv"
)
const (
OneTimeAuthMask byte = 0x10
AddrMask byte = 0xf
)
type Conn struct {
net.Conn
*Cipher
readBuf []byte
writeBuf []byte
chunkId uint32
}
func NewConn(c net.Conn, cipher *Cipher) *Conn {
return &Conn{
Conn: c,
Cipher: cipher,
readBuf: leakyBuf.Get(),
writeBuf: leakyBuf.Get()}
}
func (c *Conn) Close() error {
leakyBuf.Put(c.readBuf)
leakyBuf.Put(c.writeBuf)
return c.Conn.Close()
}
func RawAddr(addr string) (buf []byte, err error) {
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("ss: address error %s %v", addr, err)
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("ss: invalid port %s", addr)
}
hostLen := len(host)
l := 1 + 1 + hostLen + 2 // addrType + lenByte + address + port
buf = make([]byte, l)
buf[0] = 3 // 3 means the address is domain name
buf[1] = byte(hostLen) // host address length followed by host address
copy(buf[2:], host)
binary.BigEndian.PutUint16(buf[2+hostLen:2+hostLen+2], uint16(port))
return
}
// This is intended for use by users implementing a local socks proxy.
// rawaddr shoud contain part of the data in socks request, starting from the
// ATYP field. (Refer to rfc1928 for more information.)
func DialWithRawAddr(rawConn *net.Conn, rawaddr []byte, server string, cipher *Cipher) (c *Conn, err error) {
var conn net.Conn
if rawConn == nil {
conn, err = net.Dial("tcp", server)
}
if err != nil {
return
}
if rawConn != nil {
c = NewConn(*rawConn, cipher)
} else {
c = NewConn(conn, cipher)
}
if cipher.ota {
if c.enc == nil {
if _, err = c.initEncrypt(); err != nil {
return
}
}
// since we have initEncrypt, we must send iv manually
conn.Write(cipher.iv)
rawaddr[0] |= OneTimeAuthMask
rawaddr = otaConnectAuth(cipher.iv, cipher.key, rawaddr)
}
if _, err = c.write(rawaddr); err != nil {
c.Close()
return nil, err
}
return
}
// addr should be in the form of host:port
func Dial(addr, server string, cipher *Cipher) (c *Conn, err error) {
ra, err := RawAddr(addr)
if err != nil {
return
}
return DialWithRawAddr(nil, ra, server, cipher)
}
func (c *Conn) GetIv() (iv []byte) {
iv = make([]byte, len(c.iv))
copy(iv, c.iv)
return
}
func (c *Conn) GetKey() (key []byte) {
key = make([]byte, len(c.key))
copy(key, c.key)
return
}
func (c *Conn) IsOta() bool {
return c.ota
}
func (c *Conn) GetAndIncrChunkId() (chunkId uint32) {
chunkId = c.chunkId
c.chunkId += 1
return
}
func (c *Conn) Read(b []byte) (n int, err error) {
if c.dec == nil {
iv := make([]byte, c.info.ivLen)
if _, err = io.ReadFull(c.Conn, iv); err != nil {
return
}
if err = c.initDecrypt(iv); err != nil {
return
}
if len(c.iv) == 0 {
c.iv = iv
}
}
cipherData := c.readBuf
if len(b) > len(cipherData) {
cipherData = make([]byte, len(b))
} else {
cipherData = cipherData[:len(b)]
}
n, err = c.Conn.Read(cipherData)
if n > 0 {
c.decrypt(b[0:n], cipherData[0:n])
}
return
}
func (c *Conn) Write(b []byte) (n int, err error) {
nn := len(b)
if c.ota {
chunkId := c.GetAndIncrChunkId()
b = otaReqChunkAuth(c.iv, chunkId, b)
}
headerLen := len(b) - nn
n, err = c.write(b)
// Make sure <= 0 <= len(b), where b is the slice passed in.
if n >= headerLen {
n -= headerLen
}
return
}
func (c *Conn) write(b []byte) (n int, err error) {
var iv []byte
if c.enc == nil {
iv, err = c.initEncrypt()
if err != nil {
return
}
}
cipherData := c.writeBuf
dataSize := len(b) + len(iv)
if dataSize > len(cipherData) {
cipherData = make([]byte, dataSize)
} else {
cipherData = cipherData[:dataSize]
}
if iv != nil {
// Put initialization vector in buffer, do a single write to send both
// iv and data.
copy(cipherData, iv)
}
c.encrypt(cipherData[len(iv):], b)
n, err = c.Conn.Write(cipherData)
return
}

View File

@ -1,274 +0,0 @@
package ss
import (
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/md5"
"crypto/rand"
"crypto/rc4"
"encoding/binary"
"errors"
"io"
"strings"
"github.com/Yawning/chacha20"
"golang.org/x/crypto/blowfish"
"golang.org/x/crypto/cast5"
"golang.org/x/crypto/salsa20/salsa"
)
var errEmptyPassword = errors.New("empty key")
func md5sum(d []byte) []byte {
h := md5.New()
h.Write(d)
return h.Sum(nil)
}
func evpBytesToKey(password string, keyLen int) (key []byte) {
const md5Len = 16
cnt := (keyLen-1)/md5Len + 1
m := make([]byte, cnt*md5Len)
copy(m, md5sum([]byte(password)))
// Repeatedly call md5 until bytes generated is enough.
// Each call to md5 uses data: prev md5 sum + password.
d := make([]byte, md5Len+len(password))
start := 0
for i := 1; i < cnt; i++ {
start += md5Len
copy(d, m[start-md5Len:start])
copy(d[md5Len:], password)
copy(m[start:], md5sum(d))
}
return m[:keyLen]
}
type DecOrEnc int
const (
Decrypt DecOrEnc = iota
Encrypt
)
func newStream(block cipher.Block, err error, key, iv []byte,
doe DecOrEnc) (cipher.Stream, error) {
if err != nil {
return nil, err
}
if doe == Encrypt {
return cipher.NewCFBEncrypter(block, iv), nil
} else {
return cipher.NewCFBDecrypter(block, iv), nil
}
}
func newAESCFBStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
block, err := aes.NewCipher(key)
return newStream(block, err, key, iv, doe)
}
func newAESCTRStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewCTR(block, iv), nil
}
func newDESStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
block, err := des.NewCipher(key)
return newStream(block, err, key, iv, doe)
}
func newBlowFishStream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
block, err := blowfish.NewCipher(key)
return newStream(block, err, key, iv, doe)
}
func newCast5Stream(key, iv []byte, doe DecOrEnc) (cipher.Stream, error) {
block, err := cast5.NewCipher(key)
return newStream(block, err, key, iv, doe)
}
func newRC4MD5Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
h := md5.New()
h.Write(key)
h.Write(iv)
rc4key := h.Sum(nil)
return rc4.NewCipher(rc4key)
}
func newChaCha20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
return chacha20.NewCipher(key, iv)
}
func newChaCha20IETFStream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
return chacha20.NewCipher(key, iv)
}
type salsaStreamCipher struct {
nonce [8]byte
key [32]byte
counter int
}
func (c *salsaStreamCipher) XORKeyStream(dst, src []byte) {
var buf []byte
padLen := c.counter % 64
dataSize := len(src) + padLen
if cap(dst) >= dataSize {
buf = dst[:dataSize]
} else if leakyBufSize >= dataSize {
buf = leakyBuf.Get()
defer leakyBuf.Put(buf)
buf = buf[:dataSize]
} else {
buf = make([]byte, dataSize)
}
var subNonce [16]byte
copy(subNonce[:], c.nonce[:])
binary.LittleEndian.PutUint64(subNonce[len(c.nonce):], uint64(c.counter/64))
// It's difficult to avoid data copy here. src or dst maybe slice from
// Conn.Read/Write, which can't have padding.
copy(buf[padLen:], src[:])
salsa.XORKeyStream(buf, buf, &subNonce, &c.key)
copy(dst, buf[padLen:])
c.counter += len(src)
}
func newSalsa20Stream(key, iv []byte, _ DecOrEnc) (cipher.Stream, error) {
var c salsaStreamCipher
copy(c.nonce[:], iv[:8])
copy(c.key[:], key[:32])
return &c, nil
}
type cipherInfo struct {
keyLen int
ivLen int
newStream func(key, iv []byte, doe DecOrEnc) (cipher.Stream, error)
}
var cipherMethod = map[string]*cipherInfo{
"aes-128-cfb": {16, 16, newAESCFBStream},
"aes-192-cfb": {24, 16, newAESCFBStream},
"aes-256-cfb": {32, 16, newAESCFBStream},
"aes-128-ctr": {16, 16, newAESCTRStream},
"aes-192-ctr": {24, 16, newAESCTRStream},
"aes-256-ctr": {32, 16, newAESCTRStream},
"des-cfb": {8, 8, newDESStream},
"bf-cfb": {16, 8, newBlowFishStream},
"cast5-cfb": {16, 8, newCast5Stream},
"rc4-md5": {16, 16, newRC4MD5Stream},
"rc4-md5-6": {16, 6, newRC4MD5Stream},
"chacha20": {32, 8, newChaCha20Stream},
"chacha20-ietf": {32, 12, newChaCha20IETFStream},
"salsa20": {32, 8, newSalsa20Stream},
}
func CheckCipherMethod(method string) error {
if method == "" {
method = "aes-256-cfb"
}
_, ok := cipherMethod[method]
if !ok {
return errors.New("Unsupported encryption method: " + method)
}
return nil
}
type Cipher struct {
enc cipher.Stream
dec cipher.Stream
key []byte
info *cipherInfo
ota bool // one-time auth
iv []byte
}
// NewCipher creates a cipher that can be used in Dial() etc.
// Use cipher.Copy() to create a new cipher with the same method and password
// to avoid the cost of repeated cipher initialization.
func NewCipher(method, password string) (c *Cipher, err error) {
if password == "" {
return nil, errEmptyPassword
}
var ota bool
if strings.HasSuffix(strings.ToLower(method), "-auth") {
method = method[:len(method)-5] // len("-auth") = 5
ota = true
} else {
ota = false
}
mi, ok := cipherMethod[method]
if !ok {
return nil, errors.New("Unsupported encryption method: " + method)
}
key := evpBytesToKey(password, mi.keyLen)
c = &Cipher{key: key, info: mi}
if err != nil {
return nil, err
}
c.ota = ota
return c, nil
}
// Initializes the block cipher with CFB mode, returns IV.
func (c *Cipher) initEncrypt() (iv []byte, err error) {
if c.iv == nil {
iv = make([]byte, c.info.ivLen)
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
c.iv = iv
} else {
iv = c.iv
}
c.enc, err = c.info.newStream(c.key, iv, Encrypt)
return
}
func (c *Cipher) initDecrypt(iv []byte) (err error) {
c.dec, err = c.info.newStream(c.key, iv, Decrypt)
return
}
func (c *Cipher) encrypt(dst, src []byte) {
c.enc.XORKeyStream(dst, src)
}
func (c *Cipher) decrypt(dst, src []byte) {
c.dec.XORKeyStream(dst, src)
}
// Copy creates a new cipher at it's initial state.
func (c *Cipher) Copy() *Cipher {
// This optimization maybe not necessary. But without this function, we
// need to maintain a table cache for newTableCipher and use lock to
// protect concurrent access to that cache.
// AES and DES ciphers does not return specific types, so it's difficult
// to create copy. But their initizliation time is less than 4000ns on my
// 2.26 GHz Intel Core 2 Duo processor. So no need to worry.
// Currently, blow-fish and cast5 initialization cost is an order of
// maganitude slower than other ciphers. (I'm not sure whether this is
// because the current implementation is not highly optimized, or this is
// the nature of the algorithm.)
nc := *c
nc.enc = nil
nc.dec = nil
nc.ota = c.ota
return &nc
}

View File

@ -1,131 +0,0 @@
package ss
import (
"crypto/hmac"
"crypto/sha1"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"os"
"strconv"
"github.com/snail007/goproxy/utils"
)
const leakyBufSize = 4108 // data.len(2) + hmacsha1(10) + data(4096)
const maxNBuf = 2048
var leakyBuf = utils.NewLeakyBuf(maxNBuf, leakyBufSize)
func IsFileExists(path string) (bool, error) {
stat, err := os.Stat(path)
if err == nil {
if stat.Mode()&os.ModeType == 0 {
return true, nil
}
return false, errors.New(path + " exists but is not regular file")
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func HmacSha1(key []byte, data []byte) []byte {
hmacSha1 := hmac.New(sha1.New, key)
hmacSha1.Write(data)
return hmacSha1.Sum(nil)[:10]
}
func otaConnectAuth(iv, key, data []byte) []byte {
return append(data, HmacSha1(append(iv, key...), data)...)
}
func otaReqChunkAuth(iv []byte, chunkId uint32, data []byte) []byte {
nb := make([]byte, 2)
binary.BigEndian.PutUint16(nb, uint16(len(data)))
chunkIdBytes := make([]byte, 4)
binary.BigEndian.PutUint32(chunkIdBytes, chunkId)
header := append(nb, HmacSha1(append(iv, chunkIdBytes...), data)...)
return append(header, data...)
}
const (
idType = 0 // address type index
idIP0 = 1 // ip addres start index
idDmLen = 1 // domain address length index
idDm0 = 2 // domain address start index
typeIPv4 = 1 // type is ipv4 address
typeDm = 3 // type is domain address
typeIPv6 = 4 // type is ipv6 address
lenIPv4 = net.IPv4len + 2 // ipv4 + 2port
lenIPv6 = net.IPv6len + 2 // ipv6 + 2port
lenDmBase = 2 // 1addrLen + 2port, plus addrLen
lenHmacSha1 = 10
)
func GetRequest(conn *Conn) (host string, err error) {
// buf size should at least have the same size with the largest possible
// request size (when addrType is 3, domain name has at most 256 bytes)
// 1(addrType) + 1(lenByte) + 255(max length address) + 2(port) + 10(hmac-sha1)
buf := make([]byte, 269)
// read till we get possible domain length field
if _, err = io.ReadFull(conn, buf[:idType+1]); err != nil {
return
}
var reqStart, reqEnd int
addrType := buf[idType]
switch addrType & AddrMask {
case typeIPv4:
reqStart, reqEnd = idIP0, idIP0+lenIPv4
case typeIPv6:
reqStart, reqEnd = idIP0, idIP0+lenIPv6
case typeDm:
if _, err = io.ReadFull(conn, buf[idType+1:idDmLen+1]); err != nil {
return
}
reqStart, reqEnd = idDm0, idDm0+int(buf[idDmLen])+lenDmBase
default:
err = fmt.Errorf("addr type %d not supported", addrType&AddrMask)
return
}
if _, err = io.ReadFull(conn, buf[reqStart:reqEnd]); err != nil {
return
}
// Return string for typeIP is not most efficient, but browsers (Chrome,
// Safari, Firefox) all seems using typeDm exclusively. So this is not a
// big problem.
switch addrType & AddrMask {
case typeIPv4:
host = net.IP(buf[idIP0 : idIP0+net.IPv4len]).String()
case typeIPv6:
host = net.IP(buf[idIP0 : idIP0+net.IPv6len]).String()
case typeDm:
host = string(buf[idDm0 : idDm0+int(buf[idDmLen])])
}
// parse port
port := binary.BigEndian.Uint16(buf[reqEnd-2 : reqEnd])
host = net.JoinHostPort(host, strconv.Itoa(int(port)))
return
}
type ClosedFlag struct {
flag bool
}
func (flag *ClosedFlag) SetClosed() {
flag.flag = true
}
func (flag *ClosedFlag) IsClosed() bool {
return flag.flag
}