From b7c2b0e8fa91f7fbed6c3355614a65366066a122 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 25 Oct 2017 16:00:17 +0800 Subject: [PATCH] Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 2 + config.go | 15 +++-- services/args.go | 7 +++ services/http.go | 11 +++- services/socks.go | 23 +++++--- services/tcp.go | 3 +- services/tunnel_bridge.go | 4 +- services/udp.go | 3 +- utils/functions.go | 119 ++++++++++++++++++-------------------- utils/serve-channel.go | 38 +++++++++++- utils/structs.go | 14 +++-- 11 files changed, 153 insertions(+), 86 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 900e64e6..bcd33465 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ proxy更新日志 v3.4 1.socks5代理新增了用户名密码验证支持. +2.socks5,http(s)代理增加了kcp传输协议支持. +3.优化了内网穿透的心跳机制. v3.3 1.修复了socks代理模式对证书文件的判断逻辑. diff --git a/config.go b/config.go index 300d63a0..0e4516d5 100755 --- a/config.go +++ b/config.go @@ -41,8 +41,8 @@ func initConfig() (err error) { httpArgs.Parent = http.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() httpArgs.CertFile = http.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() httpArgs.KeyFile = http.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() - httpArgs.LocalType = http.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp") - httpArgs.ParentType = http.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "ssh") + httpArgs.LocalType = http.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp", "kcp") + httpArgs.ParentType = http.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "ssh", "kcp") httpArgs.Always = http.Flag("always", "always use parent proxy").Default("false").Bool() httpArgs.Timeout = http.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("2000").Int() httpArgs.HTTPTimeout = http.Flag("http-timeout", "check domain if blocked , http request timeout milliseconds when connect to host").Default("3000").Int() @@ -58,6 +58,8 @@ func initConfig() (err error) { httpArgs.SSHKeyFile = http.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String() httpArgs.SSHKeyFileSalt = http.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String() httpArgs.SSHPassword = http.Flag("ssh-password", "password for ssh").Short('A').Default("").String() + httpArgs.KCPKey = http.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String() + httpArgs.KCPMethod = http.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String() //########tcp######### tcp := app.Command("tcp", "proxy on tcp mode") @@ -110,9 +112,11 @@ func initConfig() (err error) { //########ssh######### socks := app.Command("socks", "proxy on ssh mode") socksArgs.Parent = socks.Flag("parent", "parent ssh address, such as: \"23.32.32.19:22\"").Default("").Short('P').String() - socksArgs.ParentType = socks.Flag("parent-type", "parent protocol type ").Default("tcp").Short('T').Enum("tls", "tcp", "ssh") - socksArgs.LocalType = socks.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp") + socksArgs.ParentType = socks.Flag("parent-type", "parent protocol type ").Default("tcp").Short('T').Enum("tls", "tcp", "kcp", "ssh") + socksArgs.LocalType = socks.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp", "kcp") socksArgs.Local = socks.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() + socksArgs.UDPParent = socks.Flag("udp-parent", "udp parent address, such as: \"23.32.32.19:33090\"").Default("").Short('X').String() + socksArgs.UDPLocal = socks.Flag("udp-local", "udp local ip:port to listen").Short('x').Default(":33090").String() socksArgs.CertFile = socks.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() socksArgs.KeyFile = socks.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() socksArgs.SSHUser = socks.Flag("ssh-user", "user for ssh").Short('u').Default("").String() @@ -126,6 +130,9 @@ func initConfig() (err error) { socksArgs.Direct = socks.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String() socksArgs.AuthFile = socks.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String() socksArgs.Auth = socks.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings() + socksArgs.KCPKey = socks.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String() + socksArgs.KCPMethod = socks.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String() + //parse args serviceName := kingpin.MustParse(app.Parse(os.Args[1:])) flags := log.Ldate diff --git a/services/args.go b/services/args.go index 497de64b..d3bde404 100644 --- a/services/args.go +++ b/services/args.go @@ -10,6 +10,7 @@ const ( TYPE_UDP = "udp" TYPE_HTTP = "http" TYPE_TLS = "tls" + TYPE_KCP = "kcp" CONN_CLIENT_CONTROL = uint8(1) CONN_SERVER_CONTROL = uint8(2) CONN_SERVER = uint8(3) @@ -87,6 +88,8 @@ type HTTPArgs struct { SSHUser *string SSHKeyBytes []byte SSHAuthMethod ssh.AuthMethod + KCPMethod *string + KCPKey *string } type UDPArgs struct { Parent *string @@ -122,6 +125,10 @@ type SocksArgs struct { Direct *string AuthFile *string Auth *[]string + KCPMethod *string + KCPKey *string + UDPParent *string + UDPLocal *string } func (a *TCPArgs) Protocol() string { diff --git a/services/http.go b/services/http.go index 043ae1c0..2e04a1ea 100644 --- a/services/http.go +++ b/services/http.go @@ -120,8 +120,10 @@ func (s *HTTP) Start(args interface{}) (err error) { sc := utils.NewServerChannel(host, p) if *s.cfg.LocalType == TYPE_TCP { err = sc.ListenTCP(s.callback) - } else { + } else if *s.cfg.LocalType == TYPE_TLS { err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback) + } else if *s.cfg.LocalType == TYPE_KCP { + err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.callback) } if err != nil { return @@ -197,6 +199,7 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut if *s.cfg.ParentType == "ssh" { outConn, err = s.getSSHConn(address) } else { + //log.Printf("%v", s.outPool) _outConn, err = s.outPool.Pool.Get() if err == nil { outConn = _outConn.(net.Conn) @@ -303,12 +306,14 @@ func (s *HTTP) ConnectSSH() (err error) { return } func (s *HTTP) InitOutConnPool() { - if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP { + if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP { //dur int, isTLS bool, certBytes, keyBytes []byte, //parent string, timeout int, InitialCap int, MaxCap int s.outPool = utils.NewOutPool( *s.cfg.CheckParentInterval, - *s.cfg.ParentType == TYPE_TLS, + *s.cfg.ParentType, + *s.cfg.KCPMethod, + *s.cfg.KCPKey, s.cfg.CertBytes, s.cfg.KeyBytes, *s.cfg.Parent, *s.cfg.Timeout, diff --git a/services/socks.go b/services/socks.go index 669af413..c4e50f80 100644 --- a/services/socks.go +++ b/services/socks.go @@ -105,8 +105,8 @@ func (s *Socks) InitService() { if *s.cfg.ParentType == "ssh" { log.Println("warn: socks udp not suppored for ssh") } else { - _, port, _ := net.SplitHostPort(*s.cfg.Local) - s.udpSC = utils.NewServerChannelHost(":" + port) + + s.udpSC = utils.NewServerChannelHost(*s.cfg.UDPLocal) err := s.udpSC.ListenUDP(s.udpCallback) if err != nil { log.Fatalf("init udp service fail, ERR: %s", err) @@ -133,8 +133,10 @@ func (s *Socks) Start(args interface{}) (err error) { sc := utils.NewServerChannelHost(*s.cfg.Local) if *s.cfg.LocalType == TYPE_TCP { err = sc.ListenTCP(s.socksConnCallback) - } else { + } else if *s.cfg.LocalType == TYPE_TLS { err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.socksConnCallback) + } else if *s.cfg.LocalType == TYPE_KCP { + err = sc.ListenKCP(*s.cfg.KCPMethod, *s.cfg.KCPKey, s.socksConnCallback) } if err != nil { return @@ -176,7 +178,11 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { return } } - dstAddr, err := net.ResolveUDPAddr("udp", *s.cfg.Parent) + parent := *s.cfg.UDPParent + if parent == "" { + parent = *s.cfg.Parent + } + dstAddr, err := net.ResolveUDPAddr("udp", parent) if err != nil { log.Printf("can't resolve address: %s", err) return @@ -382,7 +388,7 @@ func (s *Socks) proxyUDP(inConn *net.Conn, methodReq socks.MethodsRequest, reque } host, _, _ := net.SplitHostPort((*inConn).LocalAddr().String()) _, port, _ := net.SplitHostPort(s.udpSC.UDPListener.LocalAddr().String()) - // log.Printf("proxy udp on %s", net.JoinHostPort(host, port)) + log.Printf("proxy udp on %s", net.JoinHostPort(host, port)) request.UDPReply(socks.REP_SUCCESS, net.JoinHostPort(host, port)) } func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, request socks.Request) { @@ -428,15 +434,16 @@ func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, reque inLocalAddr := (*inConn).LocalAddr().String() log.Printf("conn %s - %s connected [%s]", inAddr, inLocalAddr, request.Addr()) - utils.IoBind0(*inConn, outConn, func(err error) { + utils.IoBind(*inConn, outConn, func(err error) { log.Printf("conn %s - %s released [%s]", inAddr, inLocalAddr, request.Addr()) utils.CloseConn(inConn) utils.CloseConn(&outConn) }) - //}, func(i int, b bool) {}, 0) } func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn net.Conn, err interface{}) { switch *s.cfg.ParentType { + case "kcp": + fallthrough case "tls": fallthrough case "tcp": @@ -444,6 +451,8 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n var _outConn tls.Conn _outConn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) outConn = net.Conn(&_outConn) + } else if *s.cfg.ParentType == "kcp" { + outConn, err = utils.ConnectKCPHost(*s.cfg.Parent, *s.cfg.KCPMethod, *s.cfg.KCPKey) } else { outConn, err = utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout) } diff --git a/services/tcp.go b/services/tcp.go index 9b446716..758046ef 100644 --- a/services/tcp.go +++ b/services/tcp.go @@ -167,7 +167,8 @@ func (s *TCP) InitOutConnPool() { //parent string, timeout int, InitialCap int, MaxCap int s.outPool = utils.NewOutPool( *s.cfg.CheckParentInterval, - *s.cfg.ParentType == TYPE_TLS, + *s.cfg.ParentType, + "", "", s.cfg.CertBytes, s.cfg.KeyBytes, *s.cfg.Parent, *s.cfg.Timeout, diff --git a/services/tunnel_bridge.go b/services/tunnel_bridge.go index 1bdd7262..8460e765 100644 --- a/services/tunnel_bridge.go +++ b/services/tunnel_bridge.go @@ -167,7 +167,7 @@ func (s *TunnelBridge) Start(args interface{}) (err error) { _, err = inConn.Write([]byte{0x00}) inConn.SetWriteDeadline(time.Time{}) if err != nil { - log.Printf("control connection write err %s", err) + log.Printf("server control connection write err %s", err) break } time.Sleep(time.Second * 3) @@ -181,7 +181,7 @@ func (s *TunnelBridge) Start(args interface{}) (err error) { _, err := inConn.Read(signal) inConn.SetReadDeadline(time.Time{}) if err != nil { - log.Printf("control connection read err: %s", err) + log.Printf("server control connection read err: %s", err) break } else { // log.Printf("heartbeat from server ,id:%s", serverID) diff --git a/services/udp.go b/services/udp.go index 28edbc31..8eed0ffb 100644 --- a/services/udp.go +++ b/services/udp.go @@ -207,7 +207,8 @@ func (s *UDP) InitOutConnPool() { //parent string, timeout int, InitialCap int, MaxCap int s.outPool = utils.NewOutPool( *s.cfg.CheckParentInterval, - *s.cfg.ParentType == TYPE_TLS, + *s.cfg.ParentType, + "", "", s.cfg.CertBytes, s.cfg.KeyBytes, *s.cfg.Parent, *s.cfg.Timeout, diff --git a/utils/functions.go b/utils/functions.go index 58adac41..a8fb8e5d 100755 --- a/utils/functions.go +++ b/utils/functions.go @@ -3,6 +3,7 @@ package utils import ( "bufio" "bytes" + "crypto/sha1" "crypto/tls" "crypto/x509" "encoding/binary" @@ -16,92 +17,50 @@ import ( "net/http" "os" "os/exec" - "sync" + + "golang.org/x/crypto/pbkdf2" "runtime/debug" "strconv" "strings" "time" + + kcp "github.com/xtaci/kcp-go" ) -func IoBind0(dst io.ReadWriter, src io.ReadWriter, fn func(err error)) { +func IoBind(dst io.ReadWriter, src io.ReadWriter, fn func(err error)) { go func() { + e1 := make(chan error, 1) + e2 := make(chan error, 1) go func() { defer func() { if e := recover(); e != nil { - log.Printf("IoBind0 crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) + log.Printf("IoBind crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) } }() - _, err := io.Copy(dst, src) - if err != nil { - fn(err) - } + + _, e := io.Copy(dst, src) + e1 <- e }() go func() { defer func() { if e := recover(); e != nil { - log.Printf("IoBind0 crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) + log.Printf("IoBind crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) } }() - _, err := io.Copy(src, dst) - if err != nil { - fn(err) - } - }() - }() -} -func IoBind(dst io.ReadWriter, src io.ReadWriter, fn func(err error)) { - var one = &sync.Once{} - go func() { - defer func() { - if e := recover(); e != nil { - log.Printf("IoBind crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) - } - }() - var err error - _, err = ioCopy(dst, src) - if err != nil { - one.Do(func() { - fn(err) - }) - } - }() - go func() { - defer func() { - if e := recover(); e != nil { - log.Printf("IoBind crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) - } + + _, e := io.Copy(src, dst) + e2 <- e }() var err error - _, err = ioCopy(src, dst) - if err != nil { - one.Do(func() { - fn(err) - }) + select { + case err = <-e1: + case err = <-e2: } + fn(err) }() } -func ioCopy(dst io.Writer, src io.Reader) (written int64, err error) { - buf := make([]byte, 32*1024) - for { - nr, er := src.Read(buf) - if er != nil { - err = er - break - } - nw, ew := dst.Write(buf[0:nr]) - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - written += int64(nw) - } - return written, err -} + func TlsConnectHost(host string, timeout int, certBytes, keyBytes []byte) (conn tls.Conn, err error) { h := strings.Split(host, ":") port, _ := strconv.Atoi(h[1]) @@ -143,6 +102,10 @@ func ConnectHost(hostAndPort string, timeout int) (conn net.Conn, err error) { conn, err = net.DialTimeout("tcp", hostAndPort, time.Duration(timeout)*time.Millisecond) return } +func ConnectKCPHost(hostAndPort, method, key string) (conn net.Conn, err error) { + conn, err = kcp.DialWithOptions(hostAndPort, GetKCPBlock(method, key), 10, 3) + return +} func ListenTls(ip string, port int, certBytes, keyBytes []byte) (ln *net.Listener, err error) { var cert tls.Certificate cert, err = tls.X509KeyPair(certBytes, keyBytes) @@ -406,6 +369,38 @@ func TlsBytes(cert, key string) (certBytes, keyBytes []byte) { } return } +func GetKCPBlock(method, key string) (block kcp.BlockCrypt) { + pass := pbkdf2.Key([]byte(key), []byte(key), 4096, 32, sha1.New) + switch method { + case "sm4": + block, _ = kcp.NewSM4BlockCrypt(pass[:16]) + case "tea": + block, _ = kcp.NewTEABlockCrypt(pass[:16]) + case "xor": + block, _ = kcp.NewSimpleXORBlockCrypt(pass) + case "none": + block, _ = kcp.NewNoneBlockCrypt(pass) + case "aes-128": + block, _ = kcp.NewAESBlockCrypt(pass[:16]) + case "aes-192": + block, _ = kcp.NewAESBlockCrypt(pass[:24]) + case "blowfish": + block, _ = kcp.NewBlowfishBlockCrypt(pass) + case "twofish": + block, _ = kcp.NewTwofishBlockCrypt(pass) + case "cast5": + block, _ = kcp.NewCast5BlockCrypt(pass[:16]) + case "3des": + block, _ = kcp.NewTripleDESBlockCrypt(pass[:24]) + case "xtea": + block, _ = kcp.NewXTEABlockCrypt(pass[:16]) + case "salsa20": + block, _ = kcp.NewSalsa20BlockCrypt(pass) + default: + block, _ = kcp.NewAESBlockCrypt(pass) + } + return +} // type sockaddr struct { // family uint16 diff --git a/utils/serve-channel.go b/utils/serve-channel.go index 653c7cde..c0235183 100644 --- a/utils/serve-channel.go +++ b/utils/serve-channel.go @@ -6,6 +6,8 @@ import ( "net" "runtime/debug" "strconv" + + kcp "github.com/xtaci/kcp-go" ) type ServerChannel struct { @@ -55,7 +57,7 @@ func (sc *ServerChannel) ListenTls(certBytes, keyBytes []byte, fn func(conn net. go func() { defer func() { if e := recover(); e != nil { - log.Printf("connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) + log.Printf("tls connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) } }() fn(conn) @@ -89,7 +91,7 @@ func (sc *ServerChannel) ListenTCP(fn func(conn net.Conn)) (err error) { go func() { defer func() { if e := recover(); e != nil { - log.Printf("connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) + log.Printf("tcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) } }() fn(conn) @@ -136,3 +138,35 @@ func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *ne } return } +func (sc *ServerChannel) ListenKCP(method, key string, fn func(conn net.Conn)) (err error) { + var l net.Listener + l, err = kcp.ListenWithOptions(fmt.Sprintf("%s:%d", sc.ip, sc.port), GetKCPBlock(method, key), 10, 3) + if err == nil { + sc.Listener = &l + go func() { + defer func() { + if e := recover(); e != nil { + log.Printf("ListenKCP crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) + } + }() + for { + var conn net.Conn + conn, err = (*sc.Listener).Accept() + if err == nil { + go func() { + defer func() { + if e := recover(); e != nil { + log.Printf("kcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) + } + }() + fn(conn) + }() + } else { + sc.errAcceptHandler(err) + break + } + } + }() + } + return +} diff --git a/utils/structs.go b/utils/structs.go index 858ae7b2..a64286f6 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -390,19 +390,23 @@ func (req *HTTPRequest) addPortIfNot() (newHost string) { type OutPool struct { Pool ConnPool dur int - isTLS bool + typ string certBytes []byte keyBytes []byte + kcpMethod string + kcpKey string address string timeout int } -func NewOutPool(dur int, isTLS bool, certBytes, keyBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutPool) { +func NewOutPool(dur int, typ, kcpMethod, kcpKey string, certBytes, keyBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutPool) { op = OutPool{ dur: dur, - isTLS: isTLS, + typ: typ, certBytes: certBytes, keyBytes: keyBytes, + kcpMethod: kcpMethod, + kcpKey: kcpKey, address: address, timeout: timeout, } @@ -436,12 +440,14 @@ func NewOutPool(dur int, isTLS bool, certBytes, keyBytes []byte, address string, return } func (op *OutPool) getConn() (conn interface{}, err error) { - if op.isTLS { + if op.typ == "tls" { var _conn tls.Conn _conn, err = TlsConnectHost(op.address, op.timeout, op.certBytes, op.keyBytes) if err == nil { conn = net.Conn(&_conn) } + } else if op.typ == "kcp" { + conn, err = ConnectKCPHost(op.address, op.kcpMethod, op.kcpKey) } else { conn, err = ConnectHost(op.address, op.timeout) }