From 5ed4702b62dddbab7614c4cbfb40bcbf913a91c9 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 8 Mar 2018 11:51:34 +0800 Subject: [PATCH 01/76] add kcp config args Signed-off-by: arraykeys@gmail.com --- config.go | 74 ++++++++++++++++++++++++++++++++++++----- services/args.go | 18 +++++----- services/http.go | 5 ++- services/kcpcfg/args.go | 24 +++++++++++++ services/socks.go | 4 +-- services/sps.go | 5 ++- services/tcp.go | 5 ++- services/udp.go | 3 +- utils/functions.go | 21 ++++++++---- utils/serve-channel.go | 34 +++++++++++++++---- utils/structs.go | 54 ++++++++++++++++++++++++++---- 11 files changed, 198 insertions(+), 49 deletions(-) create mode 100644 services/kcpcfg/args.go diff --git a/config.go b/config.go index 04be4b2..cbf8452 100755 --- a/config.go +++ b/config.go @@ -2,14 +2,18 @@ package main import ( "bufio" + "crypto/sha1" "fmt" "log" "os" "os/exec" "snail007/proxy/services" + "snail007/proxy/services/kcpcfg" "snail007/proxy/utils" "time" + kcp "github.com/xtaci/kcp-go" + "golang.org/x/crypto/pbkdf2" kingpin "gopkg.in/alecthomas/kingpin.v2" ) @@ -40,6 +44,7 @@ func initConfig() (err error) { udpArgs := services.UDPArgs{} socksArgs := services.SocksArgs{} spsArgs := services.SPSArgs{} + kcpArgs := kcpcfg.KCPConfigArgs{} //build srvice args app = kingpin.New("proxy", "happy with proxy") app.Author("snail").Version(APP_VERSION) @@ -47,6 +52,23 @@ func initConfig() (err error) { daemon := app.Flag("daemon", "run proxy in background").Default("false").Bool() forever := app.Flag("forever", "run proxy in forever,fail and retry").Default("false").Bool() logfile := app.Flag("log", "log file path").Default("").String() + kcpArgs.Key = app.Flag("kcp-key", "pre-shared secret between client and server").Default("secrect").String() + kcpArgs.Crypt = app.Flag("kcp-method", "encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none").Default("aes").String() + kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast").String() + kcpArgs.MTU = app.Flag("kcp-mtu", "set maximum transmission unit for UDP packets").Default("1350").Int() + kcpArgs.SndWnd = app.Flag("kcp-sndwnd", "set send window size(num of packets)").Default("1024").Int() + kcpArgs.RcvWnd = app.Flag("kcp-rcvwnd", "set receive window size(num of packets)").Default("1024").Int() + kcpArgs.DataShard = app.Flag("kcp-ds", "set reed-solomon erasure coding - datashard").Default("10").Int() + kcpArgs.ParityShard = app.Flag("kcp-ps", "set reed-solomon erasure coding - parityshard").Default("3").Int() + kcpArgs.DSCP = app.Flag("kcp-dscp", "set DSCP(6bit)").Default("0").Int() + kcpArgs.NoComp = app.Flag("kcp-nocomp", "disable compression").Default("false").Bool() + kcpArgs.AckNodelay = app.Flag("kcp-acknodelay", "be carefull! flush ack immediately when a packet is received").Default("true").Bool() + kcpArgs.NoDelay = app.Flag("kcp-nodelay", "be carefull!").Default("0").Int() + kcpArgs.Interval = app.Flag("kcp-interval", "be carefull!").Default("50").Int() + kcpArgs.Resend = app.Flag("kcp-resend", "be carefull!").Default("0").Int() + kcpArgs.NoCongestion = app.Flag("kcp-nc", "be carefull! no congestion").Default("0").Int() + kcpArgs.SockBuf = app.Flag("kcp-sockbuf", "be carefull!").Default("4194304").Int() + kcpArgs.KeepAlive = app.Flag("kcp-keepalive", "be carefull!").Default("10").Int() //########http######### http := app.Command("http", "proxy on http mode") @@ -70,8 +92,6 @@ 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() httpArgs.LocalIPS = http.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings() httpArgs.AuthURL = http.Flag("auth-url", "http basic auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String() httpArgs.AuthURLTimeout = http.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int() @@ -91,8 +111,6 @@ func initConfig() (err error) { tcpArgs.PoolSize = tcp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() tcpArgs.CheckParentInterval = tcp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() tcpArgs.Local = tcp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() - tcpArgs.KCPKey = tcp.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String() - tcpArgs.KCPMethod = tcp.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String() //########udp######### udp := app.Command("udp", "proxy on udp mode") @@ -178,8 +196,6 @@ 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() socksArgs.LocalIPS = socks.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings() socksArgs.AuthURL = socks.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String() socksArgs.AuthURLTimeout = socks.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int() @@ -196,14 +212,56 @@ func initConfig() (err error) { spsArgs.ParentType = sps.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "kcp") spsArgs.LocalType = sps.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp", "kcp") spsArgs.Local = sps.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String() - spsArgs.KCPKey = sps.Flag("kcp-key", "key for kcp encrypt/decrypt data").Short('B').Default("encrypt").String() - spsArgs.KCPMethod = sps.Flag("kcp-method", "kcp encrypt/decrypt method").Short('M').Default("3des").String() spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type ").Short('S').Enum("http", "socks") spsArgs.DNSAddress = sps.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() spsArgs.DNSTTL = sps.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() //parse args serviceName := kingpin.MustParse(app.Parse(os.Args[1:])) + + //set kcp config + + switch *kcpArgs.Mode { + case "normal": + *kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 40, 2, 1 + case "fast": + *kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 30, 2, 1 + case "fast2": + *kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 20, 2, 1 + case "fast3": + *kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 10, 2, 1 + } + pass := pbkdf2.Key([]byte(*kcpArgs.Key), []byte("snail007-goproxy"), 4096, 32, sha1.New) + + switch *kcpArgs.Crypt { + case "sm4": + kcpArgs.Block, _ = kcp.NewSM4BlockCrypt(pass[:16]) + case "tea": + kcpArgs.Block, _ = kcp.NewTEABlockCrypt(pass[:16]) + case "xor": + kcpArgs.Block, _ = kcp.NewSimpleXORBlockCrypt(pass) + case "none": + kcpArgs.Block, _ = kcp.NewNoneBlockCrypt(pass) + case "aes-128": + kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:16]) + case "aes-192": + kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:24]) + case "blowfish": + kcpArgs.Block, _ = kcp.NewBlowfishBlockCrypt(pass) + case "twofish": + kcpArgs.Block, _ = kcp.NewTwofishBlockCrypt(pass) + case "cast5": + kcpArgs.Block, _ = kcp.NewCast5BlockCrypt(pass[:16]) + case "3des": + kcpArgs.Block, _ = kcp.NewTripleDESBlockCrypt(pass[:24]) + case "xtea": + kcpArgs.Block, _ = kcp.NewXTEABlockCrypt(pass[:16]) + case "salsa20": + kcpArgs.Block, _ = kcp.NewSalsa20BlockCrypt(pass) + default: + *kcpArgs.Crypt = "aes" + kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass) + } flags := log.Ldate if *debug { flags |= log.Lshortfile | log.Lmicroseconds diff --git a/services/args.go b/services/args.go index 7dfa671..73f263a 100644 --- a/services/args.go +++ b/services/args.go @@ -1,6 +1,10 @@ package services -import "golang.org/x/crypto/ssh" +import ( + "snail007/proxy/services/kcpcfg" + + "golang.org/x/crypto/ssh" +) // tcp := app.Command("tcp", "proxy on tcp mode") // t := tcp.Flag("tcp-timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("2000").Int() @@ -102,8 +106,7 @@ type TCPArgs struct { Timeout *int PoolSize *int CheckParentInterval *int - KCPMethod *string - KCPKey *string + KCP kcpcfg.KCPConfigArgs } type HTTPArgs struct { @@ -135,8 +138,7 @@ type HTTPArgs struct { SSHUser *string SSHKeyBytes []byte SSHAuthMethod ssh.AuthMethod - KCPMethod *string - KCPKey *string + KCP kcpcfg.KCPConfigArgs LocalIPS *[]string DNSAddress *string DNSTTL *int @@ -179,8 +181,7 @@ type SocksArgs struct { AuthURLOkCode *int AuthURLTimeout *int AuthURLRetry *int - KCPMethod *string - KCPKey *string + KCP kcpcfg.KCPConfigArgs UDPParent *string UDPLocal *string LocalIPS *[]string @@ -197,8 +198,7 @@ type SPSArgs struct { ParentType *string LocalType *string Timeout *int - KCPMethod *string - KCPKey *string + KCP kcpcfg.KCPConfigArgs ParentServiceType *string DNSAddress *string DNSTTL *int diff --git a/services/http.go b/services/http.go index 2693417..8621a8a 100644 --- a/services/http.go +++ b/services/http.go @@ -130,7 +130,7 @@ func (s *HTTP) Start(args interface{}) (err error) { } 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) + err = sc.ListenKCP(s.cfg.KCP, s.callback) } if err != nil { return @@ -320,8 +320,7 @@ func (s *HTTP) InitOutConnPool() { s.outPool = utils.NewOutPool( *s.cfg.CheckParentInterval, *s.cfg.ParentType, - *s.cfg.KCPMethod, - *s.cfg.KCPKey, + s.cfg.KCP, s.cfg.CertBytes, s.cfg.KeyBytes, s.Resolve(*s.cfg.Parent), *s.cfg.Timeout, diff --git a/services/kcpcfg/args.go b/services/kcpcfg/args.go new file mode 100644 index 0000000..5d3b67c --- /dev/null +++ b/services/kcpcfg/args.go @@ -0,0 +1,24 @@ +package kcpcfg + +import kcp "github.com/xtaci/kcp-go" + +type KCPConfigArgs struct { + Key *string + Crypt *string + Mode *string + MTU *int + SndWnd *int + RcvWnd *int + DataShard *int + ParityShard *int + DSCP *int + NoComp *bool + AckNodelay *bool + NoDelay *int + Interval *int + Resend *int + NoCongestion *int + SockBuf *int + KeepAlive *int + Block kcp.BlockCrypt +} diff --git a/services/socks.go b/services/socks.go index c123c17..b69ed91 100644 --- a/services/socks.go +++ b/services/socks.go @@ -140,7 +140,7 @@ func (s *Socks) Start(args interface{}) (err error) { } 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) + err = sc.ListenKCP(s.cfg.KCP, s.socksConnCallback) } if err != nil { return @@ -474,7 +474,7 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n _outConn, err = utils.TlsConnectHost(s.Resolve(*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.Resolve(*s.cfg.Parent), *s.cfg.KCPMethod, *s.cfg.KCPKey) + outConn, err = utils.ConnectKCPHost(s.Resolve(*s.cfg.Parent), s.cfg.KCP) } else { outConn, err = utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout) } diff --git a/services/sps.go b/services/sps.go index fa62ff5..1e4e4a5 100644 --- a/services/sps.go +++ b/services/sps.go @@ -46,8 +46,7 @@ func (s *SPS) InitOutConnPool() { s.outPool = utils.NewOutPool( 0, *s.cfg.ParentType, - *s.cfg.KCPMethod, - *s.cfg.KCPKey, + s.cfg.KCP, s.cfg.CertBytes, s.cfg.KeyBytes, *s.cfg.Parent, *s.cfg.Timeout, @@ -78,7 +77,7 @@ func (s *SPS) Start(args interface{}) (err error) { } 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) + err = sc.ListenKCP(s.cfg.KCP, s.callback) } if err != nil { return diff --git a/services/tcp.go b/services/tcp.go index 019c410..85136a2 100644 --- a/services/tcp.go +++ b/services/tcp.go @@ -58,7 +58,7 @@ func (s *TCP) Start(args interface{}) (err error) { } 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) + err = sc.ListenKCP(s.cfg.KCP, s.callback) } if err != nil { return @@ -171,8 +171,7 @@ func (s *TCP) InitOutConnPool() { s.outPool = utils.NewOutPool( *s.cfg.CheckParentInterval, *s.cfg.ParentType, - *s.cfg.KCPMethod, - *s.cfg.KCPKey, + s.cfg.KCP, s.cfg.CertBytes, s.cfg.KeyBytes, *s.cfg.Parent, *s.cfg.Timeout, diff --git a/services/udp.go b/services/udp.go index 39a8000..6f63f0d 100644 --- a/services/udp.go +++ b/services/udp.go @@ -8,6 +8,7 @@ import ( "log" "net" "runtime/debug" + "snail007/proxy/services/kcpcfg" "snail007/proxy/utils" "strconv" "strings" @@ -208,7 +209,7 @@ func (s *UDP) InitOutConnPool() { s.outPool = utils.NewOutPool( *s.cfg.CheckParentInterval, *s.cfg.ParentType, - "", "", + kcpcfg.KCPConfigArgs{}, s.cfg.CertBytes, s.cfg.KeyBytes, *s.cfg.Parent, *s.cfg.Timeout, diff --git a/utils/functions.go b/utils/functions.go index ae6a349..3927909 100755 --- a/utils/functions.go +++ b/utils/functions.go @@ -18,6 +18,7 @@ import ( "net/http" "os" "os/exec" + "snail007/proxy/services/kcpcfg" "golang.org/x/crypto/pbkdf2" @@ -147,17 +148,23 @@ 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) { - kcpconn, err := kcp.DialWithOptions(hostAndPort, GetKCPBlock(method, key), 10, 3) +func ConnectKCPHost(hostAndPort string, config kcpcfg.KCPConfigArgs) (conn net.Conn, err error) { + kcpconn, err := kcp.DialWithOptions(hostAndPort, config.Block, *config.DataShard, *config.ParityShard) if err != nil { return } - kcpconn.SetNoDelay(1, 10, 2, 1) - kcpconn.SetWindowSize(1024, 1024) - kcpconn.SetMtu(1400) - kcpconn.SetACKNoDelay(false) - return kcpconn, err + kcpconn.SetStreamMode(true) + kcpconn.SetWriteDelay(true) + kcpconn.SetNoDelay(*config.NoDelay, *config.Interval, *config.Resend, *config.NoCongestion) + kcpconn.SetMtu(*config.MTU) + kcpconn.SetWindowSize(*config.SndWnd, *config.RcvWnd) + kcpconn.SetACKNoDelay(*config.AckNodelay) + if *config.NoComp { + return kcpconn, err + } + return NewCompStream(kcpconn), err } + func ListenTls(ip string, port int, certBytes, keyBytes []byte) (ln *net.Listener, err error) { block, _ := pem.Decode(certBytes) if block == nil { diff --git a/utils/serve-channel.go b/utils/serve-channel.go index c023518..25e0a7e 100644 --- a/utils/serve-channel.go +++ b/utils/serve-channel.go @@ -5,6 +5,7 @@ import ( "log" "net" "runtime/debug" + "snail007/proxy/services/kcpcfg" "strconv" kcp "github.com/xtaci/kcp-go" @@ -138,11 +139,19 @@ 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) +func (sc *ServerChannel) ListenKCP(config kcpcfg.KCPConfigArgs, fn func(conn net.Conn)) (err error) { + lis, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", sc.ip, sc.port), config.Block, *config.DataShard, *config.ParityShard) if err == nil { - sc.Listener = &l + if err := lis.SetDSCP(*config.DSCP); err != nil { + log.Println("SetDSCP:", err) + } + if err := lis.SetReadBuffer(*config.SockBuf); err != nil { + log.Println("SetReadBuffer:", err) + } + if err := lis.SetWriteBuffer(*config.SockBuf); err != nil { + log.Println("SetWriteBuffer:", err) + } + *sc.Listener = lis go func() { defer func() { if e := recover(); e != nil { @@ -150,8 +159,8 @@ func (sc *ServerChannel) ListenKCP(method, key string, fn func(conn net.Conn)) ( } }() for { - var conn net.Conn - conn, err = (*sc.Listener).Accept() + //var conn net.Conn + conn, err := lis.AcceptKCP() if err == nil { go func() { defer func() { @@ -159,7 +168,18 @@ func (sc *ServerChannel) ListenKCP(method, key string, fn func(conn net.Conn)) ( log.Printf("kcp connection handler crashed , err : %s , \ntrace:%s", e, string(debug.Stack())) } }() - fn(conn) + conn.SetStreamMode(true) + conn.SetWriteDelay(true) + conn.SetNoDelay(*config.NoDelay, *config.Interval, *config.Resend, *config.NoCongestion) + conn.SetMtu(*config.MTU) + conn.SetWindowSize(*config.SndWnd, *config.RcvWnd) + conn.SetACKNoDelay(*config.AckNodelay) + if *config.NoComp { + fn(conn) + } else { + cconn := NewCompStream(conn) + fn(cconn) + } }() } else { sc.errAcceptHandler(err) diff --git a/utils/structs.go b/utils/structs.go index fe22c47..a88e75c 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -12,11 +12,13 @@ import ( "log" "net" "net/url" + "snail007/proxy/services/kcpcfg" "snail007/proxy/utils/sni" "strings" "sync" "time" + "github.com/golang/snappy" "github.com/miekg/dns" ) @@ -494,20 +496,18 @@ type OutPool struct { typ string certBytes []byte keyBytes []byte - kcpMethod string - kcpKey string + kcp kcpcfg.KCPConfigArgs address string timeout int } -func NewOutPool(dur int, typ, kcpMethod, kcpKey string, certBytes, keyBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutPool) { +func NewOutPool(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutPool) { op = OutPool{ dur: dur, typ: typ, certBytes: certBytes, keyBytes: keyBytes, - kcpMethod: kcpMethod, - kcpKey: kcpKey, + kcp: kcp, address: address, timeout: timeout, } @@ -548,7 +548,7 @@ func (op *OutPool) getConn() (conn interface{}, err error) { conn = net.Conn(&_conn) } } else if op.typ == "kcp" { - conn, err = ConnectKCPHost(op.address, op.kcpMethod, op.kcpKey) + conn, err = ConnectKCPHost(op.address, op.kcp) } else { conn, err = ConnectHost(op.address, op.timeout) } @@ -924,3 +924,45 @@ func (a *DomainResolver) PrintData() { fmt.Printf("%s:ip[%s],domain[%s],expired at[%d]\n", k, (*d).ip, (*d).domain, (*d).expiredAt) } } +func NewCompStream(conn net.Conn) *CompStream { + c := new(CompStream) + c.conn = conn + c.w = snappy.NewBufferedWriter(conn) + c.r = snappy.NewReader(conn) + return c +} + +type CompStream struct { + conn net.Conn + w *snappy.Writer + r *snappy.Reader +} + +func (c *CompStream) Read(p []byte) (n int, err error) { + return c.r.Read(p) +} + +func (c *CompStream) Write(p []byte) (n int, err error) { + n, err = c.w.Write(p) + err = c.w.Flush() + return n, err +} + +func (c *CompStream) Close() error { + return c.conn.Close() +} +func (c *CompStream) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} +func (c *CompStream) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} +func (c *CompStream) SetDeadline(t time.Time) error { + return c.conn.SetDeadline(t) +} +func (c *CompStream) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} +func (c *CompStream) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} From 15994988bedc0538bcf8f8b86bbb9821d01dce3d Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 8 Mar 2018 12:04:26 +0800 Subject: [PATCH 02/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README_ZH.md b/README_ZH.md index f1055f0..f886c5f 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -126,6 +126,9 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - [6.4 链式连接](#64-链式连接) - [6.5 监听多个端口](#65-监听多个端口) - [6.6 查看帮助](#66-查看帮助) +- [7. KCP配置](#7kcp配置) + - [7.1 配置介绍](#71-配置介绍) + - [7.2 详细配置](#72-详细配置) ### Fast Start 提示:所有操作需要root权限. @@ -733,7 +736,36 @@ vps02:3.3.3.3 #### **6.6 查看帮助** `./proxy help sps` +### **7.KCP配置** +#### **7.1 配置介绍** +proxy的很多功能都支持kcp协议,凡是使用了kcp协议的功能都支持这里介绍的配置参数。 +所以这里统一对KCP配置参数进行介绍。 + +#### **7.2 详细配置** +所有的KCP配置参数共有17个,你可以都不用设置,他们都有默认值,如果为了或者最好的效果, +就需要自己根据自己根据网络情况对参数进行配置。由于kcp配置很复杂需要一定的网络基础知识, +如果想获得kcp参数更详细的配置和解说,请自行搜索。每个参数的命令行名称以及默认值和简单的功能说明如下: +``` +--kcp-key="secrect" pre-shared secret between client and server +--kcp-method="aes" encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none +--kcp-mode="secrect" profiles: fast3, fast2, fast, normal, manual +--kcp-mtu=1350 set maximum transmission unit for UDP packets +--kcp-sndwnd=1024 set send window size(num of packets) +--kcp-rcvwnd=1024 set receive window size(num of packets) +--kcp-ds=10 set reed-solomon erasure coding - datashard +--kcp-ps=3 set reed-solomon erasure coding - parityshard +--kcp-dscp=0 set DSCP(6bit) +--kcp-nocomp disable compression +--kcp-acknodelay be carefull! flush ack immediately when a packet is received +--kcp-nodelay=0 be carefull! +--kcp-interval=50 be carefull! +--kcp-resend=0 be carefull! +--kcp-nc=0 be carefull! no congestion +--kcp-sockbuf=4194304 be carefull! +--kcp-keepalive=10 be carefull! +``` + ### TODO - http,socks代理多个上级负载均衡? - http(s)代理增加pac支持? From 7eb0e0040e45f0684e8b93d993a78d39f7a86d44 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 8 Mar 2018 12:06:42 +0800 Subject: [PATCH 03/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index f886c5f..0cdea01 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -748,7 +748,8 @@ proxy的很多功能都支持kcp协议,凡是使用了kcp协议的功能都支 如果想获得kcp参数更详细的配置和解说,请自行搜索。每个参数的命令行名称以及默认值和简单的功能说明如下: ``` --kcp-key="secrect" pre-shared secret between client and server ---kcp-method="aes" encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none +--kcp-method="aes" encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, + twofish, cast5, 3des, tea, xtea, xor, sm4, none --kcp-mode="secrect" profiles: fast3, fast2, fast, normal, manual --kcp-mtu=1350 set maximum transmission unit for UDP packets --kcp-sndwnd=1024 set send window size(num of packets) From 25deffb7d68ce386e1177efb30c1e0ab0afec348 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 8 Mar 2018 12:07:19 +0800 Subject: [PATCH 04/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index 0cdea01..d6e5df5 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -749,7 +749,7 @@ proxy的很多功能都支持kcp协议,凡是使用了kcp协议的功能都支 ``` --kcp-key="secrect" pre-shared secret between client and server --kcp-method="aes" encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, - twofish, cast5, 3des, tea, xtea, xor, sm4, none + twofish, cast5, 3des, tea, xtea, xor, sm4, none --kcp-mode="secrect" profiles: fast3, fast2, fast, normal, manual --kcp-mtu=1350 set maximum transmission unit for UDP packets --kcp-sndwnd=1024 set send window size(num of packets) From c471dd8297382454eff6c8c9273cf917cb277a19 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 8 Mar 2018 12:07:44 +0800 Subject: [PATCH 05/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index d6e5df5..7c93a66 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -749,7 +749,7 @@ proxy的很多功能都支持kcp协议,凡是使用了kcp协议的功能都支 ``` --kcp-key="secrect" pre-shared secret between client and server --kcp-method="aes" encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, - twofish, cast5, 3des, tea, xtea, xor, sm4, none + twofish, cast5, 3des, tea, xtea, xor, sm4, none --kcp-mode="secrect" profiles: fast3, fast2, fast, normal, manual --kcp-mtu=1350 set maximum transmission unit for UDP packets --kcp-sndwnd=1024 set send window size(num of packets) From 1cbb4195e42abbc825d682d123bd73d3cf3fdbc0 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 8 Mar 2018 12:08:02 +0800 Subject: [PATCH 06/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index 7c93a66..fb01efe 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -749,7 +749,7 @@ proxy的很多功能都支持kcp协议,凡是使用了kcp协议的功能都支 ``` --kcp-key="secrect" pre-shared secret between client and server --kcp-method="aes" encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, - twofish, cast5, 3des, tea, xtea, xor, sm4, none + twofish, cast5, 3des, tea, xtea, xor, sm4, none --kcp-mode="secrect" profiles: fast3, fast2, fast, normal, manual --kcp-mtu=1350 set maximum transmission unit for UDP packets --kcp-sndwnd=1024 set send window size(num of packets) From a17acd73513c8eaa9e7007979392a14e75cf70c7 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 8 Mar 2018 12:08:25 +0800 Subject: [PATCH 07/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index fb01efe..bc714d7 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -749,7 +749,7 @@ proxy的很多功能都支持kcp协议,凡是使用了kcp协议的功能都支 ``` --kcp-key="secrect" pre-shared secret between client and server --kcp-method="aes" encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, - twofish, cast5, 3des, tea, xtea, xor, sm4, none + twofish, cast5, 3des, tea, xtea, xor, sm4, none --kcp-mode="secrect" profiles: fast3, fast2, fast, normal, manual --kcp-mtu=1350 set maximum transmission unit for UDP packets --kcp-sndwnd=1024 set send window size(num of packets) From 6f11deab96ca1cef718729501f294be825377ae0 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 8 Mar 2018 13:45:54 +0800 Subject: [PATCH 08/76] Signed-off-by: arraykeys@gmail.com --- Godeps/Godeps.json | 142 ++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 223984c..fae3375 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,41 +1,80 @@ { "ImportPath": "snail007/proxy", - "GoVersion": "go1.8", - "GodepVersion": "v79", + "GoVersion": "go1.9", + "GodepVersion": "v80", "Packages": [ "./..." ], "Deps": [ - { - "ImportPath": "github.com/alecthomas/template", - "Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c" - }, - { - "ImportPath": "github.com/alecthomas/template/parse", - "Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c" - }, - { - "ImportPath": "github.com/alecthomas/units", - "Rev": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" - }, { "ImportPath": "github.com/golang/snappy", "Rev": "553a641470496b2327abcac10b36396bd98e45c9" }, { "ImportPath": "github.com/miekg/dns", - "Comment": "v1.0.4", - "Rev": "5364553f1ee9cddc7ac8b62dce148309c386695b" + "Comment": "v1.0.4-1-g40b5202", + "Rev": "40b520211179dbf7eaafaa7fe1ffaa1b7d929ee0" + }, + { + "ImportPath": "github.com/xtaci/kcp-go", + "Comment": "v3.19-6-g21da33a", + "Rev": "21da33a6696d67c1bffb3c954366499d613097a6" + }, + { + "ImportPath": "github.com/xtaci/smux", + "Comment": "v1.0.6", + "Rev": "ebec7ef2574b42a7088cd7751176483e0a27d458" + }, + { + "ImportPath": "golang.org/x/crypto/pbkdf2", + "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" + }, + { + "ImportPath": "golang.org/x/crypto/ssh", + "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" + }, + { + "ImportPath": "golang.org/x/time/rate", + "Rev": "6dc17368e09b0e8634d71cac8168d853e869a0c7" + }, + { + "ImportPath": "gopkg.in/alecthomas/kingpin.v2", + "Comment": "v2.2.5", + "Rev": "1087e65c9441605df944fb12c33f0fe7072d18ca" + }, + { + "ImportPath": "golang.org/x/crypto/ed25519", + "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" + }, + { + "ImportPath": "golang.org/x/net/ipv4", + "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + }, + { + "ImportPath": "golang.org/x/net/ipv6", + "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + }, + { + "ImportPath": "golang.org/x/crypto/ed25519/internal/edwards25519", + "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" + }, + { + "ImportPath": "golang.org/x/net/bpf", + "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + }, + { + "ImportPath": "golang.org/x/net/internal/iana", + "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + }, + { + "ImportPath": "golang.org/x/net/internal/socket", + "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" }, { "ImportPath": "github.com/pkg/errors", "Comment": "v0.8.0-6-g602255c", "Rev": "602255cdb6deaf1523ea53ac30eae5554ba7bee9" }, - { - "ImportPath": "github.com/templexxx/cpufeat", - "Rev": "3794dfbfb04749f896b521032f69383f24c3687e" - }, { "ImportPath": "github.com/templexxx/reedsolomon", "Comment": "0.1.1-4-g7092926", @@ -51,16 +90,6 @@ "Comment": "v1.0.1-3-g9d99fac", "Rev": "9d99face20b0dd300b7db50b3f69758de41c096a" }, - { - "ImportPath": "github.com/xtaci/kcp-go", - "Comment": "v3.19-6-g21da33a", - "Rev": "21da33a6696d67c1bffb3c954366499d613097a6" - }, - { - "ImportPath": "github.com/xtaci/smux", - "Comment": "v1.0.6", - "Rev": "ebec7ef2574b42a7088cd7751176483e0a27d458" - }, { "ImportPath": "golang.org/x/crypto/blowfish", "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" @@ -69,34 +98,10 @@ "ImportPath": "golang.org/x/crypto/cast5", "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" }, - { - "ImportPath": "golang.org/x/crypto/curve25519", - "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" - }, - { - "ImportPath": "golang.org/x/crypto/ed25519", - "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" - }, - { - "ImportPath": "golang.org/x/crypto/ed25519/internal/edwards25519", - "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" - }, - { - "ImportPath": "golang.org/x/crypto/pbkdf2", - "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" - }, { "ImportPath": "golang.org/x/crypto/salsa20", "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" }, - { - "ImportPath": "golang.org/x/crypto/salsa20/salsa", - "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" - }, - { - "ImportPath": "golang.org/x/crypto/ssh", - "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" - }, { "ImportPath": "golang.org/x/crypto/tea", "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" @@ -110,33 +115,28 @@ "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" }, { - "ImportPath": "golang.org/x/net/bpf", - "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + "ImportPath": "github.com/templexxx/cpufeat", + "Rev": "3794dfbfb04749f896b521032f69383f24c3687e" }, { - "ImportPath": "golang.org/x/net/internal/iana", - "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + "ImportPath": "golang.org/x/crypto/salsa20/salsa", + "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" }, { - "ImportPath": "golang.org/x/net/internal/socket", - "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + "ImportPath": "golang.org/x/crypto/curve25519", + "Rev": "0fcca4842a8d74bfddc2c96a073bd2a4d2a7a2e8" }, { - "ImportPath": "golang.org/x/net/ipv4", - "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + "ImportPath": "github.com/alecthomas/template", + "Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c" }, { - "ImportPath": "golang.org/x/net/ipv6", - "Rev": "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec" + "ImportPath": "github.com/alecthomas/units", + "Rev": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" }, { - "ImportPath": "golang.org/x/time/rate", - "Rev": "6dc17368e09b0e8634d71cac8168d853e869a0c7" - }, - { - "ImportPath": "gopkg.in/alecthomas/kingpin.v2", - "Comment": "v2.2.5", - "Rev": "1087e65c9441605df944fb12c33f0fe7072d18ca" + "ImportPath": "github.com/alecthomas/template/parse", + "Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c" } ] } From 4143f14fbd12f4c306f34e96dbfbd715540cf40e Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 8 Mar 2018 18:42:50 +0800 Subject: [PATCH 09/76] optimise nat forwarding in different lan Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 14 +++++ README_ZH.md | 2 +- config.go | 2 + services/args.go | 44 ++++++++-------- services/mux_bridge.go | 28 ++++++++-- services/mux_client.go | 115 ++++++++++++++++++++++++----------------- services/mux_server.go | 66 ++++++++++++----------- 7 files changed, 166 insertions(+), 105 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 36e12be..b784585 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,18 @@ proxy更新日志 +v4.4 +1.增加了协议转换sps功能,代理协议转换使用的是sps子命令(socks+https的缩写), +sps本身不提供代理功能,只是接受代理请求"转换并转发"给已经存在的http(s)代理 +或者socks5代理;sps可以把已经存在的http(s)代理或者socks5代理转换为一个端口 +同时支持http(s)和socks5代理,而且http(s)代理支持正向代理和反向代理(SNI),转 +换后的SOCKS5代理不支持UDP功能;另外对于已经存在的http(s)代理或者socks5代理, +支持tls、tcp、kcp三种模式,支持链式连接,也就是可以多个sps结点层级连接构建 +加密通道。 +2.增加了对KCP传输参数的配置,多达17个参数可以自由的配置对kcp传输效率调优。 +3.内网穿透功能,server和client增加了--session-count参数,可以设置server每个 +监听端口到bridge打开的session数量,可以设置client到bridge打开的session数量, +之前都是1个,现在性能提升N倍,N就是你自己设置的--session-count,这个参数很大 +程度上解决了多路复用的拥塞问题,v4.4开始默认10个。 + v4.3 1.优化了参数keygen生成证书逻辑,避免证书出现特征。 2.http(s)和socks代理增加了--dns-address和--dns-ttl参数。 diff --git a/README_ZH.md b/README_ZH.md index bc714d7..9fada3c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -682,7 +682,7 @@ KCP协议需要-B参数设置一个密码用于加密解密数据 ### **6.代理协议转换** #### **6.1 功能介绍** -代理协议转换使用的是sps子命令(socks+https的缩写),sps本身不提供代理功能,只是接受代理请求"转换并转发"给已经存在的http(s)代理或者socks5代理;sps可以把已经存在的http(s)代理或者socks5代理转换为一个端口同时支持http(s)和socks5代理,而且http(s)代理支持正向代理和反向代理(SNI),转换后的SOCKS5代理不支持UDP功能;另外对于已经存在的http(s)代理或者socks5代理,支持tls、tcp、kcp三种模式,支持链式连接,也就是可以多个sps结点层级连接构建加密通道;。 +代理协议转换使用的是sps子命令(socks+https的缩写),sps本身不提供代理功能,只是接受代理请求"转换并转发"给已经存在的http(s)代理或者socks5代理;sps可以把已经存在的http(s)代理或者socks5代理转换为一个端口同时支持http(s)和socks5代理,而且http(s)代理支持正向代理和反向代理(SNI),转换后的SOCKS5代理不支持UDP功能;另外对于已经存在的http(s)代理或者socks5代理,支持tls、tcp、kcp三种模式,支持链式连接,也就是可以多个sps结点层级连接构建加密通道。 #### **6.2 HTTP(S)转HTTP(S)+SOCKS5** 假设已经存在一个普通的http(s)代理:127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080。 diff --git a/config.go b/config.go index cbf8452..56c0de4 100755 --- a/config.go +++ b/config.go @@ -133,6 +133,7 @@ func initConfig() (err error) { muxServerArgs.Key = muxServer.Flag("k", "client key").Default("default").String() muxServerArgs.Route = muxServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings() muxServerArgs.IsCompress = muxServer.Flag("c", "compress data when tcp mode").Default("false").Bool() + muxServerArgs.SessionCount = muxServer.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int() //########mux-client######### muxClient := app.Command("client", "proxy on mux client mode") @@ -142,6 +143,7 @@ func initConfig() (err error) { muxClientArgs.Timeout = muxClient.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int() muxClientArgs.Key = muxClient.Flag("k", "key same with server").Default("default").String() muxClientArgs.IsCompress = muxClient.Flag("c", "compress data when tcp mode").Default("false").Bool() + muxClientArgs.SessionCount = muxClient.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int() //########mux-bridge######### muxBridge := app.Command("bridge", "proxy on mux bridge mode") diff --git a/services/args.go b/services/args.go index 73f263a..dd23686 100644 --- a/services/args.go +++ b/services/args.go @@ -25,29 +25,31 @@ const ( ) type MuxServerArgs struct { - Parent *string - CertFile *string - KeyFile *string - CertBytes []byte - KeyBytes []byte - Local *string - IsUDP *bool - Key *string - Remote *string - Timeout *int - Route *[]string - Mgr *MuxServerManager - IsCompress *bool + Parent *string + CertFile *string + KeyFile *string + CertBytes []byte + KeyBytes []byte + Local *string + IsUDP *bool + Key *string + Remote *string + Timeout *int + Route *[]string + Mgr *MuxServerManager + IsCompress *bool + SessionCount *int } type MuxClientArgs struct { - Parent *string - CertFile *string - KeyFile *string - CertBytes []byte - KeyBytes []byte - Key *string - Timeout *int - IsCompress *bool + Parent *string + CertFile *string + KeyFile *string + CertBytes []byte + KeyBytes []byte + Key *string + Timeout *int + IsCompress *bool + SessionCount *int } type MuxBridgeArgs struct { Parent *string diff --git a/services/mux_bridge.go b/services/mux_bridge.go index e2feac1..f452b48 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -4,9 +4,11 @@ import ( "bufio" "io" "log" + "math/rand" "net" "snail007/proxy/utils" "strconv" + "strings" "time" "github.com/xtaci/smux" @@ -83,7 +85,6 @@ func (s *MuxBridge) Start(args interface{}) (err error) { go s.callback(stream, serverID, key) } case CONN_CLIENT: - log.Printf("client connection %s connected", key) session, err := smux.Client(inConn, nil) if err != nil { @@ -91,11 +92,24 @@ func (s *MuxBridge) Start(args interface{}) (err error) { log.Printf("client session error,ERR:%s", err) return } - s.clientControlConns.Set(key, session) + keyInfo := strings.Split(key, "-") + groupKey := keyInfo[0] + index := keyInfo[1] + if !s.clientControlConns.Has(groupKey) { + item := utils.NewConcurrentMap() + s.clientControlConns.Set(groupKey, &item) + } + _group, _ := s.clientControlConns.Get(groupKey) + group := _group.(*utils.ConcurrentMap) + group.Set(index, session) + // s.clientControlConns.Set(key, session) go func() { for { if session.IsClosed() { - s.clientControlConns.Remove(key) + group.Remove(index) + if group.IsEmpty() { + s.clientControlConns.Remove(groupKey) + } break } time.Sleep(time.Second * 5) @@ -124,19 +138,23 @@ func (s *MuxBridge) callback(inConn net.Conn, serverID, key string) { if key == "*" { key = s.router.GetKey() } - session, ok := s.clientControlConns.Get(key) + _group, ok := s.clientControlConns.Get(key) if !ok { log.Printf("client %s session not exists for server stream %s", key, serverID) time.Sleep(time.Second * 3) continue } + group := _group.(*utils.ConcurrentMap) + index := group.Keys()[rand.Intn(group.Count())] + log.Printf("select client : %s-%s", key, index) + session, _ := group.Get(index) stream, err := session.(*smux.Session).OpenStream() if err != nil { log.Printf("%s client session open stream %s fail, err: %s, retrying...", key, serverID, err) time.Sleep(time.Second * 3) continue } else { - log.Printf("%s server %s stream created", key, serverID) + log.Printf("stream %s -> %s created", serverID, key) die1 := make(chan bool, 1) die2 := make(chan bool, 1) go func() { diff --git a/services/mux_client.go b/services/mux_client.go index fa45a78..c41f6e6 100644 --- a/services/mux_client.go +++ b/services/mux_client.go @@ -2,6 +2,7 @@ package services import ( "crypto/tls" + "fmt" "io" "log" "net" @@ -45,57 +46,75 @@ func (s *MuxClient) Start(args interface{}) (err error) { s.CheckArgs() s.InitService() log.Printf("proxy on mux client mode, compress %v", *s.cfg.IsCompress) - for { - var _conn tls.Conn - _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) - if err != nil { - log.Printf("connection err: %s, retrying...", err) - time.Sleep(time.Second * 3) - continue - } - conn := net.Conn(&_conn) - _, err = conn.Write(utils.BuildPacket(CONN_CLIENT, *s.cfg.Key)) - if err != nil { - conn.Close() - log.Printf("connection err: %s, retrying...", err) - time.Sleep(time.Second * 3) - continue - } - session, err := smux.Server(conn, nil) - if err != nil { - log.Printf("session err: %s, retrying...", err) - conn.Close() - time.Sleep(time.Second * 3) - continue - } - for { - stream, err := session.AcceptStream() - if err != nil { - log.Printf("accept stream err: %s, retrying...", err) - session.Close() - time.Sleep(time.Second * 3) - break - } - go func() { - var ID, clientLocalAddr, serverID string - err = utils.ReadPacketData(stream, &ID, &clientLocalAddr, &serverID) - if err != nil { - log.Printf("read stream signal err: %s", err) - stream.Close() - return - } - log.Printf("signal revecived,server %s stream %s %s", serverID, ID, clientLocalAddr) - protocol := clientLocalAddr[:3] - localAddr := clientLocalAddr[4:] - if protocol == "udp" { - s.ServeUDP(stream, localAddr, ID) - } else { - s.ServeConn(stream, localAddr, ID) + for i := 1; i <= *s.cfg.SessionCount; i++ { + log.Printf("session worker[%d] started", i) + go func(i int) { + defer func() { + e := recover() + if e != nil { + log.Printf("session worker crashed: %s", e) } }() - } - } + for { + var _conn tls.Conn + _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + if err != nil { + log.Printf("connection err: %s, retrying...", err) + time.Sleep(time.Second * 3) + continue + } + conn := net.Conn(&_conn) + _, err = conn.Write(utils.BuildPacket(CONN_CLIENT, fmt.Sprintf("%s-%d", *s.cfg.Key, i))) + if err != nil { + conn.Close() + log.Printf("connection err: %s, retrying...", err) + time.Sleep(time.Second * 3) + continue + } + session, err := smux.Server(conn, nil) + if err != nil { + log.Printf("session err: %s, retrying...", err) + conn.Close() + time.Sleep(time.Second * 3) + continue + } + for { + stream, err := session.AcceptStream() + if err != nil { + log.Printf("accept stream err: %s, retrying...", err) + session.Close() + time.Sleep(time.Second * 3) + break + } + go func() { + defer func() { + e := recover() + if e != nil { + log.Printf("stream handler crashed: %s", e) + } + }() + var ID, clientLocalAddr, serverID string + err = utils.ReadPacketData(stream, &ID, &clientLocalAddr, &serverID) + if err != nil { + log.Printf("read stream signal err: %s", err) + stream.Close() + return + } + log.Printf("worker[%d] signal revecived,server %s stream %s %s", i, serverID, ID, clientLocalAddr) + protocol := clientLocalAddr[:3] + localAddr := clientLocalAddr[4:] + if protocol == "udp" { + s.ServeUDP(stream, localAddr, ID) + } else { + s.ServeConn(stream, localAddr, ID) + } + }() + } + } + }(i) + } + return } func (s *MuxClient) Clean() { s.StopService() diff --git a/services/mux_server.go b/services/mux_server.go index 7699b7b..d7c961b 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "log" + "math/rand" "net" "runtime/debug" "snail007/proxy/utils" @@ -17,11 +18,11 @@ import ( ) type MuxServer struct { - cfg MuxServerArgs - udpChn chan MuxUDPItem - sc utils.ServerChannel - session *smux.Session - lockChn chan bool + cfg MuxServerArgs + udpChn chan MuxUDPItem + sc utils.ServerChannel + sessions utils.ConcurrentMap + lockChn chan bool } type MuxServerManager struct { @@ -74,18 +75,19 @@ func (s *MuxServerManager) Start(args interface{}) (err error) { remote = fmt.Sprintf("127.0.0.1%s", remote) } err = server.Start(MuxServerArgs{ - CertBytes: s.cfg.CertBytes, - KeyBytes: s.cfg.KeyBytes, - Parent: s.cfg.Parent, - CertFile: s.cfg.CertFile, - KeyFile: s.cfg.KeyFile, - Local: &local, - IsUDP: &IsUDP, - Remote: &remote, - Key: &KEY, - Timeout: s.cfg.Timeout, - Mgr: s, - IsCompress: s.cfg.IsCompress, + CertBytes: s.cfg.CertBytes, + KeyBytes: s.cfg.KeyBytes, + Parent: s.cfg.Parent, + CertFile: s.cfg.CertFile, + KeyFile: s.cfg.KeyFile, + Local: &local, + IsUDP: &IsUDP, + Remote: &remote, + Key: &KEY, + Timeout: s.cfg.Timeout, + Mgr: s, + IsCompress: s.cfg.IsCompress, + SessionCount: s.cfg.SessionCount, }) if err != nil { @@ -110,9 +112,10 @@ func (s *MuxServerManager) InitService() { func NewMuxServer() Service { return &MuxServer{ - cfg: MuxServerArgs{}, - udpChn: make(chan MuxUDPItem, 50000), - lockChn: make(chan bool, 1), + cfg: MuxServerArgs{}, + udpChn: make(chan MuxUDPItem, 50000), + lockChn: make(chan bool, 1), + sessions: utils.NewConcurrentMap(), } } @@ -206,7 +209,7 @@ func (s *MuxServer) Clean() { } func (s *MuxServer) GetOutConn() (outConn net.Conn, ID string, err error) { - outConn, err = s.GetConn() + outConn, err = s.GetConn(fmt.Sprintf("%d", rand.Intn(*s.cfg.SessionCount))) if err != nil { log.Printf("connection err: %s", err) return @@ -224,7 +227,7 @@ func (s *MuxServer) GetOutConn() (outConn net.Conn, ID string, err error) { } return } -func (s *MuxServer) GetConn() (conn net.Conn, err error) { +func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { select { case s.lockChn <- true: default: @@ -234,32 +237,35 @@ func (s *MuxServer) GetConn() (conn net.Conn, err error) { defer func() { <-s.lockChn }() - if s.session == nil { + var session *smux.Session + _session, ok := s.sessions.Get(index) + if !ok { var _conn tls.Conn _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) if err != nil { - s.session = nil return } c := net.Conn(&_conn) _, err = c.Write(utils.BuildPacket(CONN_SERVER, *s.cfg.Key, s.cfg.Mgr.serverID)) if err != nil { c.Close() - s.session = nil return } if err == nil { - s.session, err = smux.Client(c, nil) + session, err = smux.Client(c, nil) if err != nil { - s.session = nil return } } + s.sessions.Set(index, session) + log.Printf("session[%s] created", index) + } else { + session = _session.(*smux.Session) } - conn, err = s.session.OpenStream() + conn, err = session.OpenStream() if err != nil { - s.session.Close() - s.session = nil + session.Close() + s.sessions.Remove(index) } return From 1a432a9b795fdacbbb6647c6738ec7314c0af414 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 9 Mar 2018 09:50:21 +0800 Subject: [PATCH 10/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 25 ++++++++++++++++--------- config.go | 4 ++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 9fada3c..5eab06e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -750,7 +750,7 @@ proxy的很多功能都支持kcp协议,凡是使用了kcp协议的功能都支 --kcp-key="secrect" pre-shared secret between client and server --kcp-method="aes" encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none ---kcp-mode="secrect" profiles: fast3, fast2, fast, normal, manual +--kcp-mode="fast" profiles: fast3, fast2, fast, normal, manual --kcp-mtu=1350 set maximum transmission unit for UDP packets --kcp-sndwnd=1024 set send window size(num of packets) --kcp-rcvwnd=1024 set receive window size(num of packets) @@ -766,19 +766,26 @@ proxy的很多功能都支持kcp协议,凡是使用了kcp协议的功能都支 --kcp-sockbuf=4194304 be carefull! --kcp-keepalive=10 be carefull! ``` - +提示: +参数:--kcp-mode中的四种fast3, fast2, fast, normal模式, +相当于设置了下面四个参数: +normal:`--nodelay=0 --interval=40 --resend=2 --nc=1` +fast :`--nodelay=0 --interval=30 --resend=2 --nc=1` +fast2:`--nodelay=1 --interval=20 --resend=2 --nc=1` +fast3:`--nodelay=1 --interval=10 --resend=2 --nc=1` + ### TODO - http,socks代理多个上级负载均衡? - http(s)代理增加pac支持? - 欢迎加群反馈... -### 如何使用源码? -建议go1.8.5,不保证>=1.9能用. -cd进入你的go src目录,新建文件夹snail007, -cd进入snail007,然后git clone https://github.com/snail007/goproxy.git ./proxy 即可. -编译直接:go build -运行: go run *.go -utils是工具包,service是具体的每个服务类. +### 如何使用源码? +建议go1.8.5,不保证>=1.9能用. +cd进入你的go src目录,新建文件夹snail007, +cd进入snail007,然后git clone https://github.com/snail007/goproxy.git ./proxy 即可. +编译直接:go build +运行: go run *.go +utils是工具包,service是具体的每个服务类. ### License Proxy is licensed under GPLv3 license. diff --git a/config.go b/config.go index 56c0de4..ac74541 100755 --- a/config.go +++ b/config.go @@ -53,8 +53,8 @@ func initConfig() (err error) { forever := app.Flag("forever", "run proxy in forever,fail and retry").Default("false").Bool() logfile := app.Flag("log", "log file path").Default("").String() kcpArgs.Key = app.Flag("kcp-key", "pre-shared secret between client and server").Default("secrect").String() - kcpArgs.Crypt = app.Flag("kcp-method", "encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none").Default("aes").String() - kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast").String() + kcpArgs.Crypt = app.Flag("kcp-method", "encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none").Default("aes").Enum("aes", "aes-128", "aes-192", "salsa20", "blowfish", "twofish", "cast5", "3des", "tea", "xtea", "xor", "sm4", "none") + kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast").Enum("fast3", "fast2", "fast", "normal", "manual") kcpArgs.MTU = app.Flag("kcp-mtu", "set maximum transmission unit for UDP packets").Default("1350").Int() kcpArgs.SndWnd = app.Flag("kcp-sndwnd", "set send window size(num of packets)").Default("1024").Int() kcpArgs.RcvWnd = app.Flag("kcp-rcvwnd", "set receive window size(num of packets)").Default("1024").Int() From 7bb8f19b90ce7737cf7613e5d05f831b63984a07 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 9 Mar 2018 16:02:05 +0800 Subject: [PATCH 11/76] Signed-off-by: arraykeys@gmail.com --- README.md | 16 ++++++++-------- README_ZH.md | 16 ++++++++-------- services/mux_bridge.go | 5 +++++ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 66b689b..c3a9e5c 100644 --- a/README.md +++ b/README.md @@ -281,13 +281,13 @@ Local HTTP (S) proxy use 28080 port,excute: `./proxy http -T ssh -P "2.2.2.2:22" -u user -S user.key -t tcp -p ":28080"` #### **1.8.KCP protocol transmission** -The KCP protocol requires a -B parameter to set a password which can encrypt and decrypt data. +The KCP protocol requires a --kcp-key parameter to set a password which can encrypt and decrypt data. Http first level proxy(VPS,IP:22.22.22.22) -`./proxy http -t kcp -p ":38080" -B mypassword` +`./proxy http -t kcp -p ":38080" --kcp-key mypassword` Http second level proxy(os is Linux) -`./proxy http -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" -B mypassword` +`./proxy http -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" --kcp-key mypassword` Then access to the local 8080 port is access to the proxy's port 38080 on the VPS, and the data is transmitted through the KCP protocol. #### **1.9.HTTP reverse proxy** Proxy supports not only set up a proxy through in other software, to provide services for other software, but support the request directly to the website domain to proxy monitor IP when proxy monitors 80 and 443 ports, then proxy will automatically access to the HTTP proxy access website for you.   @@ -657,13 +657,13 @@ ip: user's IP, for example: 192.168.1.200 If there is no -a or -F or --auth-url parameters, it means to turn off the authentication. #### **5.8.KCP protocol transmission** -The KCP protocol requires a -B parameter which can set a password to encrypt and decrypt data.   +The KCP protocol requires a --kcp-key parameter which can set a password to encrypt and decrypt data.   HTTP first level proxy(VPS,IP:22.22.22.22) -`./proxy socks -t kcp -p ":38080" -B mypassword` +`./proxy socks -t kcp -p ":38080" --kcp-key mypassword` HTTP two level proxy(local os is Linux) -`./proxy socks -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" -B mypassword` +`./proxy socks -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" --kcp-key mypassword` Then access to the local 8080 port is access to the proxy port 38080 on the VPS, and the data is transmitted through the KCP protocol. #### **5.9.Custom DNS** @@ -692,7 +692,7 @@ command: Suppose there is a KCP HTTP (s) proxy (password: demo123): 127.0.0.1:8080. Now we turn it into a common proxy that supports HTTP (s) and Socks5 at the same time. The local port after transformation is 18080. command: -`./proxy sps -S http -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 -B demo123` +`./proxy sps -S http -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 --kcp-key demo123` #### **6.3.SOCKS5 to HTTP(S) + SOCKS5** Suppose there is a common Socks5 proxy: 127.0.0.1:8080, now we turn it into a common proxy that supports HTTP (s) and Socks5 at the same time, and the local port after transformation is 18080. @@ -705,7 +705,7 @@ command: Suppose there is a KCP Socks5 proxy (password: demo123): 127.0.0.1:8080, now we turn it into a common proxy that support HTTP (s) and Socks5 at the same time, and the local port after transformation is 18080. command: -`./proxy sps -S socks -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 -B demo123`   +`./proxy sps -S socks -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 --kcp-key demo123`   #### **6.4.Chain style connection** It is mentioned above that multiple SPS nodes can be connected to build encrypted channels, assuming you have the following VPS and a PC. diff --git a/README_ZH.md b/README_ZH.md index 5eab06e..e3e2974 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -281,13 +281,13 @@ target:用户访问的URL,比如:http://demo.com:80/1.html或https://www.baidu.c `./proxy http -T ssh -P "2.2.2.2:22" -u user -S user.key -t tcp -p ":28080"` #### **1.8.KCP协议传输** -KCP协议需要-B参数设置一个密码用于加密解密数据 +KCP协议需要--kcp-key参数设置一个密码用于加密解密数据 一级HTTP代理(VPS,IP:22.22.22.22) -`./proxy http -t kcp -p ":38080" -B mypassword` +`./proxy http -t kcp -p ":38080" --kcp-key mypassword` 二级HTTP代理(本地Linux) -`./proxy http -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" -B mypassword` +`./proxy http -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" --kcp-key mypassword` 那么访问本地的8080端口就是访问VPS上面的代理端口38080,数据通过kcp协议传输. #### **1.9 HTTP(S)反向代理** @@ -661,13 +661,13 @@ ip:用户的IP,比如:192.168.1.200 如果没有-a或-F或--auth-url参数,就是关闭认证. #### **5.8.KCP协议传输** -KCP协议需要-B参数设置一个密码用于加密解密数据 +KCP协议需要--kcp-key参数设置一个密码用于加密解密数据 一级HTTP代理(VPS,IP:22.22.22.22) -`./proxy socks -t kcp -p ":38080" -B mypassword` +`./proxy socks -t kcp -p ":38080" --kcp-key mypassword` 二级HTTP代理(本地Linux) -`./proxy socks -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" -B mypassword` +`./proxy socks -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" --kcp-key mypassword` 那么访问本地的8080端口就是访问VPS上面的代理端口38080,数据通过kcp协议传输. #### **5.9.自定义DNS** @@ -695,7 +695,7 @@ KCP协议需要-B参数设置一个密码用于加密解密数据 假设已经存在一个kcp的http(s)代理(密码是:demo123):127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080。 命令如下: -`./proxy sps -S http -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 -B demo123` +`./proxy sps -S http -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 --kcp-key demo123` #### **6.3 SOCKS5转HTTP(S)+SOCKS5** 假设已经存在一个普通的socks5代理:127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080。 @@ -708,7 +708,7 @@ KCP协议需要-B参数设置一个密码用于加密解密数据 假设已经存在一个kcp的socks5代理(密码是:demo123):127.0.0.1:8080,现在我们把它转为同时支持http(s)和socks5的普通代理,转换后的本地端口为18080。 命令如下: -`./proxy sps -S socks -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 -B demo123` +`./proxy sps -S socks -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 --kcp-key demo123` #### **6.4 链式连接** 上面提过多个sps结点可以层级连接构建加密通道,假设有如下vps和家里的pc电脑。 diff --git a/services/mux_bridge.go b/services/mux_bridge.go index f452b48..0b097ce 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -93,6 +93,11 @@ func (s *MuxBridge) Start(args interface{}) (err error) { return } keyInfo := strings.Split(key, "-") + if len(keyInfo) != 2 { + utils.CloseConn(&inConn) + log.Printf("client key format error,key:%s", key) + return + } groupKey := keyInfo[0] index := keyInfo[1] if !s.clientControlConns.Has(groupKey) { From 1cf4313d12b1f5413b6703e82c086423c9e9ab77 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 9 Mar 2018 17:16:21 +0800 Subject: [PATCH 12/76] Signed-off-by: arraykeys@gmail.com --- config.go | 6 ++++++ utils/serve-channel.go | 10 +++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index ac74541..c105bb4 100755 --- a/config.go +++ b/config.go @@ -264,6 +264,12 @@ func initConfig() (err error) { *kcpArgs.Crypt = "aes" kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass) } + //attach kcp config + tcpArgs.KCP = kcpArgs + httpArgs.KCP = kcpArgs + socksArgs.KCP = kcpArgs + spsArgs.KCP = kcpArgs + flags := log.Ldate if *debug { flags |= log.Lshortfile | log.Lmicroseconds diff --git a/utils/serve-channel.go b/utils/serve-channel.go index 25e0a7e..a53f00e 100644 --- a/utils/serve-channel.go +++ b/utils/serve-channel.go @@ -142,15 +142,19 @@ func (sc *ServerChannel) ListenUDP(fn func(packet []byte, localAddr, srcAddr *ne func (sc *ServerChannel) ListenKCP(config kcpcfg.KCPConfigArgs, fn func(conn net.Conn)) (err error) { lis, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", sc.ip, sc.port), config.Block, *config.DataShard, *config.ParityShard) if err == nil { - if err := lis.SetDSCP(*config.DSCP); err != nil { + if err = lis.SetDSCP(*config.DSCP); err != nil { log.Println("SetDSCP:", err) + return } - if err := lis.SetReadBuffer(*config.SockBuf); err != nil { + if err = lis.SetReadBuffer(*config.SockBuf); err != nil { log.Println("SetReadBuffer:", err) + return } - if err := lis.SetWriteBuffer(*config.SockBuf); err != nil { + if err = lis.SetWriteBuffer(*config.SockBuf); err != nil { log.Println("SetWriteBuffer:", err) + return } + sc.Listener = new(net.Listener) *sc.Listener = lis go func() { defer func() { From 2d8dc56f4ea1a60f28f1348227095444377d671e Mon Sep 17 00:00:00 2001 From: snail007 Date: Sat, 10 Mar 2018 17:00:03 +0800 Subject: [PATCH 13/76] Update README_ZH.md --- README_ZH.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index e3e2974..9b638ee 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -386,16 +386,16 @@ VPS(IP:22.22.22.33)执行: #### **2.4.加密二级TCP代理** VPS(IP:22.22.22.33)执行: -`./proxy tcp -t tcp -p ":33080" -T tcp -P "127.0.0.1:8080" -C proxy.crt -K proxy.key` +`./proxy tcp -t tls -p ":33080" -T tcp -P "127.0.0.1:8080" -C proxy.crt -K proxy.key` 本地执行: `./proxy tcp -p ":23080" -T tls -P "22.22.22.33:33080" -C proxy.crt -K proxy.key` 那么访问本地23080端口就是通过加密TCP隧道访问22.22.22.33的8080端口. #### **2.5.加密三级TCP代理** 一级TCP代理VPS_01,IP:22.22.22.22 -`./proxy tcp -t tcp -p ":38080" -T tcp -P "66.66.66.66:8080" -C proxy.crt -K proxy.key` +`./proxy tcp -t tls -p ":38080" -T tcp -P "66.66.66.66:8080" -C proxy.crt -K proxy.key` 二级TCP代理VPS_02,IP:33.33.33.33 -`./proxy tcp -t tcp -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key` +`./proxy tcp -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key` 三级TCP代理(本地) `./proxy tcp -p ":8080" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key` 那么访问本地8080端口就是通过加密TCP隧道访问66.66.66.66的8080端口. @@ -428,16 +428,16 @@ VPS(IP:22.22.22.33)执行: #### **3.4.加密二级UDP代理** VPS(IP:22.22.22.33)执行: -`./proxy tcp -t tcp -p ":33080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key` +`./proxy tcp -t tls -p ":33080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key` 本地执行: `./proxy udp -p ":5353" -T tls -P "22.22.22.33:33080" -C proxy.crt -K proxy.key` 那么访问本地UDP:5353端口就是通过加密TCP隧道,通过VPS访问8.8.8.8的UDP:53端口. #### **3.5.加密三级UDP代理** 一级TCP代理VPS_01,IP:22.22.22.22 -`./proxy tcp -t tcp -p ":38080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key` +`./proxy tcp -t tls -p ":38080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key` 二级TCP代理VPS_02,IP:33.33.33.33 -`./proxy tcp -t tcp -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key` +`./proxy tcp -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key` 三级TCP代理(本地) `./proxy udp -p ":5353" -T tls -P "33.33.33.33:28080" -C proxy.crt -K proxy.key` 那么访问本地5353端口就是通过加密TCP隧道,通过VPS_01访问8.8.8.8的53端口. From 362ada2ebbef4bd21d620a7b26e6d584b27809d9 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Mon, 12 Mar 2018 13:59:25 +0800 Subject: [PATCH 14/76] v4.5 Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 4 + README.md | 2 +- README_ZH.md | 2 +- config.go | 16 ++-- install_auto.sh | 2 +- main.go | 2 +- release.sh | 2 +- services/args.go | 7 +- services/mux_bridge.go | 184 +++++++++++++++++++++++------------------ services/mux_client.go | 31 +++++-- services/mux_server.go | 47 +++++++++-- 11 files changed, 193 insertions(+), 106 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b784585..f510159 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,8 @@ proxy更新日志 +v4.5 +1.优化了mux内网穿透连接管理逻辑,增强了稳定性. +2.mux内网穿透增加了tcp和kcp协议支持,之前是tls,现在支持三种协议tcp,tls,kcp. + v4.4 1.增加了协议转换sps功能,代理协议转换使用的是sps子命令(socks+https的缩写), sps本身不提供代理功能,只是接受代理请求"转换并转发"给已经存在的http(s)代理 diff --git a/README.md b/README.md index c3a9e5c..3eb6ab6 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ If the installation fails or your VPS is not a linux64 system, please follow the Download address: https://github.com/snail007/goproxy/releases ```shell cd /root/proxy/ -wget https://github.com/snail007/goproxy/releases/download/v4.4/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.5/proxy-linux-amd64.tar.gz ``` #### **2.Download the automatic installation script** ```shell diff --git a/README_ZH.md b/README_ZH.md index e3e2974..5a20d8e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -146,7 +146,7 @@ curl -L https://raw.githubusercontent.com/snail007/goproxy/master/install_auto.s 下载地址:https://github.com/snail007/goproxy/releases ```shell cd /root/proxy/ -wget https://github.com/snail007/goproxy/releases/download/v4.4/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.5/proxy-linux-amd64.tar.gz ``` #### **2.下载自动安装脚本** ```shell diff --git a/config.go b/config.go index c105bb4..f980254 100755 --- a/config.go +++ b/config.go @@ -126,31 +126,34 @@ func initConfig() (err error) { //########mux-server######### muxServer := app.Command("server", "proxy on mux server mode") muxServerArgs.Parent = muxServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + muxServerArgs.ParentType = muxServer.Flag("parent-type", "parent protocol type ").Default("tls").Short('T').Enum("tls", "tcp", "kcp") muxServerArgs.CertFile = muxServer.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() muxServerArgs.KeyFile = muxServer.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() - muxServerArgs.Timeout = muxServer.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int() + muxServerArgs.Timeout = muxServer.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int() muxServerArgs.IsUDP = muxServer.Flag("udp", "proxy on udp mux server mode").Default("false").Bool() muxServerArgs.Key = muxServer.Flag("k", "client key").Default("default").String() muxServerArgs.Route = muxServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings() - muxServerArgs.IsCompress = muxServer.Flag("c", "compress data when tcp mode").Default("false").Bool() + muxServerArgs.IsCompress = muxServer.Flag("c", "compress data when tcp|tls mode").Default("false").Bool() muxServerArgs.SessionCount = muxServer.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int() //########mux-client######### muxClient := app.Command("client", "proxy on mux client mode") muxClientArgs.Parent = muxClient.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + muxClientArgs.ParentType = muxClient.Flag("parent-type", "parent protocol type ").Default("tls").Short('T').Enum("tls", "tcp", "kcp") muxClientArgs.CertFile = muxClient.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() muxClientArgs.KeyFile = muxClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() - muxClientArgs.Timeout = muxClient.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int() + muxClientArgs.Timeout = muxClient.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int() muxClientArgs.Key = muxClient.Flag("k", "key same with server").Default("default").String() - muxClientArgs.IsCompress = muxClient.Flag("c", "compress data when tcp mode").Default("false").Bool() + muxClientArgs.IsCompress = muxClient.Flag("c", "compress data when tcp|tls mode").Default("false").Bool() muxClientArgs.SessionCount = muxClient.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int() //########mux-bridge######### muxBridge := app.Command("bridge", "proxy on mux bridge mode") muxBridgeArgs.CertFile = muxBridge.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() muxBridgeArgs.KeyFile = muxBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() - muxBridgeArgs.Timeout = muxBridge.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int() + muxBridgeArgs.Timeout = muxBridge.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int() muxBridgeArgs.Local = muxBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() + muxBridgeArgs.LocalType = muxBridge.Flag("local-type", "local protocol type ").Default("tls").Short('t').Enum("tls", "tcp", "kcp") //########tunnel-server######### tunnelServer := app.Command("tserver", "proxy on tunnel server mode") @@ -269,6 +272,9 @@ func initConfig() (err error) { httpArgs.KCP = kcpArgs socksArgs.KCP = kcpArgs spsArgs.KCP = kcpArgs + muxBridgeArgs.KCP = kcpArgs + muxServerArgs.KCP = kcpArgs + muxClientArgs.KCP = kcpArgs flags := log.Ldate if *debug { diff --git a/install_auto.sh b/install_auto.sh index 6a5b874..e25d8d7 100755 --- a/install_auto.sh +++ b/install_auto.sh @@ -5,7 +5,7 @@ if [ -e /tmp/proxy ]; then fi mkdir /tmp/proxy cd /tmp/proxy -wget https://github.com/snail007/goproxy/releases/download/v4.4/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.5/proxy-linux-amd64.tar.gz # #install proxy tar zxvf proxy-linux-amd64.tar.gz diff --git a/main.go b/main.go index f01ec8c..1547971 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,7 @@ import ( "syscall" ) -const APP_VERSION = "4.4" +const APP_VERSION = "4.5" func main() { err := initConfig() diff --git a/release.sh b/release.sh index 3125887..5009a87 100755 --- a/release.sh +++ b/release.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER="4.4" +VER="4.5" RELEASE="release-${VER}" rm -rf .cert mkdir .cert diff --git a/services/args.go b/services/args.go index dd23686..39496e1 100644 --- a/services/args.go +++ b/services/args.go @@ -26,6 +26,7 @@ const ( type MuxServerArgs struct { Parent *string + ParentType *string CertFile *string KeyFile *string CertBytes []byte @@ -39,9 +40,11 @@ type MuxServerArgs struct { Mgr *MuxServerManager IsCompress *bool SessionCount *int + KCP kcpcfg.KCPConfigArgs } type MuxClientArgs struct { Parent *string + ParentType *string CertFile *string KeyFile *string CertBytes []byte @@ -50,16 +53,18 @@ type MuxClientArgs struct { Timeout *int IsCompress *bool SessionCount *int + KCP kcpcfg.KCPConfigArgs } type MuxBridgeArgs struct { - Parent *string CertFile *string KeyFile *string CertBytes []byte KeyBytes []byte Local *string + LocalType *string Timeout *int IsCompress *bool + KCP kcpcfg.KCPConfigArgs } type TunnelServerArgs struct { Parent *string diff --git a/services/mux_bridge.go b/services/mux_bridge.go index 0b097ce..adf361d 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -9,6 +9,7 @@ import ( "snail007/proxy/utils" "strconv" "strings" + "sync" "time" "github.com/xtaci/smux" @@ -18,12 +19,14 @@ type MuxBridge struct { cfg MuxBridgeArgs clientControlConns utils.ConcurrentMap router utils.ClientKeyRouter + l *sync.Mutex } func NewMuxBridge() Service { b := &MuxBridge{ cfg: MuxBridgeArgs{}, clientControlConns: utils.NewConcurrentMap(), + l: &sync.Mutex{}, } b.router = utils.NewClientKeyRouter(&b.clientControlConns, 50000) return b @@ -36,7 +39,9 @@ func (s *MuxBridge) CheckArgs() { if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { log.Fatalf("cert and key file required") } - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if *s.cfg.LocalType == TYPE_TLS { + s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + } } func (s *MuxBridge) StopService() { @@ -48,91 +53,100 @@ func (s *MuxBridge) Start(args interface{}) (err error) { host, port, _ := net.SplitHostPort(*s.cfg.Local) p, _ := strconv.Atoi(port) sc := utils.NewServerChannel(host, p) - - err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, func(inConn net.Conn) { - reader := bufio.NewReader(inConn) - - var err error - var connType uint8 - var key string - err = utils.ReadPacket(reader, &connType, &key) - if err != nil { - log.Printf("read error,ERR:%s", err) - return - } - switch connType { - case CONN_SERVER: - var serverID string - err = utils.ReadPacketData(reader, &serverID) - if err != nil { - log.Printf("read error,ERR:%s", err) - return - } - log.Printf("server connection %s %s connected", serverID, key) - session, err := smux.Server(inConn, nil) - if err != nil { - utils.CloseConn(&inConn) - log.Printf("server session error,ERR:%s", err) - return - } - for { - stream, err := session.AcceptStream() - if err != nil { - session.Close() - utils.CloseConn(&inConn) - return - } - go s.callback(stream, serverID, key) - } - case CONN_CLIENT: - log.Printf("client connection %s connected", key) - session, err := smux.Client(inConn, nil) - if err != nil { - utils.CloseConn(&inConn) - log.Printf("client session error,ERR:%s", err) - return - } - keyInfo := strings.Split(key, "-") - if len(keyInfo) != 2 { - utils.CloseConn(&inConn) - log.Printf("client key format error,key:%s", key) - return - } - groupKey := keyInfo[0] - index := keyInfo[1] - if !s.clientControlConns.Has(groupKey) { - item := utils.NewConcurrentMap() - s.clientControlConns.Set(groupKey, &item) - } - _group, _ := s.clientControlConns.Get(groupKey) - group := _group.(*utils.ConcurrentMap) - group.Set(index, session) - // s.clientControlConns.Set(key, session) - go func() { - for { - if session.IsClosed() { - group.Remove(index) - if group.IsEmpty() { - s.clientControlConns.Remove(groupKey) - } - break - } - time.Sleep(time.Second * 5) - } - }() - //log.Printf("set client session,key: %s", key) - } - - }) + if *s.cfg.LocalType == TYPE_TCP { + err = sc.ListenTCP(s.handler) + } else if *s.cfg.LocalType == TYPE_TLS { + err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.handler) + } else if *s.cfg.LocalType == TYPE_KCP { + err = sc.ListenKCP(s.cfg.KCP, s.handler) + } if err != nil { return } - log.Printf("proxy on mux bridge mode %s", (*sc.Listener).Addr()) + log.Printf("%s bridge on %s", *s.cfg.LocalType, (*sc.Listener).Addr()) return } func (s *MuxBridge) Clean() { s.StopService() } +func (s *MuxBridge) handler(inConn net.Conn) { + reader := bufio.NewReader(inConn) + + var err error + var connType uint8 + var key string + err = utils.ReadPacket(reader, &connType, &key) + if err != nil { + log.Printf("read error,ERR:%s", err) + return + } + switch connType { + case CONN_SERVER: + var serverID string + err = utils.ReadPacketData(reader, &serverID) + if err != nil { + log.Printf("read error,ERR:%s", err) + return + } + log.Printf("server connection %s %s connected", serverID, key) + session, err := smux.Server(inConn, nil) + if err != nil { + utils.CloseConn(&inConn) + log.Printf("server session error,ERR:%s", err) + return + } + for { + stream, err := session.AcceptStream() + if err != nil { + session.Close() + utils.CloseConn(&inConn) + return + } + go s.callback(stream, serverID, key) + } + case CONN_CLIENT: + log.Printf("client connection %s connected", key) + session, err := smux.Client(inConn, nil) + if err != nil { + utils.CloseConn(&inConn) + log.Printf("client session error,ERR:%s", err) + return + } + keyInfo := strings.Split(key, "-") + if len(keyInfo) != 2 { + utils.CloseConn(&inConn) + log.Printf("client key format error,key:%s", key) + return + } + groupKey := keyInfo[0] + index := keyInfo[1] + if !s.clientControlConns.Has(groupKey) { + item := utils.NewConcurrentMap() + s.clientControlConns.Set(groupKey, &item) + } + _group, _ := s.clientControlConns.Get(groupKey) + group := _group.(*utils.ConcurrentMap) + s.l.Lock() + defer s.l.Unlock() + group.Set(index, session) + // s.clientControlConns.Set(key, session) + go func() { + for { + if session.IsClosed() { + s.l.Lock() + defer s.l.Unlock() + if sess, ok := group.Get(index); ok && sess.(*smux.Session).IsClosed() { + group.Remove(index) + } + break + } + time.Sleep(time.Second * 5) + } + }() + //log.Printf("set client session,key: %s", key) + } + +} func (s *MuxBridge) callback(inConn net.Conn, serverID, key string) { try := 20 for { @@ -145,12 +159,22 @@ func (s *MuxBridge) callback(inConn net.Conn, serverID, key string) { } _group, ok := s.clientControlConns.Get(key) if !ok { - log.Printf("client %s session not exists for server stream %s", key, serverID) + log.Printf("client %s session not exists for server stream %s, retrying...", key, serverID) time.Sleep(time.Second * 3) continue } group := _group.(*utils.ConcurrentMap) - index := group.Keys()[rand.Intn(group.Count())] + keys := group.Keys() + keysLen := len(keys) + i := 0 + if keysLen > 0 { + i = rand.Intn(keysLen) + } else { + log.Printf("client %s session empty for server stream %s, retrying...", key, serverID) + time.Sleep(time.Second * 3) + continue + } + index := keys[i] log.Printf("select client : %s-%s", key, index) session, _ := group.Get(index) stream, err := session.(*smux.Session).OpenStream() diff --git a/services/mux_client.go b/services/mux_client.go index c41f6e6..79af08c 100644 --- a/services/mux_client.go +++ b/services/mux_client.go @@ -36,7 +36,9 @@ func (s *MuxClient) CheckArgs() { if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { log.Fatalf("cert and key file required") } - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if *s.cfg.ParentType == "tls" { + s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + } } func (s *MuxClient) StopService() { @@ -45,8 +47,12 @@ func (s *MuxClient) Start(args interface{}) (err error) { s.cfg = args.(MuxClientArgs) s.CheckArgs() s.InitService() - log.Printf("proxy on mux client mode, compress %v", *s.cfg.IsCompress) - for i := 1; i <= *s.cfg.SessionCount; i++ { + log.Printf("%s client on %s", *s.cfg.ParentType, *s.cfg.Parent) + count := 1 + if *s.cfg.SessionCount > 0 { + count = *s.cfg.SessionCount + } + for i := 1; i <= count; i++ { log.Printf("session worker[%d] started", i) go func(i int) { defer func() { @@ -56,14 +62,12 @@ func (s *MuxClient) Start(args interface{}) (err error) { } }() for { - var _conn tls.Conn - _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + conn, err := s.getParentConn() if err != nil { log.Printf("connection err: %s, retrying...", err) time.Sleep(time.Second * 3) continue } - conn := net.Conn(&_conn) _, err = conn.Write(utils.BuildPacket(CONN_CLIENT, fmt.Sprintf("%s-%d", *s.cfg.Key, i))) if err != nil { conn.Close() @@ -119,7 +123,20 @@ func (s *MuxClient) Start(args interface{}) (err error) { func (s *MuxClient) Clean() { s.StopService() } - +func (s *MuxClient) getParentConn() (conn net.Conn, err error) { + if *s.cfg.ParentType == "tls" { + var _conn tls.Conn + _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + if err == nil { + conn = net.Conn(&_conn) + } + } else if *s.cfg.ParentType == "kcp" { + conn, err = utils.ConnectKCPHost(*s.cfg.Parent, s.cfg.KCP) + } else { + conn, err = utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout) + } + return +} func (s *MuxClient) ServeUDP(inConn *smux.Stream, localAddr, ID string) { for { diff --git a/services/mux_server.go b/services/mux_server.go index d7c961b..643443b 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -43,7 +43,7 @@ func (s *MuxServerManager) Start(args interface{}) (err error) { s.cfg = args.(MuxServerArgs) s.CheckArgs() if *s.cfg.Parent != "" { - log.Printf("use tls parent %s", *s.cfg.Parent) + log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent) } else { log.Fatalf("parent required") } @@ -88,6 +88,8 @@ func (s *MuxServerManager) Start(args interface{}) (err error) { Mgr: s, IsCompress: s.cfg.IsCompress, SessionCount: s.cfg.SessionCount, + KCP: s.cfg.KCP, + ParentType: s.cfg.ParentType, }) if err != nil { @@ -105,7 +107,9 @@ func (s *MuxServerManager) CheckArgs() { if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { log.Fatalf("cert and key file required") } - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if *s.cfg.ParentType == "tls" { + s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + } } func (s *MuxServerManager) InitService() { } @@ -152,7 +156,7 @@ func (s *MuxServer) Start(args interface{}) (err error) { if err != nil { return } - log.Printf("proxy on udp mux server mode %s", (*s.sc.UDPListener).LocalAddr()) + log.Printf("server on %s", (*s.sc.UDPListener).LocalAddr()) } else { err = s.sc.ListenTCP(func(inConn net.Conn) { defer func() { @@ -201,7 +205,7 @@ func (s *MuxServer) Start(args interface{}) (err error) { if err != nil { return } - log.Printf("proxy on mux server mode %s, compress %v", (*s.sc.Listener).Addr(), *s.cfg.IsCompress) + log.Printf("%s server on %s", *s.cfg.ParentType, (*s.sc.Listener).Addr()) } return } @@ -209,7 +213,11 @@ func (s *MuxServer) Clean() { } func (s *MuxServer) GetOutConn() (outConn net.Conn, ID string, err error) { - outConn, err = s.GetConn(fmt.Sprintf("%d", rand.Intn(*s.cfg.SessionCount))) + i := 1 + if *s.cfg.SessionCount > 0 { + i = rand.Intn(*s.cfg.SessionCount) + } + outConn, err = s.GetConn(fmt.Sprintf("%d", i)) if err != nil { log.Printf("connection err: %s", err) return @@ -240,12 +248,11 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { var session *smux.Session _session, ok := s.sessions.Get(index) if !ok { - var _conn tls.Conn - _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + var c net.Conn + c, err = s.getParentConn() if err != nil { return } - c := net.Conn(&_conn) _, err = c.Write(utils.BuildPacket(CONN_SERVER, *s.cfg.Key, s.cfg.Mgr.serverID)) if err != nil { c.Close() @@ -266,10 +273,34 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { if err != nil { session.Close() s.sessions.Remove(index) + } else { + go func() { + for { + if session.IsClosed() { + s.sessions.Remove(index) + break + } + time.Sleep(time.Second * 5) + } + }() } return } +func (s *MuxServer) getParentConn() (conn net.Conn, err error) { + if *s.cfg.ParentType == "tls" { + var _conn tls.Conn + _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + if err == nil { + conn = net.Conn(&_conn) + } + } else if *s.cfg.ParentType == "kcp" { + conn, err = utils.ConnectKCPHost(*s.cfg.Parent, s.cfg.KCP) + } else { + conn, err = utils.ConnectHost(*s.cfg.Parent, *s.cfg.Timeout) + } + return +} func (s *MuxServer) UDPConnDeamon() { go func() { defer func() { From 34e9e362b925833a45c312c3b768a74d3dbaee93 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Mon, 12 Mar 2018 17:31:35 +0800 Subject: [PATCH 15/76] Signed-off-by: arraykeys@gmail.com --- config.go | 3 + services/args.go | 6 ++ services/http.go | 10 +- services/mux_bridge.go | 2 +- services/mux_client.go | 2 +- services/mux_server.go | 2 +- services/socks.go | 24 +++-- services/sps.go | 12 ++- services/tcp.go | 4 +- services/tunnel_bridge.go | 2 +- services/tunnel_client.go | 2 +- services/tunnel_server.go | 4 +- services/udp.go | 2 +- utils/functions.go | 187 ++++++++++++++++++++++++-------------- utils/serve-channel.go | 4 +- utils/structs.go | 36 ++++---- 16 files changed, 194 insertions(+), 108 deletions(-) diff --git a/config.go b/config.go index f980254..5d5e671 100755 --- a/config.go +++ b/config.go @@ -73,6 +73,7 @@ func initConfig() (err error) { //########http######### http := app.Command("http", "proxy on http mode") httpArgs.Parent = http.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + httpArgs.CaCertFile = http.Flag("ca", "ca cert file for tls").Default("").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", "kcp") @@ -189,6 +190,7 @@ func initConfig() (err error) { 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.CaCertFile = socks.Flag("ca", "ca cert file for tls").Default("").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() socksArgs.SSHKeyFile = socks.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String() @@ -213,6 +215,7 @@ func initConfig() (err error) { spsArgs.Parent = sps.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() spsArgs.CertFile = sps.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() spsArgs.KeyFile = sps.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + spsArgs.CaCertFile = sps.Flag("ca", "ca cert file for tls").Default("").String() spsArgs.Timeout = sps.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('i').Default("2000").Int() spsArgs.ParentType = sps.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "kcp") spsArgs.LocalType = sps.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp", "kcp") diff --git a/services/args.go b/services/args.go index 39496e1..2786f79 100644 --- a/services/args.go +++ b/services/args.go @@ -120,6 +120,8 @@ type HTTPArgs struct { Parent *string CertFile *string KeyFile *string + CaCertFile *string + CaCertBytes []byte CertBytes []byte KeyBytes []byte Local *string @@ -169,6 +171,8 @@ type SocksArgs struct { LocalType *string CertFile *string KeyFile *string + CaCertFile *string + CaCertBytes []byte CertBytes []byte KeyBytes []byte SSHKeyFile *string @@ -199,6 +203,8 @@ type SPSArgs struct { Parent *string CertFile *string KeyFile *string + CaCertFile *string + CaCertBytes []byte CertBytes []byte KeyBytes []byte Local *string diff --git a/services/http.go b/services/http.go index 8621a8a..9587303 100644 --- a/services/http.go +++ b/services/http.go @@ -41,6 +41,12 @@ func (s *HTTP) CheckArgs() { } if *s.cfg.ParentType == "tls" || *s.cfg.LocalType == "tls" { s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if *s.cfg.CaCertFile != "" { + s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile) + if err != nil { + log.Fatalf("read ca file error,ERR:%s", err) + } + } } if *s.cfg.ParentType == "ssh" { if *s.cfg.SSHUser == "" { @@ -128,7 +134,7 @@ func (s *HTTP) Start(args interface{}) (err error) { if *s.cfg.LocalType == TYPE_TCP { err = sc.ListenTCP(s.callback) } else if *s.cfg.LocalType == TYPE_TLS { - err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback) + err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes, s.callback) } else if *s.cfg.LocalType == TYPE_KCP { err = sc.ListenKCP(s.cfg.KCP, s.callback) } @@ -321,7 +327,7 @@ func (s *HTTP) InitOutConnPool() { *s.cfg.CheckParentInterval, *s.cfg.ParentType, s.cfg.KCP, - s.cfg.CertBytes, s.cfg.KeyBytes, + s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes, s.Resolve(*s.cfg.Parent), *s.cfg.Timeout, *s.cfg.PoolSize, diff --git a/services/mux_bridge.go b/services/mux_bridge.go index adf361d..37e52c7 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -56,7 +56,7 @@ func (s *MuxBridge) Start(args interface{}) (err error) { if *s.cfg.LocalType == TYPE_TCP { err = sc.ListenTCP(s.handler) } else if *s.cfg.LocalType == TYPE_TLS { - err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.handler) + err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.handler) } else if *s.cfg.LocalType == TYPE_KCP { err = sc.ListenKCP(s.cfg.KCP, s.handler) } diff --git a/services/mux_client.go b/services/mux_client.go index 79af08c..56b6bbf 100644 --- a/services/mux_client.go +++ b/services/mux_client.go @@ -126,7 +126,7 @@ func (s *MuxClient) Clean() { func (s *MuxClient) getParentConn() (conn net.Conn, err error) { if *s.cfg.ParentType == "tls" { var _conn tls.Conn - _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) if err == nil { conn = net.Conn(&_conn) } diff --git a/services/mux_server.go b/services/mux_server.go index 643443b..69e47b1 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -290,7 +290,7 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { func (s *MuxServer) getParentConn() (conn net.Conn, err error) { if *s.cfg.ParentType == "tls" { var _conn tls.Conn - _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) if err == nil { conn = net.Conn(&_conn) } diff --git a/services/socks.go b/services/socks.go index b69ed91..bea8180 100644 --- a/services/socks.go +++ b/services/socks.go @@ -37,16 +37,28 @@ func NewSocks() Service { func (s *Socks) CheckArgs() { var err error - if *s.cfg.LocalType == "tls" { + if *s.cfg.LocalType == "tls" || (*s.cfg.Parent != "" && *s.cfg.ParentType == "tls") { s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if *s.cfg.CaCertFile != "" { + s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile) + if err != nil { + log.Fatalf("read ca file error,ERR:%s", err) + } + } } if *s.cfg.Parent != "" { if *s.cfg.ParentType == "" { log.Fatalf("parent type unkown,use -T ") } - if *s.cfg.ParentType == "tls" { - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) - } + // if *s.cfg.ParentType == "tls" { + // s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + // if *s.cfg.CaCertFile != "" { + // s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile) + // if err != nil { + // log.Fatalf("read ca file error,ERR:%s", err) + // } + // } + // } if *s.cfg.ParentType == "ssh" { if *s.cfg.SSHUser == "" { log.Fatalf("ssh user required") @@ -138,7 +150,7 @@ func (s *Socks) Start(args interface{}) (err error) { if *s.cfg.LocalType == TYPE_TCP { err = sc.ListenTCP(s.socksConnCallback) } else if *s.cfg.LocalType == TYPE_TLS { - err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.socksConnCallback) + err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.socksConnCallback) } else if *s.cfg.LocalType == TYPE_KCP { err = sc.ListenKCP(s.cfg.KCP, s.socksConnCallback) } @@ -471,7 +483,7 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n case "tcp": if *s.cfg.ParentType == "tls" { var _outConn tls.Conn - _outConn, err = utils.TlsConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + _outConn, err = utils.TlsConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) outConn = net.Conn(&_outConn) } else if *s.cfg.ParentType == "kcp" { outConn, err = utils.ConnectKCPHost(s.Resolve(*s.cfg.Parent), s.cfg.KCP) diff --git a/services/sps.go b/services/sps.go index 1e4e4a5..1b5de03 100644 --- a/services/sps.go +++ b/services/sps.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "io/ioutil" "log" "net" "runtime/debug" @@ -34,6 +35,13 @@ func (s *SPS) CheckArgs() { } if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS { s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if *s.cfg.CaCertFile != "" { + var err error + s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile) + if err != nil { + log.Fatalf("read ca file error,ERR:%s", err) + } + } } } func (s *SPS) InitService() { @@ -47,7 +55,7 @@ func (s *SPS) InitOutConnPool() { 0, *s.cfg.ParentType, s.cfg.KCP, - s.cfg.CertBytes, s.cfg.KeyBytes, + s.cfg.CertBytes, s.cfg.KeyBytes, nil, *s.cfg.Parent, *s.cfg.Timeout, 0, @@ -75,7 +83,7 @@ func (s *SPS) Start(args interface{}) (err error) { if *s.cfg.LocalType == TYPE_TCP { err = sc.ListenTCP(s.callback) } else if *s.cfg.LocalType == TYPE_TLS { - err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback) + err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.callback) } else if *s.cfg.LocalType == TYPE_KCP { err = sc.ListenKCP(s.cfg.KCP, s.callback) } diff --git a/services/tcp.go b/services/tcp.go index 85136a2..34df8ee 100644 --- a/services/tcp.go +++ b/services/tcp.go @@ -56,7 +56,7 @@ func (s *TCP) Start(args interface{}) (err error) { if *s.cfg.LocalType == TYPE_TCP { err = sc.ListenTCP(s.callback) } else if *s.cfg.LocalType == TYPE_TLS { - err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.callback) + err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.callback) } else if *s.cfg.LocalType == TYPE_KCP { err = sc.ListenKCP(s.cfg.KCP, s.callback) } @@ -172,7 +172,7 @@ func (s *TCP) InitOutConnPool() { *s.cfg.CheckParentInterval, *s.cfg.ParentType, s.cfg.KCP, - s.cfg.CertBytes, s.cfg.KeyBytes, + s.cfg.CertBytes, s.cfg.KeyBytes, nil, *s.cfg.Parent, *s.cfg.Timeout, *s.cfg.PoolSize, diff --git a/services/tunnel_bridge.go b/services/tunnel_bridge.go index bb048dd..ddae0a7 100644 --- a/services/tunnel_bridge.go +++ b/services/tunnel_bridge.go @@ -51,7 +51,7 @@ func (s *TunnelBridge) Start(args interface{}) (err error) { p, _ := strconv.Atoi(port) sc := utils.NewServerChannel(host, p) - err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, func(inConn net.Conn) { + err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, func(inConn net.Conn) { //log.Printf("connection from %s ", inConn.RemoteAddr()) reader := bufio.NewReader(inConn) diff --git a/services/tunnel_client.go b/services/tunnel_client.go index 94daf95..0d93483 100644 --- a/services/tunnel_client.go +++ b/services/tunnel_client.go @@ -161,7 +161,7 @@ func (s *TunnelClient) GetInConn(typ uint8, data ...string) (outConn net.Conn, e } func (s *TunnelClient) GetConn() (conn net.Conn, err error) { var _conn tls.Conn - _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) if err == nil { conn = net.Conn(&_conn) } diff --git a/services/tunnel_server.go b/services/tunnel_server.go index 9ef3763..b4fbfa0 100644 --- a/services/tunnel_server.go +++ b/services/tunnel_server.go @@ -174,7 +174,7 @@ func (s *TunnelServerManager) GetOutConn(typ uint8) (outConn net.Conn, ID string } func (s *TunnelServerManager) GetConn() (conn net.Conn, err error) { var _conn tls.Conn - _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) if err == nil { conn = net.Conn(&_conn) } @@ -280,7 +280,7 @@ func (s *TunnelServer) GetOutConn(typ uint8) (outConn net.Conn, ID string, err e } func (s *TunnelServer) GetConn() (conn net.Conn, err error) { var _conn tls.Conn - _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes) + _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) if err == nil { conn = net.Conn(&_conn) } diff --git a/services/udp.go b/services/udp.go index 6f63f0d..74951fb 100644 --- a/services/udp.go +++ b/services/udp.go @@ -210,7 +210,7 @@ func (s *UDP) InitOutConnPool() { *s.cfg.CheckParentInterval, *s.cfg.ParentType, kcpcfg.KCPConfigArgs{}, - s.cfg.CertBytes, s.cfg.KeyBytes, + s.cfg.CertBytes, s.cfg.KeyBytes, nil, *s.cfg.Parent, *s.cfg.Timeout, *s.cfg.PoolSize, diff --git a/utils/functions.go b/utils/functions.go index 3927909..9d75e53 100755 --- a/utils/functions.go +++ b/utils/functions.go @@ -86,14 +86,14 @@ func ioCopy(dst io.ReadWriter, src io.ReadWriter) (err error) { } } } -func TlsConnectHost(host string, timeout int, certBytes, keyBytes []byte) (conn tls.Conn, err error) { +func TlsConnectHost(host string, timeout int, certBytes, keyBytes, caCertBytes []byte) (conn tls.Conn, err error) { h := strings.Split(host, ":") port, _ := strconv.Atoi(h[1]) - return TlsConnect(h[0], port, timeout, certBytes, keyBytes) + return TlsConnect(h[0], port, timeout, certBytes, keyBytes, caCertBytes) } -func TlsConnect(host string, port, timeout int, certBytes, keyBytes []byte) (conn tls.Conn, err error) { - conf, err := getRequestTlsConfig(certBytes, keyBytes) +func TlsConnect(host string, port, timeout int, certBytes, keyBytes, caCertBytes []byte) (conn tls.Conn, err error) { + conf, err := getRequestTlsConfig(certBytes, keyBytes, caCertBytes) if err != nil { return } @@ -103,8 +103,24 @@ func TlsConnect(host string, port, timeout int, certBytes, keyBytes []byte) (con } return *tls.Client(_conn, conf), err } -func getRequestTlsConfig(certBytes, keyBytes []byte) (conf *tls.Config, err error) { - block, _ := pem.Decode(certBytes) +func getRequestTlsConfig(certBytes, keyBytes, caCertBytes []byte) (conf *tls.Config, err error) { + + var cert tls.Certificate + cert, err = tls.X509KeyPair(certBytes, keyBytes) + if err != nil { + return + } + serverCertPool := x509.NewCertPool() + caBytes := certBytes + if caCertBytes != nil { + caBytes = caCertBytes + + } + ok := serverCertPool.AppendCertsFromPEM(caBytes) + if !ok { + err = errors.New("failed to parse root certificate") + } + block, _ := pem.Decode(caBytes) if block == nil { panic("failed to parse certificate PEM") } @@ -112,34 +128,24 @@ func getRequestTlsConfig(certBytes, keyBytes []byte) (conf *tls.Config, err erro if x509Cert == nil { panic("failed to parse block") } - var cert tls.Certificate - cert, err = tls.X509KeyPair(certBytes, keyBytes) - if err != nil { - return - } - serverCertPool := x509.NewCertPool() - ok := serverCertPool.AppendCertsFromPEM(certBytes) - if !ok { - err = errors.New("failed to parse root certificate") - } conf = &tls.Config{ RootCAs: serverCertPool, Certificates: []tls.Certificate{cert}, - InsecureSkipVerify: false, + InsecureSkipVerify: true, ServerName: x509Cert.Subject.CommonName, - // VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - // opts := x509.VerifyOptions{ - // Roots: serverCertPool, - // } - // for _, rawCert := range rawCerts { - // cert, _ := x509.ParseCertificate(rawCert) - // _, err := cert.Verify(opts) - // if err != nil { - // return err - // } - // } - // return nil - // }, + VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + opts := x509.VerifyOptions{ + Roots: serverCertPool, + } + for _, rawCert := range rawCerts { + cert, _ := x509.ParseCertificate(rawCert) + _, err := cert.Verify(opts) + if err != nil { + return err + } + } + return nil + }, } return } @@ -165,22 +171,19 @@ func ConnectKCPHost(hostAndPort string, config kcpcfg.KCPConfigArgs) (conn net.C return NewCompStream(kcpconn), err } -func ListenTls(ip string, port int, certBytes, keyBytes []byte) (ln *net.Listener, err error) { - block, _ := pem.Decode(certBytes) - if block == nil { - panic("failed to parse certificate PEM") - } - x509Cert, _ := x509.ParseCertificate(block.Bytes) - if x509Cert == nil { - panic("failed to parse block") - } +func ListenTls(ip string, port int, certBytes, keyBytes, caCertBytes []byte) (ln *net.Listener, err error) { + var cert tls.Certificate cert, err = tls.X509KeyPair(certBytes, keyBytes) if err != nil { return } clientCertPool := x509.NewCertPool() - ok := clientCertPool.AppendCertsFromPEM(certBytes) + caBytes := certBytes + if caCertBytes != nil { + caBytes = caCertBytes + } + ok := clientCertPool.AppendCertsFromPEM(caBytes) if !ok { err = errors.New("failed to parse root certificate") } @@ -188,21 +191,6 @@ func ListenTls(ip string, port int, certBytes, keyBytes []byte) (ln *net.Listene ClientCAs: clientCertPool, Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, - ServerName: x509Cert.Subject.CommonName, - // VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - // opts := x509.VerifyOptions{ - // Roots: clientCertPool, - // } - // for _, rawCert := range rawCerts { - // cert, _ := x509.ParseCertificate(rawCert) - // _, err := cert.Verify(opts) - // fmt.Println("SERVER ERR:", err) - // if err != nil { - // return err - // } - // } - // return nil - // }, } _ln, err := tls.Listen("tcp", fmt.Sprintf("%s:%d", ip, port), config) if err == nil { @@ -245,27 +233,88 @@ func CloseConn(conn *net.Conn) { } } func Keygen() (err error) { - cmd := exec.Command("sh", "-c", "openssl genrsa -out proxy.key 2048") - out, err := cmd.CombinedOutput() - if err != nil { - log.Printf("err:%s", err) - return - } - fmt.Println(string(out)) CList := []string{"AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AR", "AT", "AU", "AZ", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BR", "BS", "BW", "BY", "BZ", "CA", "CF", "CG", "CH", "CK", "CL", "CM", "CN", "CO", "CR", "CS", "CU", "CY", "CZ", "DE", "DJ", "DK", "DO", "DZ", "EC", "EE", "EG", "ES", "ET", "FI", "FJ", "FR", "GA", "GB", "GD", "GE", "GF", "GH", "GI", "GM", "GN", "GR", "GT", "GU", "GY", "HK", "HN", "HT", "HU", "ID", "IE", "IL", "IN", "IQ", "IR", "IS", "IT", "JM", "JO", "JP", "KE", "KG", "KH", "KP", "KR", "KT", "KW", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", "MA", "MC", "MD", "MG", "ML", "MM", "MN", "MO", "MS", "MT", "MU", "MV", "MW", "MX", "MY", "MZ", "NA", "NE", "NG", "NI", "NL", "NO", "NP", "NR", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PR", "PT", "PY", "QA", "RO", "RU", "SA", "SB", "SC", "SD", "SE", "SG", "SI", "SK", "SL", "SM", "SN", "SO", "SR", "ST", "SV", "SY", "SZ", "TD", "TG", "TH", "TJ", "TM", "TN", "TO", "TR", "TT", "TW", "TZ", "UA", "UG", "US", "UY", "UZ", "VC", "VE", "VN", "YE", "YU", "ZA", "ZM", "ZR", "ZW"} domainSubfixList := []string{".com", ".edu", ".gov", ".int", ".mil", ".net", ".org", ".biz", ".info", ".pro", ".name", ".museum", ".coop", ".aero", ".xxx", ".idv", ".ac", ".ad", ".ae", ".af", ".ag", ".ai", ".al", ".am", ".an", ".ao", ".aq", ".ar", ".as", ".at", ".au", ".aw", ".az", ".ba", ".bb", ".bd", ".be", ".bf", ".bg", ".bh", ".bi", ".bj", ".bm", ".bn", ".bo", ".br", ".bs", ".bt", ".bv", ".bw", ".by", ".bz", ".ca", ".cc", ".cd", ".cf", ".cg", ".ch", ".ci", ".ck", ".cl", ".cm", ".cn", ".co", ".cr", ".cu", ".cv", ".cx", ".cy", ".cz", ".de", ".dj", ".dk", ".dm", ".do", ".dz", ".ec", ".ee", ".eg", ".eh", ".er", ".es", ".et", ".eu", ".fi", ".fj", ".fk", ".fm", ".fo", ".fr", ".ga", ".gd", ".ge", ".gf", ".gg", ".gh", ".gi", ".gl", ".gm", ".gn", ".gp", ".gq", ".gr", ".gs", ".gt", ".gu", ".gw", ".gy", ".hk", ".hm", ".hn", ".hr", ".ht", ".hu", ".id", ".ie", ".il", ".im", ".in", ".io", ".iq", ".ir", ".is", ".it", ".je", ".jm", ".jo", ".jp", ".ke", ".kg", ".kh", ".ki", ".km", ".kn", ".kp", ".kr", ".kw", ".ky", ".kz", ".la", ".lb", ".lc", ".li", ".lk", ".lr", ".ls", ".lt", ".lu", ".lv", ".ly", ".ma", ".mc", ".md", ".mg", ".mh", ".mk", ".ml", ".mm", ".mn", ".mo", ".mp", ".mq", ".mr", ".ms", ".mt", ".mu", ".mv", ".mw", ".mx", ".my", ".mz", ".na", ".nc", ".ne", ".nf", ".ng", ".ni", ".nl", ".no", ".np", ".nr", ".nu", ".nz", ".om", ".pa", ".pe", ".pf", ".pg", ".ph", ".pk", ".pl", ".pm", ".pn", ".pr", ".ps", ".pt", ".pw", ".py", ".qa", ".re", ".ro", ".ru", ".rw", ".sa", ".sb", ".sc", ".sd", ".se", ".sg", ".sh", ".si", ".sj", ".sk", ".sl", ".sm", ".sn", ".so", ".sr", ".st", ".sv", ".sy", ".sz", ".tc", ".td", ".tf", ".tg", ".th", ".tj", ".tk", ".tl", ".tm", ".tn", ".to", ".tp", ".tr", ".tt", ".tv", ".tw", ".tz", ".ua", ".ug", ".uk", ".um", ".us", ".uy", ".uz", ".va", ".vc", ".ve", ".vg", ".vi", ".vn", ".vu", ".wf", ".ws", ".ye", ".yt", ".yu", ".yr", ".za", ".zm", ".zw"} C := CList[int(RandInt(4))%len(CList)] ST := RandString(int(RandInt(4) % 10)) O := RandString(int(RandInt(4) % 10)) CN := strings.ToLower(RandString(int(RandInt(4)%10)) + domainSubfixList[int(RandInt(4))%len(domainSubfixList)]) - cmdStr := fmt.Sprintf("openssl req -new -key proxy.key -x509 -days 36500 -out proxy.crt -subj /C=%s/ST=%s/O=%s/CN=%s", C, ST, O, CN) - cmd = exec.Command("sh", "-c", cmdStr) - out, err = cmd.CombinedOutput() - if err != nil { - log.Printf("err:%s", err) - return + log.Printf("C: %s, ST: %s, O: %s, CN: %s", C, ST, O, CN) + var out []byte + if len(os.Args) == 3 && os.Args[2] == "ca" { + cmd := exec.Command("sh", "-c", "openssl genrsa -out ca.key 2048") + out, err = cmd.CombinedOutput() + if err != nil { + log.Printf("err:%s", err) + return + } + fmt.Println(string(out)) + + cmdStr := fmt.Sprintf("openssl req -new -key ca.key -x509 -days 36500 -out ca.crt -subj /C=%s/ST=%s/O=%s/CN=%s", C, ST, O, "*."+CN) + cmd = exec.Command("sh", "-c", cmdStr) + out, err = cmd.CombinedOutput() + if err != nil { + log.Printf("err:%s", err) + return + } + fmt.Println(string(out)) + } else if len(os.Args) == 5 && os.Args[2] == "ca" && os.Args[3] != "" && os.Args[4] != "" { + certBytes, _ := ioutil.ReadFile("ca.crt") + block, _ := pem.Decode(certBytes) + if block == nil || certBytes == nil { + panic("failed to parse ca certificate PEM") + } + x509Cert, _ := x509.ParseCertificate(block.Bytes) + if x509Cert == nil { + panic("failed to parse block") + } + name := os.Args[3] + days := os.Args[4] + cmd := exec.Command("sh", "-c", "openssl genrsa -out "+name+".key 2048") + out, err = cmd.CombinedOutput() + if err != nil { + log.Printf("err:%s", err) + return + } + fmt.Println(string(out)) + + cmdStr := fmt.Sprintf("openssl req -new -nodes -key %s.key -out %s.csr -days %s -subj /C=%s/ST=%s/O=%s/CN=%s", name, name, days, C, ST, O, CN) + cmd = exec.Command("sh", "-c", cmdStr) + out, err = cmd.CombinedOutput() + if err != nil { + log.Printf("err:%s", err) + return + } + fmt.Println(string(out)) + + cmdStr = fmt.Sprintf("openssl x509 -req -in %s.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out %s.crt", name, name) + cmd = exec.Command("sh", "-c", cmdStr) + out, err = cmd.CombinedOutput() + if err != nil { + log.Printf("err:%s", err) + return + } + + fmt.Println(string(out)) + } else if len(os.Args) == 2 { + cmd := exec.Command("sh", "-c", "openssl genrsa -out proxy.key 2048") + out, err = cmd.CombinedOutput() + if err != nil { + log.Printf("err:%s", err) + return + } + fmt.Println(string(out)) + + cmdStr := fmt.Sprintf("openssl req -new -key proxy.key -x509 -days 36500 -out proxy.crt -subj /C=%s/ST=%s/O=%s/CN=%s", C, ST, O, CN) + cmd = exec.Command("sh", "-c", cmdStr) + out, err = cmd.CombinedOutput() + if err != nil { + log.Printf("err:%s", err) + return + } + fmt.Println(string(out)) } - fmt.Println(string(out)) + return } func GetAllInterfaceAddr() ([]net.IP, error) { diff --git a/utils/serve-channel.go b/utils/serve-channel.go index a53f00e..596cede 100644 --- a/utils/serve-channel.go +++ b/utils/serve-channel.go @@ -42,8 +42,8 @@ func NewServerChannelHost(host string) ServerChannel { func (sc *ServerChannel) SetErrAcceptHandler(fn func(err error)) { sc.errAcceptHandler = fn } -func (sc *ServerChannel) ListenTls(certBytes, keyBytes []byte, fn func(conn net.Conn)) (err error) { - sc.Listener, err = ListenTls(sc.ip, sc.port, certBytes, keyBytes) +func (sc *ServerChannel) ListenTls(certBytes, keyBytes, caCertBytes []byte, fn func(conn net.Conn)) (err error) { + sc.Listener, err = ListenTls(sc.ip, sc.port, certBytes, keyBytes, caCertBytes) if err == nil { go func() { defer func() { diff --git a/utils/structs.go b/utils/structs.go index a88e75c..9528e4f 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -491,25 +491,27 @@ func (req *HTTPRequest) addPortIfNot() (newHost string) { } type OutPool struct { - Pool ConnPool - dur int - typ string - certBytes []byte - keyBytes []byte - kcp kcpcfg.KCPConfigArgs - address string - timeout int + Pool ConnPool + dur int + typ string + certBytes []byte + keyBytes []byte + caCertBytes []byte + kcp kcpcfg.KCPConfigArgs + address string + timeout int } -func NewOutPool(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutPool) { +func NewOutPool(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyBytes, caCertBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutPool) { op = OutPool{ - dur: dur, - typ: typ, - certBytes: certBytes, - keyBytes: keyBytes, - kcp: kcp, - address: address, - timeout: timeout, + dur: dur, + typ: typ, + certBytes: certBytes, + keyBytes: keyBytes, + caCertBytes: caCertBytes, + kcp: kcp, + address: address, + timeout: timeout, } var err error op.Pool, err = NewConnPool(poolConfig{ @@ -543,7 +545,7 @@ func NewOutPool(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyByt func (op *OutPool) getConn() (conn interface{}, err error) { if op.typ == "tls" { var _conn tls.Conn - _conn, err = TlsConnectHost(op.address, op.timeout, op.certBytes, op.keyBytes) + _conn, err = TlsConnectHost(op.address, op.timeout, op.certBytes, op.keyBytes, op.caCertBytes) if err == nil { conn = net.Conn(&_conn) } From 9ce3a6e468deffe5445573fb25fe156ebfab365a Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Mon, 12 Mar 2018 18:15:14 +0800 Subject: [PATCH 16/76] Signed-off-by: arraykeys@gmail.com --- utils/functions.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/utils/functions.go b/utils/functions.go index 9d75e53..cc30c33 100755 --- a/utils/functions.go +++ b/utils/functions.go @@ -239,7 +239,7 @@ func Keygen() (err error) { ST := RandString(int(RandInt(4) % 10)) O := RandString(int(RandInt(4) % 10)) CN := strings.ToLower(RandString(int(RandInt(4)%10)) + domainSubfixList[int(RandInt(4))%len(domainSubfixList)]) - log.Printf("C: %s, ST: %s, O: %s, CN: %s", C, ST, O, CN) + //log.Printf("C: %s, ST: %s, O: %s, CN: %s", C, ST, O, CN) var out []byte if len(os.Args) == 3 && os.Args[2] == "ca" { cmd := exec.Command("sh", "-c", "openssl genrsa -out ca.key 2048") @@ -278,7 +278,8 @@ func Keygen() (err error) { } fmt.Println(string(out)) - cmdStr := fmt.Sprintf("openssl req -new -nodes -key %s.key -out %s.csr -days %s -subj /C=%s/ST=%s/O=%s/CN=%s", name, name, days, C, ST, O, CN) + cmdStr := fmt.Sprintf("openssl req -new -key %s.key -out %s.csr -subj /C=%s/ST=%s/O=%s/CN=%s", name, name, C, ST, O, CN) + fmt.Printf("%s", cmdStr) cmd = exec.Command("sh", "-c", cmdStr) out, err = cmd.CombinedOutput() if err != nil { @@ -287,7 +288,8 @@ func Keygen() (err error) { } fmt.Println(string(out)) - cmdStr = fmt.Sprintf("openssl x509 -req -in %s.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out %s.crt", name, name) + cmdStr = fmt.Sprintf("openssl x509 -req -days %s -in %s.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out %s.crt", days, name, name) + fmt.Printf("%s", cmdStr) cmd = exec.Command("sh", "-c", cmdStr) out, err = cmd.CombinedOutput() if err != nil { @@ -296,6 +298,11 @@ func Keygen() (err error) { } fmt.Println(string(out)) + } else if len(os.Args) == 3 && os.Args[2] == "usage" { + fmt.Println(`proxy keygen //generate proxy.crt and proxy.key +proxy keygen ca //generate ca.crt and ca.key +proxy keygen ca client0 30 //generate client0.crt client0.key and use ca.crt sign it with 30 days + `) } else if len(os.Args) == 2 { cmd := exec.Command("sh", "-c", "openssl genrsa -out proxy.key 2048") out, err = cmd.CombinedOutput() From 4d4fb64b59d6c5b9882446171f96773cd1e9af77 Mon Sep 17 00:00:00 2001 From: snail007 Date: Mon, 12 Mar 2018 19:27:37 +0800 Subject: [PATCH 17/76] Update mux_bridge.go --- services/mux_bridge.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/mux_bridge.go b/services/mux_bridge.go index 37e52c7..6b46ac3 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -120,14 +120,14 @@ func (s *MuxBridge) handler(inConn net.Conn) { } groupKey := keyInfo[0] index := keyInfo[1] + s.l.Lock() + defer s.l.Unlock() if !s.clientControlConns.Has(groupKey) { item := utils.NewConcurrentMap() s.clientControlConns.Set(groupKey, &item) } _group, _ := s.clientControlConns.Get(groupKey) group := _group.(*utils.ConcurrentMap) - s.l.Lock() - defer s.l.Unlock() group.Set(index, session) // s.clientControlConns.Set(key, session) go func() { From ad47104fc72c8a7f1f0cd03f05fe6fb1cdcc6241 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 13 Mar 2018 10:04:18 +0800 Subject: [PATCH 18/76] v4.5 --- services/mux_bridge.go | 3 +++ services/mux_client.go | 2 +- services/mux_server.go | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/services/mux_bridge.go b/services/mux_bridge.go index 6b46ac3..6c33246 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -138,6 +138,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { if sess, ok := group.Get(index); ok && sess.(*smux.Session).IsClosed() { group.Remove(index) } + if group.IsEmpty() { + s.clientControlConns.Remove(groupKey) + } break } time.Sleep(time.Second * 5) diff --git a/services/mux_client.go b/services/mux_client.go index 56b6bbf..42cd43d 100644 --- a/services/mux_client.go +++ b/services/mux_client.go @@ -47,7 +47,7 @@ func (s *MuxClient) Start(args interface{}) (err error) { s.cfg = args.(MuxClientArgs) s.CheckArgs() s.InitService() - log.Printf("%s client on %s", *s.cfg.ParentType, *s.cfg.Parent) + log.Printf("client started") count := 1 if *s.cfg.SessionCount > 0 { count = *s.cfg.SessionCount diff --git a/services/mux_server.go b/services/mux_server.go index 69e47b1..413b216 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -205,7 +205,7 @@ func (s *MuxServer) Start(args interface{}) (err error) { if err != nil { return } - log.Printf("%s server on %s", *s.cfg.ParentType, (*s.sc.Listener).Addr()) + log.Printf("server on %s", (*s.sc.Listener).Addr()) } return } From f348298acd64b30f170771ff3dfcd3f097a6acdc Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 13 Mar 2018 10:29:26 +0800 Subject: [PATCH 19/76] v4.5 --- CHANGELOG | 8 +++++--- README.md | 3 ++- README_ZH.md | 6 ++++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f510159..020bbda 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,10 @@ proxy更新日志 v4.5 -1.优化了mux内网穿透连接管理逻辑,增强了稳定性. -2.mux内网穿透增加了tcp和kcp协议支持,之前是tls,现在支持三种协议tcp,tls,kcp. - +1.优化了mux内网穿透连接管理逻辑,增强了稳定性. +2.mux内网穿透增加了tcp和kcp协议支持,之前是tls,现在支持三种协议tcp,tls,kcp. +3.keygen参数增加了用法: proxy keygen usage. +4.http(s)/socks5代理,tls增加了自签名证书支持. +5.建议升级. v4.4 1.增加了协议转换sps功能,代理协议转换使用的是sps子命令(socks+https的缩写), sps本身不提供代理功能,只是接受代理请求"转换并转发"给已经存在的http(s)代理 diff --git a/README.md b/README.md index 3eb6ab6..a949255 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Proxy is a high performance HTTP, HTTPS, HTTPS, websocket, TCP, UDP, Socks5 prox - ...   -This page is the v4.4 manual, and the other version of the manual can be checked by the following link. +This page is the v4.5 manual, and the other version of the manual can be checked by the following link. +- [v4.4 manual](https://github.com/snail007/goproxy/tree/v4.4) - [v4.3 manual](https://github.com/snail007/goproxy/tree/v4.3) - [v4.2 manual](https://github.com/snail007/goproxy/tree/v4.2) - [v4.0-4.1 manual](https://github.com/snail007/goproxy/tree/v4.1) diff --git a/README_ZH.md b/README_ZH.md index 19841fb..08f442f 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -35,7 +35,8 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - ... -本页是v4.4手册,其他版本手册请点击下面链接查看. +本页是v4.5手册,其他版本手册请点击下面链接查看. +- [v4.4手册](https://github.com/snail007/goproxy/tree/v4.4) - [v4.3手册](https://github.com/snail007/goproxy/tree/v4.3) - [v4.2手册](https://github.com/snail007/goproxy/tree/v4.2) - [v4.0-v4.1手册](https://github.com/snail007/goproxy/tree/v4.1) @@ -187,7 +188,8 @@ http,tcp,udp代理过程会和上级通讯,为了安全我们采用加密通讯, 在linux上并安装了openssl命令,可以直接通过下面的命令生成证书和key文件. `./proxy keygen` 默认会在当前程序目录下面生成证书文件proxy.crt和key文件proxy.key。 - +更多用法:`proxy keygen usage`。 + ### **后台运行** 默认执行proxy之后,如果要保持proxy运行,不能关闭命令行. 如果想在后台运行proxy,命令行可以关闭,只需要在命令最后加上--daemon参数即可. From f756d62b19941e5c1fadc5cdabf289b56008563b Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 13 Mar 2018 17:14:25 +0800 Subject: [PATCH 20/76] Signed-off-by: arraykeys@gmail.com --- services/mux_server.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/services/mux_server.go b/services/mux_server.go index 413b216..2bba64a 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -266,14 +266,6 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { } s.sessions.Set(index, session) log.Printf("session[%s] created", index) - } else { - session = _session.(*smux.Session) - } - conn, err = session.OpenStream() - if err != nil { - session.Close() - s.sessions.Remove(index) - } else { go func() { for { if session.IsClosed() { @@ -283,8 +275,14 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { time.Sleep(time.Second * 5) } }() + } else { + session = _session.(*smux.Session) + } + conn, err = session.OpenStream() + if err != nil { + session.Close() + s.sessions.Remove(index) } - return } func (s *MuxServer) getParentConn() (conn net.Conn, err error) { From 0d85c7dd7dfbb7c6290b44036d9a150e13a4302a Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 13 Mar 2018 17:31:51 +0800 Subject: [PATCH 21/76] bridge add timeout Signed-off-by: arraykeys@gmail.com --- services/mux_bridge.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/mux_bridge.go b/services/mux_bridge.go index 6c33246..605a187 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -180,7 +180,9 @@ func (s *MuxBridge) callback(inConn net.Conn, serverID, key string) { index := keys[i] log.Printf("select client : %s-%s", key, index) session, _ := group.Get(index) + session.(*smux.Session).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) stream, err := session.(*smux.Session).OpenStream() + session.(*smux.Session).SetDeadline(time.Time{}) if err != nil { log.Printf("%s client session open stream %s fail, err: %s, retrying...", key, serverID, err) time.Sleep(time.Second * 3) From bab4325414737cafd617f5d85a251bf6833312e9 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 13 Mar 2018 18:36:34 +0800 Subject: [PATCH 22/76] server add safe close user conn Signed-off-by: arraykeys@gmail.com --- README.md | 5 +++-- README_ZH.md | 5 +++-- install_auto.sh | 2 +- main.go | 2 +- release.sh | 2 +- services/mux_server.go | 41 ++++++++++++++++++++++++++++++++--------- 6 files changed, 41 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a949255..349a1e8 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ Proxy is a high performance HTTP, HTTPS, HTTPS, websocket, TCP, UDP, Socks5 prox - ...   -This page is the v4.5 manual, and the other version of the manual can be checked by the following link. +This page is the v4.6 manual, and the other version of the manual can be checked by the following link. +- [v4.5 manual](https://github.com/snail007/goproxy/tree/v4.5) - [v4.4 manual](https://github.com/snail007/goproxy/tree/v4.4) - [v4.3 manual](https://github.com/snail007/goproxy/tree/v4.3) - [v4.2 manual](https://github.com/snail007/goproxy/tree/v4.2) @@ -149,7 +150,7 @@ If the installation fails or your VPS is not a linux64 system, please follow the Download address: https://github.com/snail007/goproxy/releases ```shell cd /root/proxy/ -wget https://github.com/snail007/goproxy/releases/download/v4.5/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.6/proxy-linux-amd64.tar.gz ``` #### **2.Download the automatic installation script** ```shell diff --git a/README_ZH.md b/README_ZH.md index 08f442f..b0b46b4 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -35,7 +35,8 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - ... -本页是v4.5手册,其他版本手册请点击下面链接查看. +本页是v4.6手册,其他版本手册请点击下面链接查看. +- [v4.5手册](https://github.com/snail007/goproxy/tree/v4.5) - [v4.4手册](https://github.com/snail007/goproxy/tree/v4.4) - [v4.3手册](https://github.com/snail007/goproxy/tree/v4.3) - [v4.2手册](https://github.com/snail007/goproxy/tree/v4.2) @@ -147,7 +148,7 @@ curl -L https://raw.githubusercontent.com/snail007/goproxy/master/install_auto.s 下载地址:https://github.com/snail007/goproxy/releases ```shell cd /root/proxy/ -wget https://github.com/snail007/goproxy/releases/download/v4.5/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.6/proxy-linux-amd64.tar.gz ``` #### **2.下载自动安装脚本** ```shell diff --git a/install_auto.sh b/install_auto.sh index e25d8d7..69df125 100755 --- a/install_auto.sh +++ b/install_auto.sh @@ -5,7 +5,7 @@ if [ -e /tmp/proxy ]; then fi mkdir /tmp/proxy cd /tmp/proxy -wget https://github.com/snail007/goproxy/releases/download/v4.5/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.6/proxy-linux-amd64.tar.gz # #install proxy tar zxvf proxy-linux-amd64.tar.gz diff --git a/main.go b/main.go index 1547971..139d970 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,7 @@ import ( "syscall" ) -const APP_VERSION = "4.5" +const APP_VERSION = "4.6" func main() { err := initConfig() diff --git a/release.sh b/release.sh index 5009a87..965d039 100755 --- a/release.sh +++ b/release.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER="4.5" +VER="4.6" RELEASE="release-${VER}" rm -rf .cert mkdir .cert diff --git a/services/mux_server.go b/services/mux_server.go index 2bba64a..bcffc50 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -11,6 +11,7 @@ import ( "snail007/proxy/utils" "strconv" "strings" + "sync" "time" "github.com/golang/snappy" @@ -18,11 +19,13 @@ import ( ) type MuxServer struct { - cfg MuxServerArgs - udpChn chan MuxUDPItem - sc utils.ServerChannel - sessions utils.ConcurrentMap - lockChn chan bool + cfg MuxServerArgs + udpChn chan MuxUDPItem + sc utils.ServerChannel + sessions utils.ConcurrentMap + userConns utils.ConcurrentMap + lockChn chan bool + closeLock *sync.Mutex } type MuxServerManager struct { @@ -116,10 +119,12 @@ func (s *MuxServerManager) InitService() { func NewMuxServer() Service { return &MuxServer{ - cfg: MuxServerArgs{}, - udpChn: make(chan MuxUDPItem, 50000), - lockChn: make(chan bool, 1), - sessions: utils.NewConcurrentMap(), + cfg: MuxServerArgs{}, + udpChn: make(chan MuxUDPItem, 50000), + lockChn: make(chan bool, 1), + sessions: utils.NewConcurrentMap(), + userConns: utils.NewConcurrentMap(), + closeLock: &sync.Mutex{}, } } @@ -164,6 +169,8 @@ func (s *MuxServer) Start(args interface{}) (err error) { log.Printf("connection handler crashed with err : %s \nstack: %s", err, string(debug.Stack())) } }() + inConnRemoteAddr := inConn.RemoteAddr().String() + s.userConns.Set(inConnRemoteAddr, &inConn) var outConn net.Conn var ID string for { @@ -177,6 +184,9 @@ func (s *MuxServer) Start(args interface{}) (err error) { break } } + outConnRemoteAddr := outConn.RemoteAddr().String() + s.userConns.Set(outConnRemoteAddr, &outConn) + log.Printf("%s stream %s created", *s.cfg.Key, ID) if *s.cfg.IsCompress { die1 := make(chan bool, 1) @@ -195,9 +205,13 @@ func (s *MuxServer) Start(args interface{}) (err error) { } outConn.Close() inConn.Close() + s.userConns.Remove(inConnRemoteAddr) + s.userConns.Remove(outConnRemoteAddr) log.Printf("%s stream %s released", *s.cfg.Key, ID) } else { utils.IoBind(inConn, outConn, func(err interface{}) { + s.userConns.Remove(inConnRemoteAddr) + s.userConns.Remove(outConnRemoteAddr) log.Printf("%s stream %s released", *s.cfg.Key, ID) }) } @@ -270,6 +284,15 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { for { if session.IsClosed() { s.sessions.Remove(index) + s.closeLock.Lock() + if len(s.userConns) > 0 { + for _, k := range s.userConns.Keys() { + c, _ := s.userConns.Get(k) + (*(c.(*net.Conn))).Close() + s.userConns.Remove(k) + } + } + s.closeLock.Unlock() break } time.Sleep(time.Second * 5) From 7f983152b7ff4a38a62eee7a5b4408a4740f8ef4 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 14 Mar 2018 09:37:57 +0800 Subject: [PATCH 23/76] Signed-off-by: arraykeys@gmail.com --- services/mux_server.go | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/services/mux_server.go b/services/mux_server.go index bcffc50..f8e370a 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -11,7 +11,6 @@ import ( "snail007/proxy/utils" "strconv" "strings" - "sync" "time" "github.com/golang/snappy" @@ -19,13 +18,11 @@ import ( ) type MuxServer struct { - cfg MuxServerArgs - udpChn chan MuxUDPItem - sc utils.ServerChannel - sessions utils.ConcurrentMap - userConns utils.ConcurrentMap - lockChn chan bool - closeLock *sync.Mutex + cfg MuxServerArgs + udpChn chan MuxUDPItem + sc utils.ServerChannel + sessions utils.ConcurrentMap + lockChn chan bool } type MuxServerManager struct { @@ -119,12 +116,10 @@ func (s *MuxServerManager) InitService() { func NewMuxServer() Service { return &MuxServer{ - cfg: MuxServerArgs{}, - udpChn: make(chan MuxUDPItem, 50000), - lockChn: make(chan bool, 1), - sessions: utils.NewConcurrentMap(), - userConns: utils.NewConcurrentMap(), - closeLock: &sync.Mutex{}, + cfg: MuxServerArgs{}, + udpChn: make(chan MuxUDPItem, 50000), + lockChn: make(chan bool, 1), + sessions: utils.NewConcurrentMap(), } } @@ -170,7 +165,6 @@ func (s *MuxServer) Start(args interface{}) (err error) { } }() inConnRemoteAddr := inConn.RemoteAddr().String() - s.userConns.Set(inConnRemoteAddr, &inConn) var outConn net.Conn var ID string for { @@ -185,7 +179,6 @@ func (s *MuxServer) Start(args interface{}) (err error) { } } outConnRemoteAddr := outConn.RemoteAddr().String() - s.userConns.Set(outConnRemoteAddr, &outConn) log.Printf("%s stream %s created", *s.cfg.Key, ID) if *s.cfg.IsCompress { @@ -205,13 +198,9 @@ func (s *MuxServer) Start(args interface{}) (err error) { } outConn.Close() inConn.Close() - s.userConns.Remove(inConnRemoteAddr) - s.userConns.Remove(outConnRemoteAddr) log.Printf("%s stream %s released", *s.cfg.Key, ID) } else { utils.IoBind(inConn, outConn, func(err interface{}) { - s.userConns.Remove(inConnRemoteAddr) - s.userConns.Remove(outConnRemoteAddr) log.Printf("%s stream %s released", *s.cfg.Key, ID) }) } @@ -284,15 +273,6 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { for { if session.IsClosed() { s.sessions.Remove(index) - s.closeLock.Lock() - if len(s.userConns) > 0 { - for _, k := range s.userConns.Keys() { - c, _ := s.userConns.Get(k) - (*(c.(*net.Conn))).Close() - s.userConns.Remove(k) - } - } - s.closeLock.Unlock() break } time.Sleep(time.Second * 5) From d4c0775b4ac273af4a45a4e4621617fca874fb66 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 16 Mar 2018 16:16:11 +0800 Subject: [PATCH 24/76] Signed-off-by: arraykeys@gmail.com --- services/mux_bridge.go | 3 +++ services/mux_server.go | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/mux_bridge.go b/services/mux_bridge.go index 605a187..151e849 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -128,6 +128,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { } _group, _ := s.clientControlConns.Get(groupKey) group := _group.(*utils.ConcurrentMap) + if v, ok := group.Get(index); ok { + v.(*smux.Session).Close() + } group.Set(index, session) // s.clientControlConns.Set(key, session) go func() { diff --git a/services/mux_server.go b/services/mux_server.go index f8e370a..2bba64a 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -164,7 +164,6 @@ func (s *MuxServer) Start(args interface{}) (err error) { log.Printf("connection handler crashed with err : %s \nstack: %s", err, string(debug.Stack())) } }() - inConnRemoteAddr := inConn.RemoteAddr().String() var outConn net.Conn var ID string for { @@ -178,8 +177,6 @@ func (s *MuxServer) Start(args interface{}) (err error) { break } } - outConnRemoteAddr := outConn.RemoteAddr().String() - log.Printf("%s stream %s created", *s.cfg.Key, ID) if *s.cfg.IsCompress { die1 := make(chan bool, 1) From 40bce3e736e7754bac1cd01d2f6fd52a0fc99df6 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 21 Mar 2018 16:57:46 +0800 Subject: [PATCH 25/76] optimize http basic auth Signed-off-by: arraykeys@gmail.com --- utils/structs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/structs.go b/utils/structs.go index 9528e4f..dc8dcfd 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -411,6 +411,7 @@ func (req *HTTPRequest) BasicAuth() (err error) { // authorization = req.getHeader("Authorization") // code = "401" // } + authorization = strings.Trim(authorization, " \r\n\t") if authorization == "" { fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized", code) CloseConn(req.conn) From 59c914887556104243208a94fcbb5d050b4d0f5a Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 21 Mar 2018 18:17:20 +0800 Subject: [PATCH 26/76] optimize timeout of http(s)\socks\nat Signed-off-by: arraykeys@gmail.com --- services/http.go | 8 +++++--- services/mux_bridge.go | 4 ++++ services/mux_client.go | 11 +++++++++++ services/mux_server.go | 8 ++++++++ services/socks.go | 32 +++++++++++++++++++++++++++++++- 5 files changed, 59 insertions(+), 4 deletions(-) diff --git a/services/http.go b/services/http.go index 9587303..37993ab 100644 --- a/services/http.go +++ b/services/http.go @@ -94,7 +94,9 @@ func (s *HTTP) InitService() { for { conn, err := utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout*2) if err == nil { + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = conn.Write([]byte{0}) + conn.SetDeadline(time.Time{}) } if err != nil { if s.sshClient != nil { @@ -215,7 +217,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) + // log.Printf("%v", s.outPool) _outConn, err = s.outPool.Pool.Get() if err == nil { outConn = _outConn.(net.Conn) @@ -237,16 +239,16 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut utils.CloseConn(inConn) return } - outAddr := outConn.RemoteAddr().String() //outLocalAddr := outConn.LocalAddr().String() - if req.IsHTTPS() && (!useProxy || *s.cfg.ParentType == "ssh") { //https无上级或者上级非代理,proxy需要响应connect请求,并直连目标 err = req.HTTPSReply() } else { //https或者http,上级是代理,proxy需要转发 + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(req.HeadBuf) + outConn.SetDeadline(time.Time{}) if err != nil { log.Printf("write to %s , err:%s", *s.cfg.Parent, err) utils.CloseConn(inConn) diff --git a/services/mux_bridge.go b/services/mux_bridge.go index 151e849..ec57b1f 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -75,7 +75,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { var err error var connType uint8 var key string + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) err = utils.ReadPacket(reader, &connType, &key) + inConn.SetDeadline(time.Time{}) if err != nil { log.Printf("read error,ERR:%s", err) return @@ -83,7 +85,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { switch connType { case CONN_SERVER: var serverID string + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) err = utils.ReadPacketData(reader, &serverID) + inConn.SetDeadline(time.Time{}) if err != nil { log.Printf("read error,ERR:%s", err) return diff --git a/services/mux_client.go b/services/mux_client.go index 42cd43d..398744a 100644 --- a/services/mux_client.go +++ b/services/mux_client.go @@ -68,7 +68,9 @@ func (s *MuxClient) Start(args interface{}) (err error) { time.Sleep(time.Second * 3) continue } + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = conn.Write(utils.BuildPacket(CONN_CLIENT, fmt.Sprintf("%s-%d", *s.cfg.Key, i))) + conn.SetDeadline(time.Time{}) if err != nil { conn.Close() log.Printf("connection err: %s, retrying...", err) @@ -98,7 +100,9 @@ func (s *MuxClient) Start(args interface{}) (err error) { } }() var ID, clientLocalAddr, serverID string + stream.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) err = utils.ReadPacketData(stream, &ID, &clientLocalAddr, &serverID) + stream.SetDeadline(time.Time{}) if err != nil { log.Printf("read stream signal err: %s", err) stream.Close() @@ -140,7 +144,9 @@ func (s *MuxClient) getParentConn() (conn net.Conn, err error) { func (s *MuxClient) ServeUDP(inConn *smux.Stream, localAddr, ID string) { for { + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) srcAddr, body, err := utils.ReadUDPPacket(inConn) + inConn.SetDeadline(time.Time{}) if err != nil { log.Printf("udp packet revecived fail, err: %s", err) log.Printf("connection %s released", ID) @@ -169,13 +175,16 @@ func (s *MuxClient) processUDPPacket(inConn *smux.Stream, srcAddr, localAddr str } conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = conn.Write(body) + conn.SetDeadline(time.Time{}) if err != nil { log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err) return } //log.Printf("send udp packet to %s success", dstAddr.String()) buf := make([]byte, 1024) + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) length, _, err := conn.ReadFromUDP(buf) + conn.SetDeadline(time.Time{}) if err != nil { log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err) return @@ -183,7 +192,9 @@ func (s *MuxClient) processUDPPacket(inConn *smux.Stream, srcAddr, localAddr str respBody := buf[0:length] //log.Printf("revecived udp packet from %s , %v", dstAddr.String(), respBody) bs := utils.UDPPacket(srcAddr, respBody) + (*inConn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = (*inConn).Write(bs) + (*inConn).SetDeadline(time.Time{}) if err != nil { log.Printf("send udp response fail ,ERR:%s", err) inConn.Close() diff --git a/services/mux_server.go b/services/mux_server.go index 2bba64a..9e1fc65 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -227,7 +227,9 @@ func (s *MuxServer) GetOutConn() (outConn net.Conn, ID string, err error) { remoteAddr = "udp:" + *s.cfg.Remote } ID = utils.Uniqueid() + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(utils.BuildPacketData(ID, remoteAddr, s.cfg.Mgr.serverID)) + outConn.SetDeadline(time.Time{}) if err != nil { log.Printf("write stream data err: %s ,retrying...", err) utils.CloseConn(&outConn) @@ -253,7 +255,9 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { if err != nil { return } + c.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = c.Write(utils.BuildPacket(CONN_SERVER, *s.cfg.Key, s.cfg.Mgr.serverID)) + c.SetDeadline(time.Time{}) if err != nil { c.Close() return @@ -327,7 +331,9 @@ func (s *MuxServer) UDPConnDeamon() { // outConn.Close() }() for { + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn) + outConn.SetDeadline(time.Time{}) if err != nil { log.Printf("parse revecived udp packet fail, err: %s ,%v", err, body) log.Printf("UDP deamon connection %s exited", ID) @@ -341,7 +347,9 @@ func (s *MuxServer) UDPConnDeamon() { } port, _ := strconv.Atoi(_srcAddr[1]) dstAddr := &net.UDPAddr{IP: net.ParseIP(_srcAddr[0]), Port: port} + s.sc.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = s.sc.UDPListener.WriteToUDP(body, dstAddr) + s.sc.UDPListener.SetDeadline(time.Time{}) if err != nil { log.Printf("udp response to local %s fail,ERR:%s", srcAddrFromConn, err) continue diff --git a/services/socks.go b/services/socks.go index bea8180..9828733 100644 --- a/services/socks.go +++ b/services/socks.go @@ -104,7 +104,9 @@ func (s *Socks) InitService() { for { conn, err := utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout*2) if err == nil { + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = conn.Write([]byte{0}) + conn.SetDeadline(time.Time{}) } if err != nil { if s.sshClient != nil { @@ -216,6 +218,7 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { } conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*5))) _, err = conn.Write(rawB) + conn.SetDeadline(time.Time{}) log.Printf("udp request:%v", len(rawB)) if err != nil { log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err) @@ -225,7 +228,9 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { //log.Printf("send udp packet to %s success", dstAddr.String()) buf := make([]byte, 10*1024) + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) length, _, err := conn.ReadFromUDP(buf) + conn.SetDeadline(time.Time{}) if err != nil { log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err) conn.Close() @@ -250,10 +255,14 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { conn.Close() return } + s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) s.udpSC.UDPListener.WriteToUDP(d, srcAddr) + s.udpSC.UDPListener.SetDeadline(time.Time{}) log.Printf("udp reply:%v", len(d)) } else { + s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) s.udpSC.UDPListener.WriteToUDP(respBody, srcAddr) + s.udpSC.UDPListener.SetDeadline(time.Time{}) log.Printf("udp reply:%v", len(respBody)) } @@ -272,6 +281,7 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { } conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout*3))) _, err = conn.Write(p.Data()) + conn.SetDeadline(time.Time{}) log.Printf("udp send:%v", len(p.Data())) if err != nil { log.Printf("send udp packet to %s fail,ERR:%s", dstAddr.String(), err) @@ -280,7 +290,10 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { } //log.Printf("send udp packet to %s success", dstAddr.String()) buf := make([]byte, 10*1024) + conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) length, _, err := conn.ReadFromUDP(buf) + conn.SetDeadline(time.Time{}) + if err != nil { log.Printf("read udp response from %s fail ,ERR:%s", dstAddr.String(), err) conn.Close() @@ -297,9 +310,13 @@ func (s *Socks) udpCallback(b []byte, localAddr, srcAddr *net.UDPAddr) { conn.Close() return } + s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) s.udpSC.UDPListener.WriteToUDP(d, srcAddr) + s.udpSC.UDPListener.SetDeadline(time.Time{}) } else { + s.udpSC.UDPListener.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) s.udpSC.UDPListener.WriteToUDP(respPacket, srcAddr) + s.udpSC.UDPListener.SetDeadline(time.Time{}) } log.Printf("udp reply:%v", len(respPacket)) } @@ -371,9 +388,15 @@ func (s *Socks) socksConnCallback(inConn net.Conn) { //auth _addr := strings.Split(inConn.RemoteAddr().String(), ":") if s.basicAuth.CheckUserPass(user, pass, _addr[0], "") { + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) inConn.Write([]byte{0x01, 0x00}) + inConn.SetDeadline(time.Time{}) + } else { + inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) inConn.Write([]byte{0x01, 0x01}) + inConn.SetDeadline(time.Time{}) + utils.CloseConn(&inConn) return } @@ -496,25 +519,32 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n } var buf = make([]byte, 1024) //var n int + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(methodBytes) + outConn.SetDeadline(time.Time{}) if err != nil { err = fmt.Errorf("write method fail,%s", err) return } + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Read(buf) + outConn.SetDeadline(time.Time{}) if err != nil { err = fmt.Errorf("read method reply fail,%s", err) return } //resp := buf[:n] //log.Printf("resp:%v", resp) - + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(reqBytes) + outConn.SetDeadline(time.Time{}) if err != nil { err = fmt.Errorf("write req detail fail,%s", err) return } + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Read(buf) + outConn.SetDeadline(time.Time{}) if err != nil { err = fmt.Errorf("read req reply fail,%s", err) return From d81d5ffe06d3c1c6294844d6914f80ddd85f844c Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Mon, 26 Mar 2018 18:42:44 +0800 Subject: [PATCH 27/76] socks5 client server done Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- utils/socks/client.go | 253 +++++++++++++++++++++++++++++++++++++++++ utils/socks/server.go | 225 ++++++++++++++++++++++++++++++++++++ utils/socks/structs.go | 168 +++++++++++++++++++-------- 4 files changed, 599 insertions(+), 49 deletions(-) create mode 100644 utils/socks/client.go create mode 100644 utils/socks/server.go diff --git a/README_ZH.md b/README_ZH.md index b0b46b4..dea5c5e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -599,7 +599,7 @@ server连接到bridge的时候,如果同时有多个client连接到同一个brid ![5.2](/docs/images/5.2.png) 使用本地端口8090,假设上级SOCKS5代理是`22.22.22.22:8080` `./proxy socks -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" ` -我们还可以指定网站域名的黑白名单文件,一行一个域名,匹配规则是最右匹配,比如:baidu.com,匹配的是*.*.baidu.com,黑名单的域名域名直接走上级代理,白名单的域名不走上级代理. +我们还可以指定网站域名的黑白名单文件,一行一个域名,匹配规则是最右匹配,比如:baidu.com,匹配的是*.*.baidu.com,黑名单的域名域名直接走上级代理,白名单的域名不走上级代理;如果域名即在黑名单又在白名单中,那么黑名单起作用. `./proxy socks -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -b blocked.txt -d direct.txt` #### **5.3.SOCKS二级代理(加密)** diff --git a/utils/socks/client.go b/utils/socks/client.go new file mode 100644 index 0000000..7253d21 --- /dev/null +++ b/utils/socks/client.go @@ -0,0 +1,253 @@ +package socks + +import ( + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "strconv" + "time" +) + +var socks5Errors = []string{ + "", + "general failure", + "connection forbidden", + "network unreachable", + "host unreachable", + "connection refused", + "TTL expired", + "command not supported", + "address type not supported", +} + +type Auth struct { + User, Password string +} +type ClientConn struct { + user string + password string + conn *net.Conn + header []byte + timeout time.Duration + addr string + network string + udpAddr string +} + +// SOCKS5 returns a Dialer that makes SOCKSv5 connections to the given address +// with an optional username and password. See RFC 1928 and RFC 1929. +// target must be a canonical address with a host and port. +// network : tcp udp +func NewClientConn(conn *net.Conn, network, target string, timeout time.Duration, auth *Auth, header []byte) *ClientConn { + s := &ClientConn{ + conn: conn, + network: network, + timeout: timeout, + } + if auth != nil { + s.user = auth.User + s.password = auth.Password + } + if header != nil && len(header) > 0 { + s.header = header + } + if network == "udp" && target == "" { + target = "0.0.0.0:1" + } + s.addr = target + return s +} + +// connect takes an existing connection to a socks5 proxy server, +// and commands the server to extend that connection to target, +// which must be a canonical address with a host and port. +func (s *ClientConn) Connect() error { + host, portStr, err := net.SplitHostPort(s.addr) + if err != nil { + return err + } + port, err := strconv.Atoi(portStr) + if err != nil { + return errors.New("proxy: failed to parse port number: " + portStr) + } + if port < 1 || port > 0xffff { + return errors.New("proxy: port number out of range: " + portStr) + } + + if err := s.handshake(host); err != nil { + return err + } + buf := []byte{} + if s.network == "tcp" { + buf = append(buf, VERSION_V5, CMD_CONNECT, 0 /* reserved */) + + } else { + buf = append(buf, VERSION_V5, CMD_ASSOCIATE, 0 /* reserved */) + } + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + buf = append(buf, ATYP_IPV4) + ip = ip4 + } else { + buf = append(buf, ATYP_IPV6) + } + buf = append(buf, ip...) + } else { + if len(host) > 255 { + return errors.New("proxy: destination host name too long: " + host) + } + buf = append(buf, ATYP_DOMAIN) + buf = append(buf, byte(len(host))) + buf = append(buf, host...) + } + buf = append(buf, byte(port>>8), byte(port)) + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := (*s.conn).Write(buf); err != nil { + return errors.New("proxy: failed to write connect request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf[:4]); err != nil { + return errors.New("proxy: failed to read connect reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + failure := "unknown error" + if int(buf[1]) < len(socks5Errors) { + failure = socks5Errors[buf[1]] + } + + if len(failure) > 0 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " failed to connect: " + failure) + } + + bytesToDiscard := 0 + switch buf[3] { + case ATYP_IPV4: + bytesToDiscard = net.IPv4len + case ATYP_IPV6: + bytesToDiscard = net.IPv6len + case ATYP_DOMAIN: + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + _, err := io.ReadFull((*s.conn), buf[:1]) + (*s.conn).SetDeadline(time.Time{}) + if err != nil { + return errors.New("proxy: failed to read domain length from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + bytesToDiscard = int(buf[0]) + default: + return errors.New("proxy: got unknown address type " + strconv.Itoa(int(buf[3])) + " from SOCKS5 proxy at " + s.addr) + } + + if cap(buf) < bytesToDiscard { + buf = make([]byte, bytesToDiscard) + } else { + buf = buf[:bytesToDiscard] + } + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf); err != nil { + return errors.New("proxy: failed to read address from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + var ip net.IP + ip = buf + ipStr := "" + if bytesToDiscard == net.IPv4len || bytesToDiscard == net.IPv6len { + if ipv4 := ip.To4(); ipv4 != nil { + ipStr = ipv4.String() + } else { + ipStr = ip.To16().String() + } + } + //log.Printf("%v", ipStr) + // Also need to discard the port number + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil { + return errors.New("proxy: failed to read port from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + p := binary.BigEndian.Uint16([]byte{buf[0], buf[1]}) + //log.Printf("%v", p) + s.udpAddr = net.JoinHostPort(ipStr, fmt.Sprintf("%d", p)) + //log.Printf("%v", s.udpAddr) + (*s.conn).SetDeadline(time.Time{}) + return nil +} +func (s *ClientConn) SendUDP(data []byte, addr string) (respData []byte, err error) { + + c, err := net.DialTimeout("udp", s.udpAddr, s.timeout) + if err != nil { + return + } + conn := c.(*net.UDPConn) + + p := NewPacketUDP() + p.Build(addr, data) + conn.SetDeadline(time.Now().Add(s.timeout)) + conn.Write(p.Bytes()) + conn.SetDeadline(time.Time{}) + + buf := make([]byte, 1024) + conn.SetDeadline(time.Now().Add(s.timeout)) + n, _, err := conn.ReadFrom(buf) + conn.SetDeadline(time.Time{}) + if err != nil { + return + } + respData = buf[:n] + return +} +func (s *ClientConn) handshake(host string) error { + + // the size here is just an estimate + buf := make([]byte, 0, 6+len(host)) + + buf = append(buf, VERSION_V5) + if len(s.user) > 0 && len(s.user) < 256 && len(s.password) < 256 { + buf = append(buf, 2 /* num auth methods */, Method_NO_AUTH, Method_USER_PASS) + } else { + buf = append(buf, 1 /* num auth methods */, Method_NO_AUTH) + } + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := (*s.conn).Write(buf); err != nil { + return errors.New("proxy: failed to write greeting to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil { + return errors.New("proxy: failed to read greeting from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + + if buf[0] != 5 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " has unexpected version " + strconv.Itoa(int(buf[0]))) + } + if buf[1] == 0xff { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " requires authentication") + } + + // See RFC 1929 + if buf[1] == Method_USER_PASS { + buf = buf[:0] + buf = append(buf, 1 /* password protocol version */) + buf = append(buf, uint8(len(s.user))) + buf = append(buf, s.user...) + buf = append(buf, uint8(len(s.password))) + buf = append(buf, s.password...) + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := (*s.conn).Write(buf); err != nil { + return errors.New("proxy: failed to write authentication request to SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + (*s.conn).SetDeadline(time.Now().Add(s.timeout)) + if _, err := io.ReadFull((*s.conn), buf[:2]); err != nil { + return errors.New("proxy: failed to read authentication reply from SOCKS5 proxy at " + s.addr + ": " + err.Error()) + } + (*s.conn).SetDeadline(time.Time{}) + if buf[1] != 0 { + return errors.New("proxy: SOCKS5 proxy at " + s.addr + " rejected username/password") + } + } + return nil +} diff --git a/utils/socks/server.go b/utils/socks/server.go new file mode 100644 index 0000000..121798a --- /dev/null +++ b/utils/socks/server.go @@ -0,0 +1,225 @@ +package socks + +import ( + "fmt" + "net" + "snail007/proxy/utils" + "strings" + "time" +) + +const ( + Method_NO_AUTH = uint8(0x00) + Method_GSSAPI = uint8(0x01) + Method_USER_PASS = uint8(0x02) + Method_IANA = uint8(0x7F) + Method_RESVERVE = uint8(0x80) + Method_NONE_ACCEPTABLE = uint8(0xFF) + VERSION_V5 = uint8(0x05) + CMD_CONNECT = uint8(0x01) + CMD_BIND = uint8(0x02) + CMD_ASSOCIATE = uint8(0x03) + ATYP_IPV4 = uint8(0x01) + ATYP_DOMAIN = uint8(0x03) + ATYP_IPV6 = uint8(0x04) + REP_SUCCESS = uint8(0x00) + REP_REQ_FAIL = uint8(0x01) + REP_RULE_FORBIDDEN = uint8(0x02) + REP_NETWOR_UNREACHABLE = uint8(0x03) + REP_HOST_UNREACHABLE = uint8(0x04) + REP_CONNECTION_REFUSED = uint8(0x05) + REP_TTL_TIMEOUT = uint8(0x06) + REP_CMD_UNSUPPORTED = uint8(0x07) + REP_ATYP_UNSUPPORTED = uint8(0x08) + REP_UNKNOWN = uint8(0x09) + RSV = uint8(0x00) +) + +var ( + ZERO_IP = []byte{0x00, 0x00, 0x00, 0x00} + ZERO_PORT = []byte{0x00, 0x00} +) + +type ServerConn struct { + target string + user string + password string + conn *net.Conn + timeout time.Duration + auth *utils.BasicAuth + header []byte + ver uint8 + //method + methodsCount uint8 + methods []uint8 + method uint8 + //request + cmd uint8 + reserve uint8 + addressType uint8 + dstAddr string + dstPort string + dstHost string + udpAddress string +} + +func NewServerConn(conn *net.Conn, timeout time.Duration, auth *utils.BasicAuth, udpAddress string, header []byte) *ServerConn { + if udpAddress == "" { + udpAddress = "0.0.0.0:16666" + } + s := &ServerConn{ + conn: conn, + timeout: timeout, + auth: auth, + header: header, + ver: VERSION_V5, + udpAddress: udpAddress, + } + return s + +} +func (s *ServerConn) Close() { + utils.CloseConn(s.conn) +} +func (s *ServerConn) AuthData() Auth { + return Auth{s.user, s.password} +} +func (s *ServerConn) Method() uint8 { + return s.method +} +func (s *ServerConn) Target() string { + return s.target +} +func (s *ServerConn) Handshake() (err error) { + remoteAddr := (*s.conn).RemoteAddr() + //协商开始 + //method select request + var methodReq MethodsRequest + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + + methodReq, e := NewMethodsRequest((*s.conn), s.header) + (*s.conn).SetReadDeadline(time.Time{}) + if e != nil { + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + methodReq.Reply(Method_NONE_ACCEPTABLE) + (*s.conn).SetReadDeadline(time.Time{}) + err = fmt.Errorf("new methods request fail,ERR: %s", e) + return + } + if s.auth == nil { + if !methodReq.Select(Method_NO_AUTH) { + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + methodReq.Reply(Method_NONE_ACCEPTABLE) + (*s.conn).SetReadDeadline(time.Time{}) + err = fmt.Errorf("none method found : Method_NO_AUTH") + return + } + s.method = Method_NO_AUTH + //method select reply + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + err = methodReq.Reply(Method_NO_AUTH) + (*s.conn).SetReadDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("reply answer data fail,ERR: %s", err) + return + } + // err = fmt.Errorf("% x", methodReq.Bytes()) + } else { + //auth + if !methodReq.Select(Method_USER_PASS) { + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + methodReq.Reply(Method_NONE_ACCEPTABLE) + (*s.conn).SetReadDeadline(time.Time{}) + err = fmt.Errorf("none method found : Method_USER_PASS") + return + } + s.method = Method_USER_PASS + //method reply need auth + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + err = methodReq.Reply(Method_USER_PASS) + (*s.conn).SetReadDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("reply answer data fail,ERR: %s", err) + return + } + //read auth + buf := make([]byte, 500) + var n int + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + n, err = (*s.conn).Read(buf) + (*s.conn).SetReadDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("read auth info fail,ERR: %s", err) + return + } + r := buf[:n] + s.user = string(r[2 : r[1]+2]) + s.password = string(r[2+r[1]+1:]) + //err = fmt.Errorf("user:%s,pass:%s", user, pass) + //auth + _addr := strings.Split(remoteAddr.String(), ":") + if s.auth.CheckUserPass(s.user, s.password, _addr[0], "") { + (*s.conn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(s.timeout))) + _, err = (*s.conn).Write([]byte{0x01, 0x00}) + (*s.conn).SetDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("answer auth success to %s fail,ERR: %s", remoteAddr, err) + return + } + } else { + (*s.conn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(s.timeout))) + _, err = (*s.conn).Write([]byte{0x01, 0x01}) + (*s.conn).SetDeadline(time.Time{}) + if err != nil { + err = fmt.Errorf("answer auth fail to %s fail,ERR: %s", remoteAddr, err) + return + } + err = fmt.Errorf("auth fail from %s", remoteAddr) + return + } + } + //request detail + (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + request, e := NewRequest(*s.conn) + (*s.conn).SetReadDeadline(time.Time{}) + if e != nil { + err = fmt.Errorf("read request data fail,ERR: %s", e) + return + } + //协商结束 + + switch request.CMD() { + case CMD_BIND: + err = request.TCPReply(REP_UNKNOWN) + if err != nil { + err = fmt.Errorf("TCPReply REP_UNKNOWN to %s fail,ERR: %s", remoteAddr, err) + return + } + err = fmt.Errorf("cmd bind not supported, form: %s", remoteAddr) + return + case CMD_CONNECT: + err = request.TCPReply(REP_SUCCESS) + if err != nil { + err = fmt.Errorf("TCPReply REP_SUCCESS to %s fail,ERR: %s", remoteAddr, err) + return + } + case CMD_ASSOCIATE: + err = request.UDPReply(REP_SUCCESS, s.udpAddress) + if err != nil { + err = fmt.Errorf("UDPReply REP_SUCCESS to %s fail,ERR: %s", remoteAddr, err) + return + } + } + + //fill socks info + s.target = request.Addr() + s.methodsCount = methodReq.MethodsCount() + s.methods = methodReq.Methods() + s.cmd = request.CMD() + s.reserve = request.reserve + s.addressType = request.addressType + s.dstAddr = request.dstAddr + s.dstHost = request.dstHost + s.dstPort = request.dstPort + return +} diff --git a/utils/socks/structs.go b/utils/socks/structs.go index ef5b51b..a5ec922 100644 --- a/utils/socks/structs.go +++ b/utils/socks/structs.go @@ -3,44 +3,13 @@ package socks import ( "bytes" "encoding/binary" + "errors" "fmt" "io" "net" "strconv" ) -const ( - Method_NO_AUTH = uint8(0x00) - Method_GSSAPI = uint8(0x01) - Method_USER_PASS = uint8(0x02) - Method_IANA = uint8(0x7F) - Method_RESVERVE = uint8(0x80) - Method_NONE_ACCEPTABLE = uint8(0xFF) - VERSION_V5 = uint8(0x05) - CMD_CONNECT = uint8(0x01) - CMD_BIND = uint8(0x02) - CMD_ASSOCIATE = uint8(0x03) - ATYP_IPV4 = uint8(0x01) - ATYP_DOMAIN = uint8(0x03) - ATYP_IPV6 = uint8(0x04) - REP_SUCCESS = uint8(0x00) - REP_REQ_FAIL = uint8(0x01) - REP_RULE_FORBIDDEN = uint8(0x02) - REP_NETWOR_UNREACHABLE = uint8(0x03) - REP_HOST_UNREACHABLE = uint8(0x04) - REP_CONNECTION_REFUSED = uint8(0x05) - REP_TTL_TIMEOUT = uint8(0x06) - REP_CMD_UNSUPPORTED = uint8(0x07) - REP_ATYP_UNSUPPORTED = uint8(0x08) - REP_UNKNOWN = uint8(0x09) - RSV = uint8(0x00) -) - -var ( - ZERO_IP = []byte{0x00, 0x00, 0x00, 0x00} - ZERO_PORT = []byte{0x00, 0x00} -) - type Request struct { ver uint8 cmd uint8 @@ -57,7 +26,7 @@ func NewRequest(rw io.ReadWriter, header ...[]byte) (req Request, err interface{ var b = make([]byte, 1024) var n int req = Request{rw: rw} - if len(header) == 1 { + if header != nil && len(header) == 1 && len(header[0]) > 1 { b = header[0] n = len(header[0]) } else { @@ -71,7 +40,6 @@ func NewRequest(rw io.ReadWriter, header ...[]byte) (req Request, err interface{ req.cmd = uint8(b[1]) req.reserve = uint8(b[2]) req.addressType = uint8(b[3]) - if b[0] != 0x5 { err = fmt.Errorf("sosck version supported") req.TCPReply(REP_REQ_FAIL) @@ -129,7 +97,7 @@ func (s *Request) NewReply(rep uint8, addr string) []byte { ipv6[4], ipv6[5], ipv6[6], ipv6[7], ipv6[8], ipv6[9], ipv6[10], ipv6[11], ) - if ipv6 != nil && "0000000000255255" != zeroiIPv6 { + if ipb == nil && ipv6 != nil && "0000000000255255" != zeroiIPv6 { atyp = ATYP_IPV6 ipb = ip.To16() } @@ -165,7 +133,7 @@ func NewMethodsRequest(r io.ReadWriter, header ...[]byte) (s MethodsRequest, err s.rw = &r var buf = make([]byte, 300) var n int - if len(header) == 1 { + if header != nil && len(header) == 1 && len(header[0]) > 1 { buf = header[0] n = len(header[0]) } else { @@ -182,7 +150,6 @@ func NewMethodsRequest(r io.ReadWriter, header ...[]byte) (s MethodsRequest, err err = fmt.Errorf("socks methods data length error") return } - s.ver = buf[0] s.methodsCount = buf[1] s.methods = buf[2:n] @@ -195,6 +162,9 @@ func (s *MethodsRequest) Version() uint8 { func (s *MethodsRequest) MethodsCount() uint8 { return s.methodsCount } +func (s *MethodsRequest) Methods() []uint8 { + return s.methods +} func (s *MethodsRequest) Select(method uint8) bool { for _, m := range s.methods { if m == method { @@ -211,17 +181,6 @@ func (s *MethodsRequest) Bytes() []byte { return s.bytes } -type UDPPacket struct { - rsv uint16 - frag uint8 - atype uint8 - dstHost string - dstPort string - data []byte - header []byte - bytes []byte -} - func ParseUDPPacket(b []byte) (p UDPPacket, err error) { p = UDPPacket{} p.frag = uint8(b[2]) @@ -249,6 +208,18 @@ func ParseUDPPacket(b []byte) (p UDPPacket, err error) { p.header = b[:portIndex+2] return } + +type UDPPacket struct { + rsv uint16 + frag uint8 + atype uint8 + dstHost string + dstPort string + data []byte + header []byte + bytes []byte +} + func (s *UDPPacket) Header() []byte { return s.header } @@ -268,3 +239,104 @@ func (s *UDPPacket) Port() string { func (s *UDPPacket) Data() []byte { return s.data } + +type PacketUDP struct { + rsv uint16 + frag uint8 + atype uint8 + dstHost string + dstPort string + data []byte +} + +func NewPacketUDP() (p PacketUDP) { + return PacketUDP{} +} +func (p *PacketUDP) Build(destAddr string, data []byte) (err error) { + host, port, err := net.SplitHostPort(destAddr) + if err != nil { + return + } + p.rsv = 0 + p.frag = 0 + p.dstHost = host + p.dstPort = port + p.atype = ATYP_IPV4 + if ip := net.ParseIP(host); ip != nil { + if ip4 := ip.To4(); ip4 != nil { + p.atype = ATYP_IPV4 + ip = ip4 + } else { + p.atype = ATYP_IPV6 + } + } else { + if len(host) > 255 { + err = errors.New("proxy: destination host name too long: " + host) + return + } + p.atype = ATYP_DOMAIN + } + p.data = data + + return +} +func (p *PacketUDP) Parse(b []byte) (err error) { + p.frag = uint8(b[2]) + if p.frag != 0 { + err = fmt.Errorf("FRAG only support for 0 , %v ,%v", p.frag, b[:4]) + return + } + portIndex := 0 + p.atype = b[3] + switch p.atype { + case ATYP_IPV4: //IP V4 + p.dstHost = net.IPv4(b[4], b[5], b[6], b[7]).String() + portIndex = 8 + case ATYP_DOMAIN: //域名 + domainLen := uint8(b[4]) + p.dstHost = string(b[5 : 5+domainLen]) //b[4]表示域名的长度 + portIndex = int(5 + domainLen) + case ATYP_IPV6: //IP V6 + p.dstHost = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String() + portIndex = 20 + } + p.dstPort = strconv.Itoa(int(b[portIndex])<<8 | int(b[portIndex+1])) + p.data = b[portIndex+2:] + return +} +func (p *PacketUDP) Header() []byte { + header := new(bytes.Buffer) + header.Write([]byte{0x00, 0x00, p.frag, p.atype}) + if p.atype == ATYP_IPV4 { + ip := net.ParseIP(p.dstHost) + header.Write(ip.To4()) + } else if p.atype == ATYP_IPV6 { + ip := net.ParseIP(p.dstHost) + header.Write(ip.To16()) + } else if p.atype == ATYP_DOMAIN { + hBytes := []byte(p.dstHost) + header.WriteByte(byte(len(hBytes))) + header.Write(hBytes) + } + port, _ := strconv.ParseUint(p.dstPort, 10, 64) + portBytes := new(bytes.Buffer) + binary.Write(portBytes, binary.BigEndian, port) + header.Write(portBytes.Bytes()[portBytes.Len()-2:]) + return header.Bytes() +} +func (p *PacketUDP) Bytes() []byte { + packBytes := new(bytes.Buffer) + packBytes.Write(p.Header()) + packBytes.Write(p.data) + return packBytes.Bytes() +} +func (p *PacketUDP) Host() string { + return p.dstHost +} + +func (p *PacketUDP) Port() string { + return p.dstPort +} +func (p *PacketUDP) Data() []byte { + return p.data +} From 4a34566f087c6ea845ff3c4c33bbf7e7935cb318 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:14:06 +0800 Subject: [PATCH 28/76] Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 6 ++ README_ZH.md | 45 +++++++++- config.go | 9 +- services/args.go | 8 ++ services/sps.go | 204 ++++++++++++++++++++++++------------------ utils/socks/client.go | 2 +- utils/socks/server.go | 19 ++-- utils/structs.go | 23 ++--- 8 files changed, 206 insertions(+), 110 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 020bbda..38e7efa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,10 @@ proxy更新日志 +v4.6 +1.sps,http(s),socks5,内网穿透都做了大量的超时优化处理,更加稳定. +2.sps增加了强大的树形级联认证支持,可以轻松构建你的认证代理网络. +3.手册增加了6.6对sps认证功能的介绍. + + v4.5 1.优化了mux内网穿透连接管理逻辑,增强了稳定性. 2.mux内网穿透增加了tcp和kcp协议支持,之前是tls,现在支持三种协议tcp,tls,kcp. diff --git a/README_ZH.md b/README_ZH.md index dea5c5e..d08694d 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -127,7 +127,8 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - [6.3 SOCKS5转HTTP(S)+SOCKS5](#63-socks5转httpssocks5) - [6.4 链式连接](#64-链式连接) - [6.5 监听多个端口](#65-监听多个端口) - - [6.6 查看帮助](#66-查看帮助) + - [6.6 认证功能](#66-认证功能) + - [6.7 查看帮助](#67-查看帮助) - [7. KCP配置](#7kcp配置) - [7.1 配置介绍](#71-配置介绍) - [7.2 详细配置](#72-详细配置) @@ -736,7 +737,47 @@ vps02:3.3.3.3 一般情况下监听一个端口就可以,不过如果作为反向代理需要同时监听80和443两个端口,那么-p参数是支持的, 格式是:`-p 0.0.0.0:80,0.0.0.0:443`,多个绑定用逗号分隔即可。 -#### **6.6 查看帮助** +#### **6.6 认证功能** +sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: +1:用户发送认证信息`user-auth`。 +2:设置的本地认证信息`local-auth`。 +3:设置的连接上级使用的认证信息`parent-auth`。 +4:最终发送给上级的认证信息`auth-info-to-parent`。 +他们的情况关系如下: +| user-auth | local-auth | parent-auth | auth-info-to-parent +| 有/没有 | 有 | 有 | 来自parent-auth +| 有/没有 | 没有 | 有 | 来自parent-auth +| 有/没有 | 有 | 没有 | 无 +| 没有 | 没有 | 没有 | 无 +| 有 | 没有 | 没有 | 来自user-auth + +对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` +多个用户,重复-a参数即可. +也可以放在文件中,格式是一行一个"用户名:密码",然后用-F指定. +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -F auth-file.txt` + +如果上级有认证,下级可以通过-A参数设置认证信息,比如: +上级:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` +下级:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -A "user1:pass1" -t tcp -p ":33080" ` + +另外,sps代理,本地认证集成了外部HTTP API认证,我们可以通过--auth-url参数指定一个http url接口地址, +然后有用户连接的时候,proxy会GET方式请求这url,带上下面四个参数,如果返回HTTP状态码204,代表认证成功 +其它情况认为认证失败. +比如: +`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" --auth-url "http://test.com/auth.php"` +用户连接的时候,proxy会GET方式请求这url("http://test.com/auth.php"), +带上user,pass,ip,三个参数: +http://test.com/auth.php?user={USER}&pass={PASS}&ip={IP}&target={TARGET} +user:用户名 +pass:密码 +ip:用户的IP,比如:192.168.1.200 +target:如果客户端是http(s)代理请求,这里代表的是请求的完整url,其它情况为空. + +如果没有-a或-F或--auth-url参数,就是关闭本地认证. +如果没有-A参数,连接上级不使用认证. + +#### **6.7 查看帮助** `./proxy help sps` ### **7.KCP配置** diff --git a/config.go b/config.go index 5d5e671..70f0a4a 100755 --- a/config.go +++ b/config.go @@ -223,7 +223,14 @@ func initConfig() (err error) { spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type ").Short('S').Enum("http", "socks") spsArgs.DNSAddress = sps.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() spsArgs.DNSTTL = sps.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() - + spsArgs.AuthFile = sps.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String() + spsArgs.Auth = sps.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings() + spsArgs.LocalIPS = sps.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings() + spsArgs.AuthURL = sps.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String() + spsArgs.AuthURLTimeout = sps.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int() + spsArgs.AuthURLOkCode = sps.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int() + spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int() + spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String() //parse args serviceName := kingpin.MustParse(app.Parse(os.Args[1:])) diff --git a/services/args.go b/services/args.go index 2786f79..68ccb78 100644 --- a/services/args.go +++ b/services/args.go @@ -215,6 +215,14 @@ type SPSArgs struct { ParentServiceType *string DNSAddress *string DNSTTL *int + AuthFile *string + Auth *[]string + AuthURL *string + AuthURLOkCode *int + AuthURLTimeout *int + AuthURLRetry *int + LocalIPS *[]string + ParentAuth *string } func (a *SPSArgs) Protocol() string { diff --git a/services/sps.go b/services/sps.go index 1b5de03..590e6d9 100644 --- a/services/sps.go +++ b/services/sps.go @@ -2,6 +2,7 @@ package services import ( "bytes" + "encoding/base64" "errors" "fmt" "io/ioutil" @@ -12,18 +13,21 @@ import ( "snail007/proxy/utils/socks" "strconv" "strings" + "time" ) type SPS struct { outPool utils.OutPool cfg SPSArgs domainResolver utils.DomainResolver + basicAuth utils.BasicAuth } func NewSPS() Service { return &SPS{ - outPool: utils.OutPool{}, - cfg: SPSArgs{}, + outPool: utils.OutPool{}, + cfg: SPSArgs{}, + basicAuth: utils.BasicAuth{}, } } func (s *SPS) CheckArgs() { @@ -46,6 +50,10 @@ func (s *SPS) CheckArgs() { } func (s *SPS) InitService() { s.InitOutConnPool() + if *s.cfg.DNSAddress != "" { + (*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL) + } + s.InitBasicAuth() } func (s *SPS) InitOutConnPool() { if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP { @@ -117,7 +125,7 @@ func (s *SPS) callback(inConn net.Conn) { err = fmt.Errorf("unkown parent type %s", *s.cfg.ParentType) } if err != nil { - log.Printf("connect to %s parent %s fail, ERR:%s", *s.cfg.ParentType, *s.cfg.Parent, err) + log.Printf("connect to %s parent %s fail, ERR:%s from %s", *s.cfg.ParentType, *s.cfg.Parent, err, inConn.RemoteAddr()) utils.CloseConn(&inConn) } } @@ -131,51 +139,32 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { return } address := "" + var auth socks.Auth var forwardBytes []byte //fmt.Printf("%v", header) if header[0] == socks.VERSION_V5 { - //socks - methodReq, e := socks.NewMethodsRequest(*inConn, header) - if e != nil { - log.Printf("new method request err:%s", e) - utils.CloseConn(inConn) - err = e.(error) + //socks5 server + var serverConn *socks.ServerConn + if s.IsBasicAuth() { + serverConn = socks.NewServerConn(inConn, time.Millisecond*time.Duration(*s.cfg.Timeout), &s.basicAuth, "", header) + } else { + serverConn = socks.NewServerConn(inConn, time.Millisecond*time.Duration(*s.cfg.Timeout), nil, "", header) + } + if err = serverConn.Handshake(); err != nil { return } - if !methodReq.Select(socks.Method_NO_AUTH) { - methodReq.Reply(socks.Method_NONE_ACCEPTABLE) - utils.CloseConn(inConn) - log.Printf("none method found : Method_NO_AUTH") - return - } - //method select reply - err = methodReq.Reply(socks.Method_NO_AUTH) - if err != nil { - log.Printf("reply answer data fail,ERR: %s", err) - utils.CloseConn(inConn) - return - } - //request detail - request, e := socks.NewRequest(*inConn) - if e != nil { - log.Printf("read request data fail,ERR: %s", e) - utils.CloseConn(inConn) - err = e.(error) - return - } - if request.CMD() != socks.CMD_CONNECT { - //只支持tcp - request.TCPReply(socks.REP_UNKNOWN) - utils.CloseConn(inConn) - err = errors.New("cmd not supported") - return - } - address = request.Addr() - request.TCPReply(socks.REP_SUCCESS) + address = serverConn.Target() + auth = serverConn.AuthData() } else if bytes.IndexByte(header, '\n') != -1 { //http var request utils.HTTPRequest - request, err = utils.NewHTTPRequest(inConn, 1024, false, nil, header) + (*inConn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) + if s.IsBasicAuth() { + request, err = utils.NewHTTPRequest(inConn, 1024, true, &s.basicAuth, header) + } else { + request, err = utils.NewHTTPRequest(inConn, 1024, false, nil, header) + } + (*inConn).SetDeadline(time.Time{}) if err != nil { log.Printf("new http request fail,ERR: %s", err) utils.CloseConn(inConn) @@ -189,6 +178,17 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { forwardBytes = request.HeadBuf } address = request.Host + var userpass string + if s.IsBasicAuth() { + userpass, err = request.GetAuthDataStr() + if err != nil { + return + } + userpassA := strings.Split(userpass, ":") + if len(userpassA) == 2 { + auth = socks.Auth{User: userpassA[0], Password: userpassA[1]} + } + } } else { log.Printf("unknown request from: %s,%s", (*inConn).RemoteAddr(), string(header)) utils.CloseConn(inConn) @@ -207,12 +207,42 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { utils.CloseConn(inConn) return } + //ask parent for connect to target address if *s.cfg.ParentServiceType == "http" { //http parent - fmt.Fprintf(outConn, "CONNECT %s HTTP/1.1\r\n", address) + pb := new(bytes.Buffer) + pb.Write([]byte(fmt.Sprintf("CONNECT %s HTTP/1.1\r\nProxy-Connection: Keep-Alive\r\n", address))) + //Proxy-Authorization:\r\n + u := "" + if *s.cfg.ParentAuth != "" { + a := strings.Split(*s.cfg.ParentAuth, ":") + if len(a) != 2 { + err = fmt.Errorf("parent auth data format error") + return + } + u = fmt.Sprintf("%s:%s", a[0], a[1]) + } else { + if !s.IsBasicAuth() && auth.Password != "" && auth.User != "" { + u = fmt.Sprintf("%s:%s", auth.User, auth.Password) + } + } + if u != "" { + pb.Write([]byte(fmt.Sprintf("Proxy-Authorization:Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(u))))) + } + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) + _, err = outConn.Write(pb.Bytes()) + outConn.SetDeadline(time.Time{}) + if err != nil { + log.Printf("write CONNECT to %s , err:%s", *s.cfg.Parent, err) + utils.CloseConn(inConn) + utils.CloseConn(&outConn) + return + } reply := make([]byte, 100) - n, err = outConn.Read(reply) + outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) + _, err = outConn.Read(reply) + outConn.SetDeadline(time.Time{}) if err != nil { log.Printf("read reply from %s , err:%s", *s.cfg.Parent, err) utils.CloseConn(inConn) @@ -222,53 +252,25 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { //log.Printf("reply: %s", string(reply[:n])) } else { log.Printf("connect %s", address) - //socks parent - //send auth type - _, err = outConn.Write([]byte{0x05, 0x01, 0x00}) - if err != nil { - log.Printf("write method to %s fail, err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) + //socks client + var clientConn *socks.ClientConn + if *s.cfg.ParentAuth != "" { + a := strings.Split(*s.cfg.ParentAuth, ":") + if len(a) != 2 { + err = fmt.Errorf("parent auth data format error") + return + } + clientConn = socks.NewClientConn(&outConn, "tcp", address, time.Millisecond*time.Duration(*s.cfg.Timeout), &socks.Auth{User: a[0], Password: a[1]}, header) + } else { + if !s.IsBasicAuth() && auth.Password != "" && auth.User != "" { + clientConn = socks.NewClientConn(&outConn, "tcp", address, time.Millisecond*time.Duration(*s.cfg.Timeout), &auth, header) + } else { + clientConn = socks.NewClientConn(&outConn, "tcp", address, time.Millisecond*time.Duration(*s.cfg.Timeout), nil, header) + } + } + if err = clientConn.Handshake(); err != nil { return } - //read reply - reply := make([]byte, 512) - n, err = outConn.Read(reply) - if err != nil { - log.Printf("read reply from %s , err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) - return - } - //log.Printf("method reply %v", reply[:n]) - - //build request - buf, err = s.buildRequest(address) - if err != nil { - log.Printf("build request to %s fail , err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) - return - } - //send address request - _, err = outConn.Write(buf) - if err != nil { - log.Printf("write request to %s fail, err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) - return - } - //read reply - reply = make([]byte, 512) - n, err = outConn.Read(reply) - if err != nil { - log.Printf("read reply from %s , err:%s", *s.cfg.Parent, err) - utils.CloseConn(inConn) - utils.CloseConn(&outConn) - return - } - - //log.Printf("request reply %v", reply[:n]) } //forward client data to target,if necessary. if len(forwardBytes) > 0 { @@ -283,6 +285,34 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { log.Printf("conn %s - %s connected", inAddr, outAddr) return } +func (s *SPS) InitBasicAuth() (err error) { + if *s.cfg.DNSAddress != "" { + s.basicAuth = utils.NewBasicAuth(&(*s).domainResolver) + } else { + s.basicAuth = utils.NewBasicAuth(nil) + } + if *s.cfg.AuthURL != "" { + s.basicAuth.SetAuthURL(*s.cfg.AuthURL, *s.cfg.AuthURLOkCode, *s.cfg.AuthURLTimeout, *s.cfg.AuthURLRetry) + log.Printf("auth from %s", *s.cfg.AuthURL) + } + if *s.cfg.AuthFile != "" { + var n = 0 + n, err = s.basicAuth.AddFromFile(*s.cfg.AuthFile) + if err != nil { + err = fmt.Errorf("auth-file ERR:%s", err) + return + } + log.Printf("auth data added from file %d , total:%d", n, s.basicAuth.Total()) + } + if len(*s.cfg.Auth) > 0 { + n := s.basicAuth.Add(*s.cfg.Auth) + log.Printf("auth data added %d, total:%d", n, s.basicAuth.Total()) + } + return +} +func (s *SPS) IsBasicAuth() bool { + return *s.cfg.AuthFile != "" || len(*s.cfg.Auth) > 0 || *s.cfg.AuthURL != "" +} func (s *SPS) buildRequest(address string) (buf []byte, err error) { host, portStr, err := net.SplitHostPort(address) if err != nil { diff --git a/utils/socks/client.go b/utils/socks/client.go index 7253d21..619e3c7 100644 --- a/utils/socks/client.go +++ b/utils/socks/client.go @@ -63,7 +63,7 @@ func NewClientConn(conn *net.Conn, network, target string, timeout time.Duration // connect takes an existing connection to a socks5 proxy server, // and commands the server to extend that connection to target, // which must be a canonical address with a host and port. -func (s *ClientConn) Connect() error { +func (s *ClientConn) Handshake() error { host, portStr, err := net.SplitHostPort(s.addr) if err != nil { return err diff --git a/utils/socks/server.go b/utils/socks/server.go index 121798a..3cf00be 100644 --- a/utils/socks/server.go +++ b/utils/socks/server.go @@ -106,14 +106,15 @@ func (s *ServerConn) Handshake() (err error) { err = fmt.Errorf("new methods request fail,ERR: %s", e) return } - if s.auth == nil { - if !methodReq.Select(Method_NO_AUTH) { - (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) - methodReq.Reply(Method_NONE_ACCEPTABLE) - (*s.conn).SetReadDeadline(time.Time{}) - err = fmt.Errorf("none method found : Method_NO_AUTH") - return - } + //log.Printf("%v,s.auth == %v && methodReq.Select(Method_NO_AUTH) %v", methodReq.methods, s.auth, methodReq.Select(Method_NO_AUTH)) + if s.auth == nil && methodReq.Select(Method_NO_AUTH) && !methodReq.Select(Method_USER_PASS) { + // if !methodReq.Select(Method_NO_AUTH) { + // (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) + // methodReq.Reply(Method_NONE_ACCEPTABLE) + // (*s.conn).SetReadDeadline(time.Time{}) + // err = fmt.Errorf("none method found : Method_NO_AUTH") + // return + // } s.method = Method_NO_AUTH //method select reply (*s.conn).SetReadDeadline(time.Now().Add(time.Second * s.timeout)) @@ -158,7 +159,7 @@ func (s *ServerConn) Handshake() (err error) { //err = fmt.Errorf("user:%s,pass:%s", user, pass) //auth _addr := strings.Split(remoteAddr.String(), ":") - if s.auth.CheckUserPass(s.user, s.password, _addr[0], "") { + if s.auth == nil || s.auth.CheckUserPass(s.user, s.password, _addr[0], "") { (*s.conn).SetDeadline(time.Now().Add(time.Millisecond * time.Duration(s.timeout))) _, err = (*s.conn).Write([]byte{0x01, 0x00}) (*s.conn).SetDeadline(time.Time{}) diff --git a/utils/structs.go b/utils/structs.go index dc8dcfd..764622d 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -317,7 +317,7 @@ func NewHTTPRequest(inConn *net.Conn, bufSize int, isBasicAuth bool, basicAuth * req = HTTPRequest{ conn: inConn, } - if len(header) == 1 { + if header != nil && len(header) == 1 && len(header[0]) > 1 { buf = header[0] n = len(header[0]) } else { @@ -402,18 +402,13 @@ func (req *HTTPRequest) IsHTTPS() bool { return req.Method == "CONNECT" } -func (req *HTTPRequest) BasicAuth() (err error) { - +func (req *HTTPRequest) GetAuthDataStr() (basicInfo string, err error) { // log.Printf("request :%s", string(req.HeadBuf)) - code := "407" authorization := req.getHeader("Proxy-Authorization") - // if authorization == "" { - // authorization = req.getHeader("Authorization") - // code = "401" - // } + authorization = strings.Trim(authorization, " \r\n\t") if authorization == "" { - fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized", code) + fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\nWWW-Authenticate: Basic realm=\"\"\r\n\r\nUnauthorized", "407") CloseConn(req.conn) err = errors.New("require auth header data") return @@ -431,6 +426,10 @@ func (req *HTTPRequest) BasicAuth() (err error) { CloseConn(req.conn) return } + basicInfo = string(user) + return +} +func (req *HTTPRequest) BasicAuth() (err error) { addr := strings.Split((*req.conn).RemoteAddr().String(), ":") URL := "" if req.IsHTTPS() { @@ -438,10 +437,14 @@ func (req *HTTPRequest) BasicAuth() (err error) { } else { URL = req.getHTTPURL() } + user, err := req.GetAuthDataStr() + if err != nil { + return + } authOk := (*req.basicAuth).Check(string(user), addr[0], URL) //log.Printf("auth %s,%v", string(user), authOk) if !authOk { - fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\n\r\nUnauthorized", code) + fmt.Fprintf((*req.conn), "HTTP/1.1 %s Unauthorized\r\n\r\nUnauthorized", "407") CloseConn(req.conn) err = fmt.Errorf("basic auth fail") return From 59b5ef2df4795708bc830f7a887bd729002abef8 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:21:02 +0800 Subject: [PATCH 29/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index d08694d..0ebd5d9 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -743,13 +743,14 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 2:设置的本地认证信息`local-auth`。 3:设置的连接上级使用的认证信息`parent-auth`。 4:最终发送给上级的认证信息`auth-info-to-parent`。 -他们的情况关系如下: -| user-auth | local-auth | parent-auth | auth-info-to-parent -| 有/没有 | 有 | 有 | 来自parent-auth -| 有/没有 | 没有 | 有 | 来自parent-auth -| 有/没有 | 有 | 没有 | 无 -| 没有 | 没有 | 没有 | 无 -| 有 | 没有 | 没有 | 来自user-auth +他们的情况关系如下: +user-auth | local-auth | parent-auth | auth-info-to-parent +:-:| :-: | :-: |:-: +有/没有 | 有 | 有 | 来自parent-auth +有/没有 | 没有 | 有 | 来自parent-auth +有/没有 | 有 | 没有 | 无 +没有 | 没有 | 没有 | 无 +有 | 没有 | 没有 | 来自user-auth 对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` From dd52ad8a8ade9932b1d5da53cdaee0c994b6317b Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:22:37 +0800 Subject: [PATCH 30/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 0ebd5d9..70c9f6c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -744,13 +744,13 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 3:设置的连接上级使用的认证信息`parent-auth`。 4:最终发送给上级的认证信息`auth-info-to-parent`。 他们的情况关系如下: -user-auth | local-auth | parent-auth | auth-info-to-parent -:-:| :-: | :-: |:-: -有/没有 | 有 | 有 | 来自parent-auth -有/没有 | 没有 | 有 | 来自parent-auth -有/没有 | 有 | 没有 | 无 -没有 | 没有 | 没有 | 无 -有 | 没有 | 没有 | 来自user-auth +|user-auth | local-auth | parent-auth | auth-info-to-parent| +|:-:| :-: | :-: |:-:| +|有/没有 | 有 | 有 | 来自parent-auth| +|有/没有 | 没有 | 有 | 来自parent-auth| +|有/没有 | 有 | 没有 | 无| +|没有 | 没有 | 没有 | 无| +|有 | 没有 | 没有 | 来自user-auth| 对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` From 69b65a37caab7d3863248b589540a13826d1b7ed Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:25:17 +0800 Subject: [PATCH 31/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 70c9f6c..1731022 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -743,14 +743,14 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 2:设置的本地认证信息`local-auth`。 3:设置的连接上级使用的认证信息`parent-auth`。 4:最终发送给上级的认证信息`auth-info-to-parent`。 -他们的情况关系如下: -|user-auth | local-auth | parent-auth | auth-info-to-parent| -|:-:| :-: | :-: |:-:| -|有/没有 | 有 | 有 | 来自parent-auth| -|有/没有 | 没有 | 有 | 来自parent-auth| -|有/没有 | 有 | 没有 | 无| -|没有 | 没有 | 没有 | 无| -|有 | 没有 | 没有 | 来自user-auth| +他们的情况关系如下: +| user-auth | local-auth | parent-auth | auth-info-to-paren +| ------ | ------ | ------ | ------ +| 有/没有 | 有 | 有 | 来自parent-auth +| 有/没有 | 没有 | 有 | 来自parent-auth +| 有/没有 | 有 | 没有 | 无 +| 没有 | 没有 | 没有 | 无 +| 有 | 没有 | 没有 | 来自user-auth 对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` From bd056d74cca6bdda6a9ea78fb314841854b64a82 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:25:47 +0800 Subject: [PATCH 32/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README_ZH.md b/README_ZH.md index 1731022..7946109 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -744,6 +744,7 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 3:设置的连接上级使用的认证信息`parent-auth`。 4:最终发送给上级的认证信息`auth-info-to-parent`。 他们的情况关系如下: + | user-auth | local-auth | parent-auth | auth-info-to-paren | ------ | ------ | ------ | ------ | 有/没有 | 有 | 有 | 来自parent-auth From d911be7d8053ecb7e2b997761ff396cbe8b7b0ea Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:27:59 +0800 Subject: [PATCH 33/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 7946109..522c58b 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -753,17 +753,17 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: | 没有 | 没有 | 没有 | 无 | 有 | 没有 | 没有 | 来自user-auth -对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 +对于sps代理我们可以进行用户名密码认证,认证的用户名和密码可以在命令行指定 `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` 多个用户,重复-a参数即可. 也可以放在文件中,格式是一行一个"用户名:密码",然后用-F指定. `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -F auth-file.txt` -如果上级有认证,下级可以通过-A参数设置认证信息,比如: +如果上级有认证,下级可以通过-A参数设置认证信息,比如: 上级:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" -a "user1:pass1" -a "user2:pass2"` 下级:`./proxy sps -S http -T tcp -P 127.0.0.1:8080 -A "user1:pass1" -t tcp -p ":33080" ` -另外,sps代理,本地认证集成了外部HTTP API认证,我们可以通过--auth-url参数指定一个http url接口地址, +另外,sps代理,本地认证集成了外部HTTP API认证,我们可以通过--auth-url参数指定一个http url接口地址, 然后有用户连接的时候,proxy会GET方式请求这url,带上下面四个参数,如果返回HTTP状态码204,代表认证成功 其它情况认为认证失败. 比如: From b11c7e632b402036be9d94167cb9dc7bd11d3922 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:30:09 +0800 Subject: [PATCH 34/76] fix #47 Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index 522c58b..5afc267 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -769,7 +769,7 @@ sps支持http(s)\socks5代理认证,可以级联认证,有四个重要的信息: 比如: `./proxy sps -S http -T tcp -P 127.0.0.1:8080 -t tcp -p ":33080" --auth-url "http://test.com/auth.php"` 用户连接的时候,proxy会GET方式请求这url("http://test.com/auth.php"), -带上user,pass,ip,三个参数: +带上user,pass,ip,target四个参数: http://test.com/auth.php?user={USER}&pass={PASS}&ip={IP}&target={TARGET} user:用户名 pass:密码 From 27ce6e1bd29d27328e89eb5860891143efce3684 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 28 Mar 2018 13:34:08 +0800 Subject: [PATCH 35/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index 5afc267..81fb1b0 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -23,7 +23,7 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - 集成外部API,HTTP(S),SOCKS5代理认证功能可以与外部HTTP API集成,可以方便的通过外部系统控制代理用户. - 反向代理,支持直接把域名解析到proxy监听的ip,然后proxy就会帮你代理访问需要访问的HTTP(S)网站. - 透明HTTP(S)代理,配合iptables,在网关直接把出去的80,443方向的流量转发到proxy,就能实现无感知的智能路由器代理. -- 协议转换,可以把已经存在的HTTP(S)或SOCKS5代理转换为一个端口同时支持HTTP(S)和SOCKS5代理,转换后的SOCKS5代理不支持UDP功能。 +- 协议转换,可以把已经存在的HTTP(S)或SOCKS5代理转换为一个端口同时支持HTTP(S)和SOCKS5代理,转换后的SOCKS5代理不支持UDP功能,同时支持强大的级联认证功能。 ### Why need these? - 当由于某某原因,我们不能访问我们在其它地方的服务,我们可以通过多个相连的proxy节点建立起一个安全的隧道访问我们的服务. From c4d9382ac7a652c39eaf6b6fff827adda38f478c Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 29 Mar 2018 16:59:52 +0800 Subject: [PATCH 36/76] Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 3 +++ README_ZH.md | 5 +++-- install_auto.sh | 2 +- main.go | 2 +- release.sh | 2 +- services/mux_bridge.go | 2 ++ services/sps.go | 1 + 7 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 38e7efa..3d81cbd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,7 @@ proxy更新日志 +v4.7 +1.优化了bridge的日志,增加了client和server的掉线日志. + v4.6 1.sps,http(s),socks5,内网穿透都做了大量的超时优化处理,更加稳定. 2.sps增加了强大的树形级联认证支持,可以轻松构建你的认证代理网络. diff --git a/README_ZH.md b/README_ZH.md index 81fb1b0..54de418 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -35,7 +35,8 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - ... -本页是v4.6手册,其他版本手册请点击下面链接查看. +本页是v4.7手册,其他版本手册请点击下面链接查看. +- [v4.6手册](https://github.com/snail007/goproxy/tree/v4.6) - [v4.5手册](https://github.com/snail007/goproxy/tree/v4.5) - [v4.4手册](https://github.com/snail007/goproxy/tree/v4.4) - [v4.3手册](https://github.com/snail007/goproxy/tree/v4.3) @@ -149,7 +150,7 @@ curl -L https://raw.githubusercontent.com/snail007/goproxy/master/install_auto.s 下载地址:https://github.com/snail007/goproxy/releases ```shell cd /root/proxy/ -wget https://github.com/snail007/goproxy/releases/download/v4.6/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.7/proxy-linux-amd64.tar.gz ``` #### **2.下载自动安装脚本** ```shell diff --git a/install_auto.sh b/install_auto.sh index 69df125..fac84d2 100755 --- a/install_auto.sh +++ b/install_auto.sh @@ -5,7 +5,7 @@ if [ -e /tmp/proxy ]; then fi mkdir /tmp/proxy cd /tmp/proxy -wget https://github.com/snail007/goproxy/releases/download/v4.6/proxy-linux-amd64.tar.gz +wget https://github.com/snail007/goproxy/releases/download/v4.7/proxy-linux-amd64.tar.gz # #install proxy tar zxvf proxy-linux-amd64.tar.gz diff --git a/main.go b/main.go index 139d970..bd047ee 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,7 @@ import ( "syscall" ) -const APP_VERSION = "4.6" +const APP_VERSION = "4.7" func main() { err := initConfig() diff --git a/release.sh b/release.sh index 965d039..7426d55 100755 --- a/release.sh +++ b/release.sh @@ -1,5 +1,5 @@ #!/bin/bash -VER="4.6" +VER="4.7" RELEASE="release-${VER}" rm -rf .cert mkdir .cert diff --git a/services/mux_bridge.go b/services/mux_bridge.go index ec57b1f..c2d51fd 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -104,6 +104,7 @@ func (s *MuxBridge) handler(inConn net.Conn) { if err != nil { session.Close() utils.CloseConn(&inConn) + log.Printf("server connection %s %s released", serverID, key) return } go s.callback(stream, serverID, key) @@ -144,6 +145,7 @@ func (s *MuxBridge) handler(inConn net.Conn) { defer s.l.Unlock() if sess, ok := group.Get(index); ok && sess.(*smux.Session).IsClosed() { group.Remove(index) + log.Printf("client connection %s released", key) } if group.IsEmpty() { s.clientControlConns.Remove(groupKey) diff --git a/services/sps.go b/services/sps.go index 590e6d9..5d99564 100644 --- a/services/sps.go +++ b/services/sps.go @@ -230,6 +230,7 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { if u != "" { pb.Write([]byte(fmt.Sprintf("Proxy-Authorization:Basic %s\r\n", base64.StdEncoding.EncodeToString([]byte(u))))) } + pb.Write([]byte("\r\n")) outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Write(pb.Bytes()) outConn.SetDeadline(time.Time{}) From 4c33a1e9b2c6642a1a2d8ffce68263493c76e03a Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 29 Mar 2018 17:25:32 +0800 Subject: [PATCH 37/76] Signed-off-by: arraykeys@gmail.com --- services/sps.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/sps.go b/services/sps.go index 5d99564..8d687cc 100644 --- a/services/sps.go +++ b/services/sps.go @@ -240,7 +240,7 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { utils.CloseConn(&outConn) return } - reply := make([]byte, 100) + reply := make([]byte, 1024) outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) _, err = outConn.Read(reply) outConn.SetDeadline(time.Time{}) From f7b363ec73a12514450e31f8fd0944238123cbfb Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 29 Mar 2018 17:31:38 +0800 Subject: [PATCH 38/76] Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 3d81cbd..ba7a176 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,9 @@ proxy更新日志 v4.7 1.优化了bridge的日志,增加了client和server的掉线日志. +2.优化了sps读取http(s)代理响应的缓冲大小,同时优化了CONNECT请求, +避免了某些代理服务器返回过多数据导致不能正常通讯的问题. + v4.6 1.sps,http(s),socks5,内网穿透都做了大量的超时优化处理,更加稳定. From a11ce38747caede4ec517939c2e20bda22c57a55 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 3 Apr 2018 17:34:33 +0800 Subject: [PATCH 39/76] Signed-off-by: arraykeys@gmail.com --- utils/structs.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/structs.go b/utils/structs.go index 764622d..e7f3958 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -280,7 +280,11 @@ func (ba *BasicAuth) checkFromURL(userpass, ip, target string) (err error) { } else { err = fmt.Errorf("token error") } - err = fmt.Errorf("auth fail from url %s,resonse code: %d, except: %d , %s , %s", URL, code, ba.authOkCode, ip, string(body)) + b := string(body) + if len(b) > 50 { + b = b[:50] + } + err = fmt.Errorf("auth fail from url %s,resonse code: %d, except: %d , %s , %s", URL, code, ba.authOkCode, ip, b) } if err != nil && tryCount < ba.authRetry { log.Print(err) From 3aba428b76146da39d4a506cae445be5e109622a Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Mon, 9 Apr 2018 16:16:55 +0800 Subject: [PATCH 40/76] =?UTF-8?q?=E5=A2=9E=E5=8A=A0gomobile=20sdk=E5=AF=B9?= =?UTF-8?q?android/ios=E8=BF=9B=E8=A1=8C=E6=94=AF=E6=8C=81.=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=94=99=E8=AF=AF=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: arraykeys@gmail.com --- sdk/release.sh | 41 +++++ sdk/sdk.go | 325 ++++++++++++++++++++++++++++++++++++++ services/http.go | 43 +++-- services/mux_bridge.go | 25 ++- services/mux_client.go | 27 ++-- services/mux_server.go | 42 +++-- services/service.go | 30 ++-- services/socks.go | 63 ++++---- services/sps.go | 32 ++-- services/tcp.go | 26 ++- services/tunnel_bridge.go | 117 ++------------ services/tunnel_client.go | 81 ++-------- services/tunnel_server.go | 94 ++++------- services/udp.go | 26 ++- utils/functions.go | 8 +- 15 files changed, 626 insertions(+), 354 deletions(-) create mode 100755 sdk/release.sh create mode 100644 sdk/sdk.go diff --git a/sdk/release.sh b/sdk/release.sh new file mode 100755 index 0000000..09b7b00 --- /dev/null +++ b/sdk/release.sh @@ -0,0 +1,41 @@ +#/bin/bash +VER="v4.7" +rm -rf release-* +#arm +gomobile bind -v -target=android/arm +mkdir proxy-sdk-arm +mv sdk.aar proxy-sdk-arm/proxy-sdk-arm.aar +mv sdk-sources.jar proxy-sdk-arm/proxy-sdk-arm-sources.jar +tar zcfv proxy-sdk-arm-${VER}.tar.gz proxy-sdk-arm +rm -rf proxy-sdk-arm +#arm64 +gomobile bind -v -target=android/arm64 +mkdir proxy-sdk-arm64 +mv sdk.aar proxy-sdk-arm64/proxy-sdk-arm64.aar +mv sdk-sources.jar proxy-sdk-arm64/proxy-sdk-arm64-sources.jar +tar zcfv proxy-sdk-arm64-${VER}.tar.gz proxy-sdk-arm64 +rm -rf proxy-sdk-arm64 +#386 +gomobile bind -v -target=android/386 +mkdir proxy-sdk-386 +mv sdk.aar proxy-sdk-386/proxy-sdk-386.aar +mv sdk-sources.jar proxy-sdk-386/proxy-sdk-386-sources.jar +tar zcfv proxy-sdk-386-${VER}.tar.gz proxy-sdk-386 +rm -rf proxy-sdk-386 +#amd64 +gomobile bind -v -target=android/amd64 +mkdir proxy-sdk-amd64 +mv sdk.aar proxy-sdk-amd64/proxy-sdk-amd64.aar +mv sdk-sources.jar proxy-sdk-amd64/proxy-sdk-amd64-sources.jar +tar zcfv proxy-sdk-amd64-${VER}.tar.gz proxy-sdk-amd64 +rm -rf proxy-sdk-amd64 +#all-in-one +gomobile bind -v -target=android +mkdir proxy-sdk-all +mv sdk.aar proxy-sdk-all/proxy-sdk-all.aar +mv sdk-sources.jar proxy-sdk-all/proxy-sdk-all-sources.jar +tar zcfv proxy-sdk-all-${VER}.tar.gz proxy-sdk-all +rm -rf proxy-sdk-all +mkdir proxy-sdk-release-${VER} +mv *.tar.gz proxy-sdk-release-${VER} +echo "done." \ No newline at end of file diff --git a/sdk/sdk.go b/sdk/sdk.go new file mode 100644 index 0000000..cb37165 --- /dev/null +++ b/sdk/sdk.go @@ -0,0 +1,325 @@ +package sdk + +import ( + "crypto/sha1" + "fmt" + "log" + "os" + "snail007/proxy/services" + "snail007/proxy/services/kcpcfg" + "strings" + + kcp "github.com/xtaci/kcp-go" + "golang.org/x/crypto/pbkdf2" + kingpin "gopkg.in/alecthomas/kingpin.v2" +) + +var ( + app *kingpin.Application + service *services.ServiceItem +) + +//Start argsStr: is the whole command line args string +//such as : +//1."http -t tcp -p :8989" +//2."socks -t tcp -p :8989" +//and so on. +//if an error occured , errStr will be the error reason +//if start success, errStr is empty. +func Start(argsStr string) (errStr string) { + //define args + tcpArgs := services.TCPArgs{} + httpArgs := services.HTTPArgs{} + tunnelServerArgs := services.TunnelServerArgs{} + tunnelClientArgs := services.TunnelClientArgs{} + tunnelBridgeArgs := services.TunnelBridgeArgs{} + muxServerArgs := services.MuxServerArgs{} + muxClientArgs := services.MuxClientArgs{} + muxBridgeArgs := services.MuxBridgeArgs{} + udpArgs := services.UDPArgs{} + socksArgs := services.SocksArgs{} + spsArgs := services.SPSArgs{} + kcpArgs := kcpcfg.KCPConfigArgs{} + //build srvice args + app = kingpin.New("proxy", "happy with proxy") + app.Author("snail").Version("4.7") + debug := app.Flag("debug", "debug log output").Default("false").Bool() + logfile := app.Flag("log", "log file path").Default("").String() + kcpArgs.Key = app.Flag("kcp-key", "pre-shared secret between client and server").Default("secrect").String() + kcpArgs.Crypt = app.Flag("kcp-method", "encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none").Default("aes").Enum("aes", "aes-128", "aes-192", "salsa20", "blowfish", "twofish", "cast5", "3des", "tea", "xtea", "xor", "sm4", "none") + kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast").Enum("fast3", "fast2", "fast", "normal", "manual") + kcpArgs.MTU = app.Flag("kcp-mtu", "set maximum transmission unit for UDP packets").Default("1350").Int() + kcpArgs.SndWnd = app.Flag("kcp-sndwnd", "set send window size(num of packets)").Default("1024").Int() + kcpArgs.RcvWnd = app.Flag("kcp-rcvwnd", "set receive window size(num of packets)").Default("1024").Int() + kcpArgs.DataShard = app.Flag("kcp-ds", "set reed-solomon erasure coding - datashard").Default("10").Int() + kcpArgs.ParityShard = app.Flag("kcp-ps", "set reed-solomon erasure coding - parityshard").Default("3").Int() + kcpArgs.DSCP = app.Flag("kcp-dscp", "set DSCP(6bit)").Default("0").Int() + kcpArgs.NoComp = app.Flag("kcp-nocomp", "disable compression").Default("false").Bool() + kcpArgs.AckNodelay = app.Flag("kcp-acknodelay", "be carefull! flush ack immediately when a packet is received").Default("true").Bool() + kcpArgs.NoDelay = app.Flag("kcp-nodelay", "be carefull!").Default("0").Int() + kcpArgs.Interval = app.Flag("kcp-interval", "be carefull!").Default("50").Int() + kcpArgs.Resend = app.Flag("kcp-resend", "be carefull!").Default("0").Int() + kcpArgs.NoCongestion = app.Flag("kcp-nc", "be carefull! no congestion").Default("0").Int() + kcpArgs.SockBuf = app.Flag("kcp-sockbuf", "be carefull!").Default("4194304").Int() + kcpArgs.KeepAlive = app.Flag("kcp-keepalive", "be carefull!").Default("10").Int() + + //########http######### + http := app.Command("http", "proxy on http mode") + httpArgs.Parent = http.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + httpArgs.CaCertFile = http.Flag("ca", "ca cert file for tls").Default("").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", "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() + httpArgs.Interval = http.Flag("interval", "check domain if blocked every interval seconds").Default("10").Int() + httpArgs.Blocked = http.Flag("blocked", "blocked domain file , one domain each line").Default("blocked").Short('b').String() + httpArgs.Direct = http.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String() + httpArgs.AuthFile = http.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String() + httpArgs.Auth = http.Flag("auth", "http basic auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings() + httpArgs.PoolSize = http.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() + httpArgs.CheckParentInterval = http.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() + httpArgs.Local = http.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String() + httpArgs.SSHUser = http.Flag("ssh-user", "user for ssh").Short('u').Default("").String() + 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.LocalIPS = http.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings() + httpArgs.AuthURL = http.Flag("auth-url", "http basic auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String() + httpArgs.AuthURLTimeout = http.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int() + httpArgs.AuthURLOkCode = http.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int() + httpArgs.AuthURLRetry = http.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("1").Int() + httpArgs.DNSAddress = http.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() + httpArgs.DNSTTL = http.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() + + //########tcp######### + tcp := app.Command("tcp", "proxy on tcp mode") + tcpArgs.Parent = tcp.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + tcpArgs.CertFile = tcp.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() + tcpArgs.KeyFile = tcp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + tcpArgs.Timeout = tcp.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('e').Default("2000").Int() + tcpArgs.ParentType = tcp.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "udp", "kcp") + tcpArgs.LocalType = tcp.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp", "kcp") + tcpArgs.PoolSize = tcp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() + tcpArgs.CheckParentInterval = tcp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() + tcpArgs.Local = tcp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() + + //########udp######### + udp := app.Command("udp", "proxy on udp mode") + udpArgs.Parent = udp.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + udpArgs.CertFile = udp.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() + udpArgs.KeyFile = udp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + udpArgs.Timeout = udp.Flag("timeout", "tcp timeout milliseconds when connect to parent proxy").Short('t').Default("2000").Int() + udpArgs.ParentType = udp.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "udp") + udpArgs.PoolSize = udp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() + udpArgs.CheckParentInterval = udp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() + udpArgs.Local = udp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() + + //########mux-server######### + muxServer := app.Command("server", "proxy on mux server mode") + muxServerArgs.Parent = muxServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + muxServerArgs.ParentType = muxServer.Flag("parent-type", "parent protocol type ").Default("tls").Short('T').Enum("tls", "tcp", "kcp") + muxServerArgs.CertFile = muxServer.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() + muxServerArgs.KeyFile = muxServer.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + muxServerArgs.Timeout = muxServer.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int() + muxServerArgs.IsUDP = muxServer.Flag("udp", "proxy on udp mux server mode").Default("false").Bool() + muxServerArgs.Key = muxServer.Flag("k", "client key").Default("default").String() + muxServerArgs.Route = muxServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings() + muxServerArgs.IsCompress = muxServer.Flag("c", "compress data when tcp|tls mode").Default("false").Bool() + muxServerArgs.SessionCount = muxServer.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int() + + //########mux-client######### + muxClient := app.Command("client", "proxy on mux client mode") + muxClientArgs.Parent = muxClient.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + muxClientArgs.ParentType = muxClient.Flag("parent-type", "parent protocol type ").Default("tls").Short('T').Enum("tls", "tcp", "kcp") + muxClientArgs.CertFile = muxClient.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() + muxClientArgs.KeyFile = muxClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + muxClientArgs.Timeout = muxClient.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int() + muxClientArgs.Key = muxClient.Flag("k", "key same with server").Default("default").String() + muxClientArgs.IsCompress = muxClient.Flag("c", "compress data when tcp|tls mode").Default("false").Bool() + muxClientArgs.SessionCount = muxClient.Flag("session-count", "session count which connect to bridge").Short('n').Default("10").Int() + + //########mux-bridge######### + muxBridge := app.Command("bridge", "proxy on mux bridge mode") + muxBridgeArgs.CertFile = muxBridge.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() + muxBridgeArgs.KeyFile = muxBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + muxBridgeArgs.Timeout = muxBridge.Flag("timeout", "tcp timeout with milliseconds").Short('i').Default("2000").Int() + muxBridgeArgs.Local = muxBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() + muxBridgeArgs.LocalType = muxBridge.Flag("local-type", "local protocol type ").Default("tls").Short('t').Enum("tls", "tcp", "kcp") + + //########tunnel-server######### + tunnelServer := app.Command("tserver", "proxy on tunnel server mode") + tunnelServerArgs.Parent = tunnelServer.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + tunnelServerArgs.CertFile = tunnelServer.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() + tunnelServerArgs.KeyFile = tunnelServer.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + tunnelServerArgs.Timeout = tunnelServer.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int() + tunnelServerArgs.IsUDP = tunnelServer.Flag("udp", "proxy on udp tunnel server mode").Default("false").Bool() + tunnelServerArgs.Key = tunnelServer.Flag("k", "client key").Default("default").String() + tunnelServerArgs.Route = tunnelServer.Flag("route", "local route to client's network, such as: PROTOCOL://LOCAL_IP:LOCAL_PORT@[CLIENT_KEY]CLIENT_LOCAL_HOST:CLIENT_LOCAL_PORT").Short('r').Default("").Strings() + + //########tunnel-client######### + tunnelClient := app.Command("tclient", "proxy on tunnel client mode") + tunnelClientArgs.Parent = tunnelClient.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + tunnelClientArgs.CertFile = tunnelClient.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() + tunnelClientArgs.KeyFile = tunnelClient.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + tunnelClientArgs.Timeout = tunnelClient.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int() + tunnelClientArgs.Key = tunnelClient.Flag("k", "key same with server").Default("default").String() + + //########tunnel-bridge######### + tunnelBridge := app.Command("tbridge", "proxy on tunnel bridge mode") + tunnelBridgeArgs.CertFile = tunnelBridge.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() + tunnelBridgeArgs.KeyFile = tunnelBridge.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + tunnelBridgeArgs.Timeout = tunnelBridge.Flag("timeout", "tcp timeout with milliseconds").Short('t').Default("2000").Int() + tunnelBridgeArgs.Local = tunnelBridge.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() + + //########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", "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.CaCertFile = socks.Flag("ca", "ca cert file for tls").Default("").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() + socksArgs.SSHKeyFile = socks.Flag("ssh-key", "private key file for ssh").Short('S').Default("").String() + socksArgs.SSHKeyFileSalt = socks.Flag("ssh-keysalt", "salt of ssh private key").Short('s').Default("").String() + socksArgs.SSHPassword = socks.Flag("ssh-password", "password for ssh").Short('A').Default("").String() + socksArgs.Always = socks.Flag("always", "always use parent proxy").Default("false").Bool() + socksArgs.Timeout = socks.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Default("5000").Int() + socksArgs.Interval = socks.Flag("interval", "check domain if blocked every interval seconds").Default("10").Int() + socksArgs.Blocked = socks.Flag("blocked", "blocked domain file , one domain each line").Default("blocked").Short('b').String() + 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.LocalIPS = socks.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings() + socksArgs.AuthURL = socks.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String() + socksArgs.AuthURLTimeout = socks.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int() + socksArgs.AuthURLOkCode = socks.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int() + socksArgs.AuthURLRetry = socks.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int() + socksArgs.DNSAddress = socks.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() + socksArgs.DNSTTL = socks.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() + //########socks+http(s)######### + sps := app.Command("sps", "proxy on socks+http(s) mode") + spsArgs.Parent = sps.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() + spsArgs.CertFile = sps.Flag("cert", "cert file for tls").Short('C').Default("proxy.crt").String() + spsArgs.KeyFile = sps.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() + spsArgs.CaCertFile = sps.Flag("ca", "ca cert file for tls").Default("").String() + spsArgs.Timeout = sps.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('i').Default("2000").Int() + spsArgs.ParentType = sps.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "kcp") + spsArgs.LocalType = sps.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp", "kcp") + spsArgs.Local = sps.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String() + spsArgs.ParentServiceType = sps.Flag("parent-service-type", "parent service type ").Short('S').Enum("http", "socks") + spsArgs.DNSAddress = sps.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() + spsArgs.DNSTTL = sps.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() + spsArgs.AuthFile = sps.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String() + spsArgs.Auth = sps.Flag("auth", "socks auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings() + spsArgs.LocalIPS = sps.Flag("local bind ips", "if your host behind a nat,set your public ip here avoid dead loop").Short('g').Strings() + spsArgs.AuthURL = sps.Flag("auth-url", "auth username and password will send to this url,response http code equal to 'auth-code' means ok,others means fail.").Default("").String() + spsArgs.AuthURLTimeout = sps.Flag("auth-timeout", "access 'auth-url' timeout milliseconds").Default("3000").Int() + spsArgs.AuthURLOkCode = sps.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int() + spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int() + spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String() + //parse args + args := strings.Fields(strings.Trim(argsStr, " ")) + serviceName, err := app.Parse(args) + if err != nil { + return fmt.Sprintf("parse args fail,err: %s", err) + } + //set kcp config + + switch *kcpArgs.Mode { + case "normal": + *kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 40, 2, 1 + case "fast": + *kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 0, 30, 2, 1 + case "fast2": + *kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 20, 2, 1 + case "fast3": + *kcpArgs.NoDelay, *kcpArgs.Interval, *kcpArgs.Resend, *kcpArgs.NoCongestion = 1, 10, 2, 1 + } + pass := pbkdf2.Key([]byte(*kcpArgs.Key), []byte("snail007-goproxy"), 4096, 32, sha1.New) + + switch *kcpArgs.Crypt { + case "sm4": + kcpArgs.Block, _ = kcp.NewSM4BlockCrypt(pass[:16]) + case "tea": + kcpArgs.Block, _ = kcp.NewTEABlockCrypt(pass[:16]) + case "xor": + kcpArgs.Block, _ = kcp.NewSimpleXORBlockCrypt(pass) + case "none": + kcpArgs.Block, _ = kcp.NewNoneBlockCrypt(pass) + case "aes-128": + kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:16]) + case "aes-192": + kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass[:24]) + case "blowfish": + kcpArgs.Block, _ = kcp.NewBlowfishBlockCrypt(pass) + case "twofish": + kcpArgs.Block, _ = kcp.NewTwofishBlockCrypt(pass) + case "cast5": + kcpArgs.Block, _ = kcp.NewCast5BlockCrypt(pass[:16]) + case "3des": + kcpArgs.Block, _ = kcp.NewTripleDESBlockCrypt(pass[:24]) + case "xtea": + kcpArgs.Block, _ = kcp.NewXTEABlockCrypt(pass[:16]) + case "salsa20": + kcpArgs.Block, _ = kcp.NewSalsa20BlockCrypt(pass) + default: + *kcpArgs.Crypt = "aes" + kcpArgs.Block, _ = kcp.NewAESBlockCrypt(pass) + } + //attach kcp config + tcpArgs.KCP = kcpArgs + httpArgs.KCP = kcpArgs + socksArgs.KCP = kcpArgs + spsArgs.KCP = kcpArgs + muxBridgeArgs.KCP = kcpArgs + muxServerArgs.KCP = kcpArgs + muxClientArgs.KCP = kcpArgs + + flags := log.Ldate + if *debug { + flags |= log.Lshortfile | log.Lmicroseconds + } else { + flags |= log.Ltime + } + log.SetFlags(flags) + + if *logfile != "" { + f, e := os.OpenFile(*logfile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if e != nil { + log.Fatal(e) + } + log.SetOutput(f) + } + + //regist services and run service + services.Regist("http", services.NewHTTP(), httpArgs) + services.Regist("tcp", services.NewTCP(), tcpArgs) + services.Regist("udp", services.NewUDP(), udpArgs) + services.Regist("tserver", services.NewTunnelServerManager(), tunnelServerArgs) + services.Regist("tclient", services.NewTunnelClient(), tunnelClientArgs) + services.Regist("tbridge", services.NewTunnelBridge(), tunnelBridgeArgs) + services.Regist("server", services.NewMuxServerManager(), muxServerArgs) + services.Regist("client", services.NewMuxClient(), muxClientArgs) + services.Regist("bridge", services.NewMuxBridge(), muxBridgeArgs) + services.Regist("socks", services.NewSocks(), socksArgs) + services.Regist("sps", services.NewSPS(), spsArgs) + + service, err = services.Run(serviceName) + if err != nil { + return fmt.Sprintf("run service [%s] fail, ERR:%s", serviceName, err) + } + return +} + +func Stop() { + if service != nil && service.S != nil { + service.S.Clean() + } +} diff --git a/services/http.go b/services/http.go index 37993ab..62fb269 100644 --- a/services/http.go +++ b/services/http.go @@ -34,26 +34,32 @@ func NewHTTP() Service { lockChn: make(chan bool, 1), } } -func (s *HTTP) CheckArgs() { - var err error +func (s *HTTP) CheckArgs() (err error) { if *s.cfg.Parent != "" && *s.cfg.ParentType == "" { - log.Fatalf("parent type unkown,use -T ") + err = fmt.Errorf("parent type unkown,use -T ") + return } if *s.cfg.ParentType == "tls" || *s.cfg.LocalType == "tls" { - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if err != nil { + return + } if *s.cfg.CaCertFile != "" { s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile) if err != nil { - log.Fatalf("read ca file error,ERR:%s", err) + err = fmt.Errorf("read ca file error,ERR:%s", err) + return } } } if *s.cfg.ParentType == "ssh" { if *s.cfg.SSHUser == "" { - log.Fatalf("ssh user required") + err = fmt.Errorf("ssh user required") + return } if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" { - log.Fatalf("ssh password or key required") + err = fmt.Errorf("ssh password or key required") + return } if *s.cfg.SSHPassword != "" { @@ -62,7 +68,8 @@ func (s *HTTP) CheckArgs() { var SSHSigner ssh.Signer s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile) if err != nil { - log.Fatalf("read key file ERR: %s", err) + err = fmt.Errorf("read key file ERR: %s", err) + return } if *s.cfg.SSHKeyFileSalt != "" { SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt)) @@ -70,13 +77,15 @@ func (s *HTTP) CheckArgs() { SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes) } if err != nil { - log.Fatalf("parse ssh private key fail,ERR: %s", err) + err = fmt.Errorf("parse ssh private key fail,ERR: %s", err) + return } s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner) } } + return } -func (s *HTTP) InitService() { +func (s *HTTP) InitService() (err error) { s.InitBasicAuth() if *s.cfg.Parent != "" { s.checker = utils.NewChecker(*s.cfg.HTTPTimeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct) @@ -85,9 +94,10 @@ func (s *HTTP) InitService() { (*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL) } if *s.cfg.ParentType == "ssh" { - err := s.ConnectSSH() + err = s.ConnectSSH() if err != nil { - log.Fatalf("init service fail, ERR: %s", err) + err = fmt.Errorf("init service fail, ERR: %s", err) + return } go func() { //循环检查ssh网络连通性 @@ -114,6 +124,7 @@ func (s *HTTP) InitService() { } }() } + return } func (s *HTTP) StopService() { if s.outPool.Pool != nil { @@ -122,12 +133,16 @@ func (s *HTTP) StopService() { } func (s *HTTP) Start(args interface{}) (err error) { s.cfg = args.(HTTPArgs) - s.CheckArgs() + if err = s.CheckArgs(); err != nil { + return + } if *s.cfg.Parent != "" { log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent) s.InitOutConnPool() } - s.InitService() + if err = s.InitService(); err != nil { + return + } for _, addr := range strings.Split(*s.cfg.Local, ",") { if addr != "" { host, port, _ := net.SplitHostPort(addr) diff --git a/services/mux_bridge.go b/services/mux_bridge.go index c2d51fd..ee4afa6 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -2,6 +2,7 @@ package services import ( "bufio" + "fmt" "io" "log" "math/rand" @@ -32,24 +33,34 @@ func NewMuxBridge() Service { return b } -func (s *MuxBridge) InitService() { - +func (s *MuxBridge) InitService() (err error) { + return } -func (s *MuxBridge) CheckArgs() { +func (s *MuxBridge) CheckArgs() (err error) { if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { - log.Fatalf("cert and key file required") + err = fmt.Errorf("cert and key file required") + return } if *s.cfg.LocalType == TYPE_TLS { - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if err != nil { + return + } } + return } func (s *MuxBridge) StopService() { } func (s *MuxBridge) Start(args interface{}) (err error) { s.cfg = args.(MuxBridgeArgs) - s.CheckArgs() - s.InitService() + if err = s.CheckArgs(); err != nil { + return + } + if err = s.InitService(); err != nil { + return + } + host, port, _ := net.SplitHostPort(*s.cfg.Local) p, _ := strconv.Atoi(port) sc := utils.NewServerChannel(host, p) diff --git a/services/mux_client.go b/services/mux_client.go index 398744a..2ea91ed 100644 --- a/services/mux_client.go +++ b/services/mux_client.go @@ -23,30 +23,40 @@ func NewMuxClient() Service { } } -func (s *MuxClient) InitService() { - +func (s *MuxClient) InitService() (err error) { + return } -func (s *MuxClient) CheckArgs() { +func (s *MuxClient) CheckArgs() (err error) { if *s.cfg.Parent != "" { log.Printf("use tls parent %s", *s.cfg.Parent) } else { - log.Fatalf("parent required") + err = fmt.Errorf("parent required") + return } if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { - log.Fatalf("cert and key file required") + err = fmt.Errorf("cert and key file required") + return } if *s.cfg.ParentType == "tls" { - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if err != nil { + return + } } + return } func (s *MuxClient) StopService() { } func (s *MuxClient) Start(args interface{}) (err error) { s.cfg = args.(MuxClientArgs) - s.CheckArgs() - s.InitService() + if err = s.CheckArgs(); err != nil { + return + } + if err = s.InitService(); err != nil { + return + } log.Printf("client started") count := 1 if *s.cfg.SessionCount > 0 { @@ -119,7 +129,6 @@ func (s *MuxClient) Start(args interface{}) (err error) { }() } } - }(i) } return diff --git a/services/mux_server.go b/services/mux_server.go index 9e1fc65..2ffc6b7 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -41,14 +41,19 @@ func NewMuxServerManager() Service { } func (s *MuxServerManager) Start(args interface{}) (err error) { s.cfg = args.(MuxServerArgs) - s.CheckArgs() + if err = s.CheckArgs(); err != nil { + return + } if *s.cfg.Parent != "" { log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent) } else { - log.Fatalf("parent required") + err = fmt.Errorf("parent required") + return } - s.InitService() + if err = s.InitService(); err != nil { + return + } log.Printf("server id: %s", s.serverID) //log.Printf("route:%v", *s.cfg.Route) @@ -103,15 +108,21 @@ func (s *MuxServerManager) Clean() { } func (s *MuxServerManager) StopService() { } -func (s *MuxServerManager) CheckArgs() { +func (s *MuxServerManager) CheckArgs() (err error) { if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { - log.Fatalf("cert and key file required") + err = fmt.Errorf("cert and key file required") + return } if *s.cfg.ParentType == "tls" { - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if err != nil { + return + } } + return } -func (s *MuxServerManager) InitService() { +func (s *MuxServerManager) InitService() (err error) { + return } func NewMuxServer() Service { @@ -129,19 +140,26 @@ type MuxUDPItem struct { srcAddr *net.UDPAddr } -func (s *MuxServer) InitService() { +func (s *MuxServer) InitService() (err error) { s.UDPConnDeamon() + return } -func (s *MuxServer) CheckArgs() { +func (s *MuxServer) CheckArgs() (err error) { if *s.cfg.Remote == "" { - log.Fatalf("remote required") + err = fmt.Errorf("remote required") + return } + return } func (s *MuxServer) Start(args interface{}) (err error) { s.cfg = args.(MuxServerArgs) - s.CheckArgs() - s.InitService() + if err = s.CheckArgs(); err != nil { + return + } + if err = s.InitService(); err != nil { + return + } host, port, _ := net.SplitHostPort(*s.cfg.Local) p, _ := strconv.Atoi(port) s.sc = utils.NewServerChannel(host, p) diff --git a/services/service.go b/services/service.go index de280af..b133210 100644 --- a/services/service.go +++ b/services/service.go @@ -2,7 +2,6 @@ package services import ( "fmt" - "log" "runtime/debug" ) @@ -28,24 +27,21 @@ func Regist(name string, s Service, args interface{}) { func Run(name string, args ...interface{}) (service *ServiceItem, err error) { service, ok := servicesMap[name] if ok { - go func() { - defer func() { - err := recover() - if err != nil { - log.Fatalf("%s servcie crashed, ERR: %s\ntrace:%s", name, err, string(debug.Stack())) - } - }() - if len(args) == 1 { - err = service.S.Start(args[0]) - } else { - err = service.S.Start(service.Args) - } - if err != nil { - log.Fatalf("%s servcie fail, ERR: %s", name, err) + defer func() { + e := recover() + if e != nil { + err = fmt.Errorf("%s servcie crashed, ERR: %s\ntrace:%s", name, e, string(debug.Stack())) } }() - } - if !ok { + if len(args) == 1 { + err = service.S.Start(args[0]) + } else { + err = service.S.Start(service.Args) + } + if err != nil { + err = fmt.Errorf("%s servcie fail, ERR: %s", name, err) + } + } else { err = fmt.Errorf("service %s not found", name) } return diff --git a/services/socks.go b/services/socks.go index 9828733..02fcaa6 100644 --- a/services/socks.go +++ b/services/socks.go @@ -35,36 +35,34 @@ func NewSocks() Service { } } -func (s *Socks) CheckArgs() { - var err error +func (s *Socks) CheckArgs() (err error) { + if *s.cfg.LocalType == "tls" || (*s.cfg.Parent != "" && *s.cfg.ParentType == "tls") { - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if err != nil { + return + } if *s.cfg.CaCertFile != "" { s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile) if err != nil { - log.Fatalf("read ca file error,ERR:%s", err) + err = fmt.Errorf("read ca file error,ERR:%s", err) + return } } } if *s.cfg.Parent != "" { if *s.cfg.ParentType == "" { - log.Fatalf("parent type unkown,use -T ") + err = fmt.Errorf("parent type unkown,use -T ") + return } - // if *s.cfg.ParentType == "tls" { - // s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) - // if *s.cfg.CaCertFile != "" { - // s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile) - // if err != nil { - // log.Fatalf("read ca file error,ERR:%s", err) - // } - // } - // } if *s.cfg.ParentType == "ssh" { if *s.cfg.SSHUser == "" { - log.Fatalf("ssh user required") + err = fmt.Errorf("ssh user required") + return } if *s.cfg.SSHKeyFile == "" && *s.cfg.SSHPassword == "" { - log.Fatalf("ssh password or key required") + err = fmt.Errorf("ssh password or key required") + return } if *s.cfg.SSHPassword != "" { s.cfg.SSHAuthMethod = ssh.Password(*s.cfg.SSHPassword) @@ -72,7 +70,8 @@ func (s *Socks) CheckArgs() { var SSHSigner ssh.Signer s.cfg.SSHKeyBytes, err = ioutil.ReadFile(*s.cfg.SSHKeyFile) if err != nil { - log.Fatalf("read key file ERR: %s", err) + err = fmt.Errorf("read key file ERR: %s", err) + return } if *s.cfg.SSHKeyFileSalt != "" { SSHSigner, err = ssh.ParsePrivateKeyWithPassphrase(s.cfg.SSHKeyBytes, []byte(*s.cfg.SSHKeyFileSalt)) @@ -80,24 +79,26 @@ func (s *Socks) CheckArgs() { SSHSigner, err = ssh.ParsePrivateKey(s.cfg.SSHKeyBytes) } if err != nil { - log.Fatalf("parse ssh private key fail,ERR: %s", err) + err = fmt.Errorf("parse ssh private key fail,ERR: %s", err) + return } s.cfg.SSHAuthMethod = ssh.PublicKeys(SSHSigner) } } } - + return } -func (s *Socks) InitService() { +func (s *Socks) InitService() (err error) { s.InitBasicAuth() if *s.cfg.DNSAddress != "" { (*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL) } s.checker = utils.NewChecker(*s.cfg.Timeout, int64(*s.cfg.Interval), *s.cfg.Blocked, *s.cfg.Direct) if *s.cfg.ParentType == "ssh" { - err := s.ConnectSSH() - if err != nil { - log.Fatalf("init service fail, ERR: %s", err) + e := s.ConnectSSH() + if e != nil { + err = fmt.Errorf("init service fail, ERR: %s", e) + return } go func() { //循环检查ssh网络连通性 @@ -125,12 +126,14 @@ func (s *Socks) InitService() { log.Println("warn: socks udp not suppored for ssh") } else { 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) + e := s.udpSC.ListenUDP(s.udpCallback) + if e != nil { + err = fmt.Errorf("init udp service fail, ERR: %s", e) + return } log.Printf("udp socks proxy on %s", s.udpSC.UDPListener.LocalAddr()) } + return } func (s *Socks) StopService() { if s.sshClient != nil { @@ -143,8 +146,12 @@ func (s *Socks) StopService() { func (s *Socks) Start(args interface{}) (err error) { //start() s.cfg = args.(SocksArgs) - s.CheckArgs() - s.InitService() + if err = s.CheckArgs(); err != nil { + return + } + if err = s.InitService(); err != nil { + s.InitService() + } if *s.cfg.Parent != "" { log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent) } diff --git a/services/sps.go b/services/sps.go index 8d687cc..a169aaa 100644 --- a/services/sps.go +++ b/services/sps.go @@ -30,30 +30,37 @@ func NewSPS() Service { basicAuth: utils.BasicAuth{}, } } -func (s *SPS) CheckArgs() { +func (s *SPS) CheckArgs() (err error) { if *s.cfg.Parent == "" { - log.Fatalf("parent required for %s %s", s.cfg.Protocol(), *s.cfg.Local) + err = fmt.Errorf("parent required for %s %s", s.cfg.Protocol(), *s.cfg.Local) + return } if *s.cfg.ParentType == "" { - log.Fatalf("parent type unkown,use -T ") + err = fmt.Errorf("parent type unkown,use -T ") + return } if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS { - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if err != nil { + return + } if *s.cfg.CaCertFile != "" { - var err error s.cfg.CaCertBytes, err = ioutil.ReadFile(*s.cfg.CaCertFile) if err != nil { - log.Fatalf("read ca file error,ERR:%s", err) + err = fmt.Errorf("read ca file error,ERR:%s", err) + return } } } + return } -func (s *SPS) InitService() { +func (s *SPS) InitService() (err error) { s.InitOutConnPool() if *s.cfg.DNSAddress != "" { (*s).domainResolver = utils.NewDomainResolver(*s.cfg.DNSAddress, *s.cfg.DNSTTL) } - s.InitBasicAuth() + err = s.InitBasicAuth() + return } func (s *SPS) InitOutConnPool() { if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP || *s.cfg.ParentType == TYPE_KCP { @@ -79,10 +86,13 @@ func (s *SPS) StopService() { } func (s *SPS) Start(args interface{}) (err error) { s.cfg = args.(SPSArgs) - s.CheckArgs() + if err = s.CheckArgs(); err != nil { + return + } + if err = s.InitService(); err != nil { + return + } log.Printf("use %s %s parent %s", *s.cfg.ParentType, *s.cfg.ParentServiceType, *s.cfg.Parent) - s.InitService() - for _, addr := range strings.Split(*s.cfg.Local, ",") { if addr != "" { host, port, _ := net.SplitHostPort(*s.cfg.Local) diff --git a/services/tcp.go b/services/tcp.go index 34df8ee..edf0689 100644 --- a/services/tcp.go +++ b/services/tcp.go @@ -24,19 +24,26 @@ func NewTCP() Service { cfg: TCPArgs{}, } } -func (s *TCP) CheckArgs() { +func (s *TCP) CheckArgs() (err error) { if *s.cfg.Parent == "" { - log.Fatalf("parent required for %s %s", s.cfg.Protocol(), *s.cfg.Local) + err = fmt.Errorf("parent required for %s %s", s.cfg.Protocol(), *s.cfg.Local) + return } if *s.cfg.ParentType == "" { - log.Fatalf("parent type unkown,use -T ") + err = fmt.Errorf("parent type unkown,use -T ") + return } if *s.cfg.ParentType == TYPE_TLS || *s.cfg.LocalType == TYPE_TLS { - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if err != nil { + return + } } + return } -func (s *TCP) InitService() { +func (s *TCP) InitService() (err error) { s.InitOutConnPool() + return } func (s *TCP) StopService() { if s.outPool.Pool != nil { @@ -45,10 +52,13 @@ func (s *TCP) StopService() { } func (s *TCP) Start(args interface{}) (err error) { s.cfg = args.(TCPArgs) - s.CheckArgs() + if err = s.CheckArgs(); err != nil { + return + } + if err = s.InitService(); err != nil { + return + } log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent) - s.InitService() - host, port, _ := net.SplitHostPort(*s.cfg.Local) p, _ := strconv.Atoi(port) sc := utils.NewServerChannel(host, p) diff --git a/services/tunnel_bridge.go b/services/tunnel_bridge.go index ddae0a7..f7122b3 100644 --- a/services/tunnel_bridge.go +++ b/services/tunnel_bridge.go @@ -2,6 +2,7 @@ package services import ( "bufio" + "fmt" "log" "net" "snail007/proxy/utils" @@ -31,22 +32,28 @@ func NewTunnelBridge() Service { } } -func (s *TunnelBridge) InitService() { - +func (s *TunnelBridge) InitService() (err error) { + return } -func (s *TunnelBridge) CheckArgs() { +func (s *TunnelBridge) CheckArgs() (err error) { if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { - log.Fatalf("cert and key file required") + err = fmt.Errorf("cert and key file required") + return } - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + return } func (s *TunnelBridge) StopService() { } func (s *TunnelBridge) Start(args interface{}) (err error) { s.cfg = args.(TunnelBridgeArgs) - s.CheckArgs() - s.InitService() + if err = s.CheckArgs(); err != nil { + return + } + if err = s.InitService(); err != nil { + return + } host, port, _ := net.SplitHostPort(*s.cfg.Local) p, _ := strconv.Atoi(port) sc := utils.NewServerChannel(host, p) @@ -135,102 +142,6 @@ func (s *TunnelBridge) Start(args interface{}) (err error) { } s.clientControlConns.Set(key, &inConn) log.Printf("set client %s control conn", key) - - // case CONN_SERVER_HEARBEAT: - // var serverID string - // err = utils.ReadPacketData(reader, &serverID) - // if err != nil { - // log.Printf("read error,ERR:%s", err) - // return - // } - // log.Printf("server heartbeat connection, id: %s", serverID) - // writeDie := make(chan bool) - // readDie := make(chan bool) - // go func() { - // for { - // inConn.SetWriteDeadline(time.Now().Add(time.Second * 3)) - // _, err = inConn.Write([]byte{0x00}) - // inConn.SetWriteDeadline(time.Time{}) - // if err != nil { - // log.Printf("server heartbeat connection write err %s", err) - // break - // } - // time.Sleep(time.Second * 3) - // } - // close(writeDie) - // }() - // go func() { - // for { - // signal := make([]byte, 1) - // inConn.SetReadDeadline(time.Now().Add(time.Second * 6)) - // _, err := inConn.Read(signal) - // inConn.SetReadDeadline(time.Time{}) - // if err != nil { - // log.Printf("server heartbeat connection read err: %s", err) - // break - // } else { - // // log.Printf("heartbeat from server ,id:%s", serverID) - // } - // } - // close(readDie) - // }() - // select { - // case <-readDie: - // case <-writeDie: - // } - // utils.CloseConn(&inConn) - // s.cmServer.Remove(serverID) - // log.Printf("server heartbeat conn %s released", serverID) - // case CONN_CLIENT_HEARBEAT: - // var clientID string - // err = utils.ReadPacketData(reader, &clientID) - // if err != nil { - // log.Printf("read error,ERR:%s", err) - // return - // } - // log.Printf("client heartbeat connection, id: %s", clientID) - // writeDie := make(chan bool) - // readDie := make(chan bool) - // go func() { - // for { - // inConn.SetWriteDeadline(time.Now().Add(time.Second * 3)) - // _, err = inConn.Write([]byte{0x00}) - // inConn.SetWriteDeadline(time.Time{}) - // if err != nil { - // log.Printf("client heartbeat connection write err %s", err) - // break - // } - // time.Sleep(time.Second * 3) - // } - // close(writeDie) - // }() - // go func() { - // for { - // signal := make([]byte, 1) - // inConn.SetReadDeadline(time.Now().Add(time.Second * 6)) - // _, err := inConn.Read(signal) - // inConn.SetReadDeadline(time.Time{}) - // if err != nil { - // log.Printf("client control connection read err: %s", err) - // break - // } else { - // // log.Printf("heartbeat from client ,id:%s", clientID) - // } - // } - // close(readDie) - // }() - // select { - // case <-readDie: - // case <-writeDie: - // } - // utils.CloseConn(&inConn) - // s.cmClient.Remove(clientID) - // if s.clientControlConns.Has(clientID) { - // item, _ := s.clientControlConns.Get(clientID) - // (*item.(*net.Conn)).Close() - // } - // s.clientControlConns.Remove(clientID) - // log.Printf("client heartbeat conn %s released", clientID) } }) if err != nil { diff --git a/services/tunnel_client.go b/services/tunnel_client.go index 0d93483..012756f 100644 --- a/services/tunnel_client.go +++ b/services/tunnel_client.go @@ -23,86 +23,35 @@ func NewTunnelClient() Service { } } -func (s *TunnelClient) InitService() { - // s.InitHeartbeatDeamon() +func (s *TunnelClient) InitService() (err error) { + return } -// func (s *TunnelClient) InitHeartbeatDeamon() { -// log.Printf("heartbeat started") -// go func() { -// var heartbeatConn net.Conn -// var ID = *s.cfg.Key -// for { - -// //close all connection -// s.cm.RemoveAll() -// if s.ctrlConn != nil { -// s.ctrlConn.Close() -// } -// utils.CloseConn(&heartbeatConn) -// heartbeatConn, err := s.GetInConn(CONN_CLIENT_HEARBEAT, ID) -// if err != nil { -// log.Printf("heartbeat connection err: %s, retrying...", err) -// time.Sleep(time.Second * 3) -// utils.CloseConn(&heartbeatConn) -// continue -// } -// log.Printf("heartbeat connection created,id:%s", ID) -// writeDie := make(chan bool) -// readDie := make(chan bool) -// go func() { -// for { -// heartbeatConn.SetWriteDeadline(time.Now().Add(time.Second * 3)) -// _, err = heartbeatConn.Write([]byte{0x00}) -// heartbeatConn.SetWriteDeadline(time.Time{}) -// if err != nil { -// log.Printf("heartbeat connection write err %s", err) -// break -// } -// time.Sleep(time.Second * 3) -// } -// close(writeDie) -// }() -// go func() { -// for { -// signal := make([]byte, 1) -// heartbeatConn.SetReadDeadline(time.Now().Add(time.Second * 6)) -// _, err := heartbeatConn.Read(signal) -// heartbeatConn.SetReadDeadline(time.Time{}) -// if err != nil { -// log.Printf("heartbeat connection read err: %s", err) -// break -// } else { -// //log.Printf("heartbeat from bridge") -// } -// } -// close(readDie) -// }() -// select { -// case <-readDie: -// case <-writeDie: -// } -// } -// }() -// } -func (s *TunnelClient) CheckArgs() { +func (s *TunnelClient) CheckArgs() (err error) { if *s.cfg.Parent != "" { log.Printf("use tls parent %s", *s.cfg.Parent) } else { - log.Fatalf("parent required") + err = fmt.Errorf("parent required") + return } if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { - log.Fatalf("cert and key file required") + err = fmt.Errorf("cert and key file required") + return } - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + return } func (s *TunnelClient) StopService() { // s.cm.RemoveAll() } func (s *TunnelClient) Start(args interface{}) (err error) { s.cfg = args.(TunnelClientArgs) - s.CheckArgs() - s.InitService() + if err = s.CheckArgs(); err != nil { + return + } + if err = s.InitService(); err != nil { + return + } log.Printf("proxy on tunnel client mode") for { diff --git a/services/tunnel_server.go b/services/tunnel_server.go index b4fbfa0..e3ed0cf 100644 --- a/services/tunnel_server.go +++ b/services/tunnel_server.go @@ -37,14 +37,19 @@ func NewTunnelServerManager() Service { } func (s *TunnelServerManager) Start(args interface{}) (err error) { s.cfg = args.(TunnelServerArgs) - s.CheckArgs() + if err = s.CheckArgs(); err != nil { + return + } if *s.cfg.Parent != "" { log.Printf("use tls parent %s", *s.cfg.Parent) } else { - log.Fatalf("parent required") + err = fmt.Errorf("parent required") + return } - s.InitService() + if err = s.InitService(); err != nil { + return + } log.Printf("server id: %s", s.serverID) //log.Printf("route:%v", *s.cfg.Route) @@ -93,70 +98,18 @@ func (s *TunnelServerManager) Clean() { func (s *TunnelServerManager) StopService() { // s.cm.RemoveAll() } -func (s *TunnelServerManager) CheckArgs() { +func (s *TunnelServerManager) CheckArgs() (err error) { if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { - log.Fatalf("cert and key file required") + err = fmt.Errorf("cert and key file required") + return } - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + return } -func (s *TunnelServerManager) InitService() { - // s.InitHeartbeatDeamon() +func (s *TunnelServerManager) InitService() (err error) { + return } -// func (s *TunnelServerManager) InitHeartbeatDeamon() { -// log.Printf("heartbeat started") -// go func() { -// var heartbeatConn net.Conn -// var ID string -// for { -// //close all connection -// s.cm.Remove(ID) -// utils.CloseConn(&heartbeatConn) -// heartbeatConn, ID, err := s.GetOutConn(CONN_SERVER_HEARBEAT) -// if err != nil { -// log.Printf("heartbeat connection err: %s, retrying...", err) -// time.Sleep(time.Second * 3) -// utils.CloseConn(&heartbeatConn) -// continue -// } -// log.Printf("heartbeat connection created,id:%s", ID) -// writeDie := make(chan bool) -// readDie := make(chan bool) -// go func() { -// for { -// heartbeatConn.SetWriteDeadline(time.Now().Add(time.Second * 3)) -// _, err = heartbeatConn.Write([]byte{0x00}) -// heartbeatConn.SetWriteDeadline(time.Time{}) -// if err != nil { -// log.Printf("heartbeat connection write err %s", err) -// break -// } -// time.Sleep(time.Second * 3) -// } -// close(writeDie) -// }() -// go func() { -// for { -// signal := make([]byte, 1) -// heartbeatConn.SetReadDeadline(time.Now().Add(time.Second * 6)) -// _, err := heartbeatConn.Read(signal) -// heartbeatConn.SetReadDeadline(time.Time{}) -// if err != nil { -// log.Printf("heartbeat connection read err: %s", err) -// break -// } else { -// // log.Printf("heartbeat from bridge") -// } -// } -// close(readDie) -// }() -// select { -// case <-readDie: -// case <-writeDie: -// } -// } -// }() -// } func (s *TunnelServerManager) GetOutConn(typ uint8) (outConn net.Conn, ID string, err error) { outConn, err = s.GetConn() if err != nil { @@ -193,19 +146,26 @@ type UDPItem struct { srcAddr *net.UDPAddr } -func (s *TunnelServer) InitService() { +func (s *TunnelServer) InitService() (err error) { s.UDPConnDeamon() + return } -func (s *TunnelServer) CheckArgs() { +func (s *TunnelServer) CheckArgs() (err error) { if *s.cfg.Remote == "" { - log.Fatalf("remote required") + err = fmt.Errorf("remote required") + return } + return } func (s *TunnelServer) Start(args interface{}) (err error) { s.cfg = args.(TunnelServerArgs) - s.CheckArgs() - s.InitService() + if err = s.CheckArgs(); err != nil { + return + } + if err = s.InitService(); err != nil { + return + } host, port, _ := net.SplitHostPort(*s.cfg.Local) p, _ := strconv.Atoi(port) s.sc = utils.NewServerChannel(host, p) diff --git a/services/udp.go b/services/udp.go index 74951fb..400b252 100644 --- a/services/udp.go +++ b/services/udp.go @@ -28,21 +28,28 @@ func NewUDP() Service { p: utils.NewConcurrentMap(), } } -func (s *UDP) CheckArgs() { +func (s *UDP) CheckArgs() (err error) { if *s.cfg.Parent == "" { - log.Fatalf("parent required for udp %s", *s.cfg.Local) + err = fmt.Errorf("parent required for udp %s", *s.cfg.Local) + return } if *s.cfg.ParentType == "" { - log.Fatalf("parent type unkown,use -T ") + err = fmt.Errorf("parent type unkown,use -T ") + return } if *s.cfg.ParentType == "tls" { - s.cfg.CertBytes, s.cfg.KeyBytes = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + s.cfg.CertBytes, s.cfg.KeyBytes, err = utils.TlsBytes(*s.cfg.CertFile, *s.cfg.KeyFile) + if err != nil { + return + } } + return } -func (s *UDP) InitService() { +func (s *UDP) InitService() (err error) { if *s.cfg.ParentType != TYPE_UDP { s.InitOutConnPool() } + return } func (s *UDP) StopService() { if s.outPool.Pool != nil { @@ -51,10 +58,13 @@ func (s *UDP) StopService() { } func (s *UDP) Start(args interface{}) (err error) { s.cfg = args.(UDPArgs) - s.CheckArgs() + if err = s.CheckArgs(); err != nil { + return + } log.Printf("use %s parent %s", *s.cfg.ParentType, *s.cfg.Parent) - s.InitService() - + if err = s.InitService(); err != nil { + return + } host, port, _ := net.SplitHostPort(*s.cfg.Local) p, _ := strconv.Atoi(port) sc := utils.NewServerChannel(host, p) diff --git a/utils/functions.go b/utils/functions.go index cc30c33..9429a40 100755 --- a/utils/functions.go +++ b/utils/functions.go @@ -524,15 +524,15 @@ func SubBytes(bytes []byte, start, end int) []byte { } return bytes[start:end] } -func TlsBytes(cert, key string) (certBytes, keyBytes []byte) { - certBytes, err := ioutil.ReadFile(cert) +func TlsBytes(cert, key string) (certBytes, keyBytes []byte, err error) { + certBytes, err = ioutil.ReadFile(cert) if err != nil { - log.Fatalf("err : %s", err) + err = fmt.Errorf("err : %s", err) return } keyBytes, err = ioutil.ReadFile(key) if err != nil { - log.Fatalf("err : %s", err) + err = fmt.Errorf("err : %s", err) return } return From 2086966a896126bebe337c3014e5ada3b90a35a5 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Mon, 9 Apr 2018 16:21:27 +0800 Subject: [PATCH 41/76] Signed-off-by: arraykeys@gmail.com --- sdk/release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/release.sh b/sdk/release.sh index 09b7b00..a30863f 100755 --- a/sdk/release.sh +++ b/sdk/release.sh @@ -1,6 +1,6 @@ #/bin/bash VER="v4.7" -rm -rf release-* +rm -rf proxy-sdk-release-* #arm gomobile bind -v -target=android/arm mkdir proxy-sdk-arm @@ -38,4 +38,4 @@ tar zcfv proxy-sdk-all-${VER}.tar.gz proxy-sdk-all rm -rf proxy-sdk-all mkdir proxy-sdk-release-${VER} mv *.tar.gz proxy-sdk-release-${VER} -echo "done." \ No newline at end of file +echo "done." From 68deae6bf80b8243e6d02286b5a95bf7422b6b74 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 10 Apr 2018 18:32:17 +0800 Subject: [PATCH 42/76] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9C=8D=E5=8A=A1stop?= =?UTF-8?q?=E6=96=B9=E6=B3=95,=E6=96=B9=E4=BE=BFsdk=E5=BC=80=E5=8F=91.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 6 +- sdk/sdk.go | 35 ++++-- services/http.go | 55 +++++++--- services/mux_bridge.go | 40 ++++++- services/mux_client.go | 50 ++++++++- services/mux_server.go | 64 ++++++++++- services/service.go | 6 ++ services/socks.go | 25 ++++- services/sps.go | 36 ++++--- services/tcp.go | 35 ++++-- services/tunnel_bridge.go | 25 ++++- services/tunnel_client.go | 35 +++++- services/tunnel_server.go | 63 +++++++++-- services/udp.go | 29 +++-- utils/pool.go | 145 ------------------------- utils/structs.go | 216 +++----------------------------------- 16 files changed, 429 insertions(+), 436 deletions(-) delete mode 100755 utils/pool.go diff --git a/CHANGELOG b/CHANGELOG index ba7a176..d4c7c1e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,11 @@ proxy更新日志 v4.7 1.优化了bridge的日志,增加了client和server的掉线日志. 2.优化了sps读取http(s)代理响应的缓冲大小,同时优化了CONNECT请求, -避免了某些代理服务器返回过多数据导致不能正常通讯的问题. + 避免了某些代理服务器返回过多数据导致不能正常通讯的问题. +3.去除了鸡肋连接池功能. +4.增加了gomobile sdk,对安卓/IOS提供支持. +5.优化了所有服务代码,方便对sdk提供支持. + v4.6 diff --git a/sdk/sdk.go b/sdk/sdk.go index cb37165..ee881f4 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -299,17 +299,30 @@ func Start(argsStr string) (errStr string) { } //regist services and run service - services.Regist("http", services.NewHTTP(), httpArgs) - services.Regist("tcp", services.NewTCP(), tcpArgs) - services.Regist("udp", services.NewUDP(), udpArgs) - services.Regist("tserver", services.NewTunnelServerManager(), tunnelServerArgs) - services.Regist("tclient", services.NewTunnelClient(), tunnelClientArgs) - services.Regist("tbridge", services.NewTunnelBridge(), tunnelBridgeArgs) - services.Regist("server", services.NewMuxServerManager(), muxServerArgs) - services.Regist("client", services.NewMuxClient(), muxClientArgs) - services.Regist("bridge", services.NewMuxBridge(), muxBridgeArgs) - services.Regist("socks", services.NewSocks(), socksArgs) - services.Regist("sps", services.NewSPS(), spsArgs) + switch serviceName { + case "http": + services.Regist("http", services.NewHTTP(), httpArgs) + case "tcp": + services.Regist("tcp", services.NewTCP(), tcpArgs) + case "udp": + services.Regist("udp", services.NewUDP(), udpArgs) + case "tserver": + services.Regist("tserver", services.NewTunnelServerManager(), tunnelServerArgs) + case "tclient": + services.Regist("tclient", services.NewTunnelClient(), tunnelClientArgs) + case "tbridge": + services.Regist("tbridge", services.NewTunnelBridge(), tunnelBridgeArgs) + case "server": + services.Regist("server", services.NewMuxServerManager(), muxServerArgs) + case "client": + services.Regist("client", services.NewMuxClient(), muxClientArgs) + case "bridge": + services.Regist("bridge", services.NewMuxBridge(), muxBridgeArgs) + case "socks": + services.Regist("socks", services.NewSocks(), socksArgs) + case "sps": + services.Regist("sps", services.NewSPS(), spsArgs) + } service, err = services.Run(serviceName) if err != nil { diff --git a/services/http.go b/services/http.go index 62fb269..c4da7fd 100644 --- a/services/http.go +++ b/services/http.go @@ -16,22 +16,26 @@ import ( ) type HTTP struct { - outPool utils.OutPool + outPool utils.OutConn cfg HTTPArgs checker utils.Checker basicAuth utils.BasicAuth sshClient *ssh.Client lockChn chan bool domainResolver utils.DomainResolver + isStop bool + serverChannels []*utils.ServerChannel } func NewHTTP() Service { return &HTTP{ - outPool: utils.OutPool{}, - cfg: HTTPArgs{}, - checker: utils.Checker{}, - basicAuth: utils.BasicAuth{}, - lockChn: make(chan bool, 1), + outPool: utils.OutConn{}, + cfg: HTTPArgs{}, + checker: utils.Checker{}, + basicAuth: utils.BasicAuth{}, + lockChn: make(chan bool, 1), + isStop: false, + serverChannels: []*utils.ServerChannel{}, } } func (s *HTTP) CheckArgs() (err error) { @@ -102,6 +106,9 @@ func (s *HTTP) InitService() (err error) { go func() { //循环检查ssh网络连通性 for { + if s.isStop { + return + } conn, err := utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout*2) if err == nil { conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) @@ -127,8 +134,26 @@ func (s *HTTP) InitService() (err error) { return } func (s *HTTP) StopService() { - if s.outPool.Pool != nil { - s.outPool.Pool.ReleaseAll() + defer func() { + e := recover() + if e != nil { + log.Printf("stop http(s) service crashed,%s", e) + } else { + log.Printf("service http(s) stoped,%s", e) + } + }() + s.isStop = true + s.checker.Stop() + if s.sshClient != nil { + s.sshClient.Close() + } + for _, sc := range s.serverChannels { + if sc.Listener != nil && *sc.Listener != nil { + (*sc.Listener).Close() + } + if sc.UDPListener != nil { + (*sc.UDPListener).Close() + } } } func (s *HTTP) Start(args interface{}) (err error) { @@ -159,6 +184,7 @@ func (s *HTTP) Start(args interface{}) (err error) { return } log.Printf("%s http(s) proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr()) + s.serverChannels = append(s.serverChannels, &sc) } } return @@ -224,19 +250,18 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut return } var outConn net.Conn - var _outConn interface{} tryCount := 0 maxTryCount := 5 for { + if s.isStop { + return + } if useProxy { 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) - } + outConn, err = s.outPool.Get() } } else { outConn, err = utils.ConnectHost(s.Resolve(address), *s.cfg.Timeout) @@ -283,7 +308,7 @@ func (s *HTTP) getSSHConn(host string) (outConn net.Conn, err interface{}) { maxTryCount := 1 tryCount := 0 RETRY: - if tryCount >= maxTryCount { + if tryCount >= maxTryCount || s.isStop { return } wait := make(chan bool, 1) @@ -340,7 +365,7 @@ func (s *HTTP) InitOutConnPool() { 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.outPool = utils.NewOutConn( *s.cfg.CheckParentInterval, *s.cfg.ParentType, s.cfg.KCP, diff --git a/services/mux_bridge.go b/services/mux_bridge.go index ee4afa6..e0f8fe7 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -21,6 +21,8 @@ type MuxBridge struct { clientControlConns utils.ConcurrentMap router utils.ClientKeyRouter l *sync.Mutex + isStop bool + sc *utils.ServerChannel } func NewMuxBridge() Service { @@ -28,6 +30,7 @@ func NewMuxBridge() Service { cfg: MuxBridgeArgs{}, clientControlConns: utils.NewConcurrentMap(), l: &sync.Mutex{}, + isStop: false, } b.router = utils.NewClientKeyRouter(&b.clientControlConns, 50000) return b @@ -50,7 +53,23 @@ func (s *MuxBridge) CheckArgs() (err error) { return } func (s *MuxBridge) StopService() { - + defer func() { + e := recover() + if e != nil { + log.Printf("stop bridge service crashed,%s", e) + } else { + log.Printf("service bridge stoped,%s", e) + } + }() + s.isStop = true + if s.sc != nil && (*s.sc).Listener != nil { + (*(*s.sc).Listener).Close() + } + for _, g := range s.clientControlConns.Items() { + for _, session := range g.(utils.ConcurrentMap).Items() { + (session.(*smux.Session)).Close() + } + } } func (s *MuxBridge) Start(args interface{}) (err error) { s.cfg = args.(MuxBridgeArgs) @@ -74,6 +93,7 @@ func (s *MuxBridge) Start(args interface{}) (err error) { if err != nil { return } + s.sc = &sc log.Printf("%s bridge on %s", *s.cfg.LocalType, (*sc.Listener).Addr()) return } @@ -111,6 +131,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { return } for { + if s.isStop { + return + } stream, err := session.AcceptStream() if err != nil { session.Close() @@ -118,7 +141,14 @@ func (s *MuxBridge) handler(inConn net.Conn) { log.Printf("server connection %s %s released", serverID, key) return } - go s.callback(stream, serverID, key) + go func() { + defer func() { + if e := recover(); e != nil { + log.Printf("bridge callback crashed,err: %s", e) + } + }() + s.callback(stream, serverID, key) + }() } case CONN_CLIENT: log.Printf("client connection %s connected", key) @@ -151,6 +181,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { // s.clientControlConns.Set(key, session) go func() { for { + if s.isStop { + return + } if session.IsClosed() { s.l.Lock() defer s.l.Unlock() @@ -173,6 +206,9 @@ func (s *MuxBridge) handler(inConn net.Conn) { func (s *MuxBridge) callback(inConn net.Conn, serverID, key string) { try := 20 for { + if s.isStop { + return + } try-- if try == 0 { break diff --git a/services/mux_client.go b/services/mux_client.go index 2ea91ed..b233598 100644 --- a/services/mux_client.go +++ b/services/mux_client.go @@ -14,12 +14,16 @@ import ( ) type MuxClient struct { - cfg MuxClientArgs + cfg MuxClientArgs + isStop bool + sessions utils.ConcurrentMap } func NewMuxClient() Service { return &MuxClient{ - cfg: MuxClientArgs{}, + cfg: MuxClientArgs{}, + isStop: false, + sessions: utils.NewConcurrentMap(), } } @@ -47,7 +51,18 @@ func (s *MuxClient) CheckArgs() (err error) { return } func (s *MuxClient) StopService() { - + defer func() { + e := recover() + if e != nil { + log.Printf("stop client service crashed,%s", e) + } else { + log.Printf("service client stoped,%s", e) + } + }() + s.isStop = true + for _, sess := range s.sessions.Items() { + sess.(*smux.Session).Close() + } } func (s *MuxClient) Start(args interface{}) (err error) { s.cfg = args.(MuxClientArgs) @@ -63,7 +78,8 @@ func (s *MuxClient) Start(args interface{}) (err error) { count = *s.cfg.SessionCount } for i := 1; i <= count; i++ { - log.Printf("session worker[%d] started", i) + key := fmt.Sprintf("worker[%d]", i) + log.Printf("session %s started", key) go func(i int) { defer func() { e := recover() @@ -72,6 +88,9 @@ func (s *MuxClient) Start(args interface{}) (err error) { } }() for { + if s.isStop { + return + } conn, err := s.getParentConn() if err != nil { log.Printf("connection err: %s, retrying...", err) @@ -94,7 +113,14 @@ func (s *MuxClient) Start(args interface{}) (err error) { time.Sleep(time.Second * 3) continue } + if _sess, ok := s.sessions.Get(key); ok { + _sess.(*smux.Session).Close() + } + s.sessions.Set(key, session) for { + if s.isStop { + return + } stream, err := session.AcceptStream() if err != nil { log.Printf("accept stream err: %s, retrying...", err) @@ -153,6 +179,9 @@ func (s *MuxClient) getParentConn() (conn net.Conn, err error) { func (s *MuxClient) ServeUDP(inConn *smux.Stream, localAddr, ID string) { for { + if s.isStop { + return + } inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) srcAddr, body, err := utils.ReadUDPPacket(inConn) inConn.SetDeadline(time.Time{}) @@ -163,7 +192,15 @@ func (s *MuxClient) ServeUDP(inConn *smux.Stream, localAddr, ID string) { break } else { //log.Printf("udp packet revecived:%s,%v", srcAddr, body) - go s.processUDPPacket(inConn, srcAddr, localAddr, body) + go func() { + defer func() { + if e := recover(); e != nil { + log.Printf("client processUDPPacket crashed,err: %s", e) + } + }() + s.processUDPPacket(inConn, srcAddr, localAddr, body) + }() + } } @@ -216,6 +253,9 @@ func (s *MuxClient) ServeConn(inConn *smux.Stream, localAddr, ID string) { var outConn net.Conn i := 0 for { + if s.isStop { + return + } i++ outConn, err = utils.ConnectHost(localAddr, *s.cfg.Timeout) if err == nil || i == 3 { diff --git a/services/mux_server.go b/services/mux_server.go index 2ffc6b7..27dde50 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -23,13 +23,15 @@ type MuxServer struct { sc utils.ServerChannel sessions utils.ConcurrentMap lockChn chan bool + isStop bool + udpConn *net.Conn } type MuxServerManager struct { cfg MuxServerArgs udpChn chan MuxUDPItem - sc utils.ServerChannel serverID string + servers []*Service } func NewMuxServerManager() Service { @@ -37,8 +39,10 @@ func NewMuxServerManager() Service { cfg: MuxServerArgs{}, udpChn: make(chan MuxUDPItem, 50000), serverID: utils.Uniqueid(), + servers: []*Service{}, } } + func (s *MuxServerManager) Start(args interface{}) (err error) { s.cfg = args.(MuxServerArgs) if err = s.CheckArgs(); err != nil { @@ -100,6 +104,7 @@ func (s *MuxServerManager) Start(args interface{}) (err error) { if err != nil { return } + s.servers = append(s.servers, &server) } return } @@ -107,6 +112,9 @@ func (s *MuxServerManager) Clean() { s.StopService() } func (s *MuxServerManager) StopService() { + for _, server := range s.servers { + (*server).Clean() + } } func (s *MuxServerManager) CheckArgs() (err error) { if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { @@ -131,6 +139,7 @@ func NewMuxServer() Service { udpChn: make(chan MuxUDPItem, 50000), lockChn: make(chan bool, 1), sessions: utils.NewConcurrentMap(), + isStop: false, } } @@ -140,6 +149,29 @@ type MuxUDPItem struct { srcAddr *net.UDPAddr } +func (s *MuxServer) StopService() { + defer func() { + e := recover() + if e != nil { + log.Printf("stop server service crashed,%s", e) + } else { + log.Printf("service server stoped,%s", e) + } + }() + s.isStop = true + for _, sess := range s.sessions.Items() { + sess.(*smux.Session).Close() + } + if s.sc.Listener != nil { + (*s.sc.Listener).Close() + } + if s.sc.UDPListener != nil { + (*s.sc.UDPListener).Close() + } + if s.udpConn != nil { + (*s.udpConn).Close() + } +} func (s *MuxServer) InitService() (err error) { s.UDPConnDeamon() return @@ -185,6 +217,9 @@ func (s *MuxServer) Start(args interface{}) (err error) { var outConn net.Conn var ID string for { + if s.isStop { + return + } outConn, ID, err = s.GetOutConn() if err != nil { utils.CloseConn(&outConn) @@ -228,7 +263,7 @@ func (s *MuxServer) Start(args interface{}) (err error) { return } func (s *MuxServer) Clean() { - + s.StopService() } func (s *MuxServer) GetOutConn() (outConn net.Conn, ID string, err error) { i := 1 @@ -286,10 +321,16 @@ func (s *MuxServer) GetConn(index string) (conn net.Conn, err error) { return } } + if _sess, ok := s.sessions.Get(index); ok { + _sess.(*smux.Session).Close() + } s.sessions.Set(index, session) log.Printf("session[%s] created", index) go func() { for { + if s.isStop { + return + } if session.IsClosed() { s.sessions.Remove(index) break @@ -332,10 +373,19 @@ func (s *MuxServer) UDPConnDeamon() { var ID string var err error for { + if s.isStop { + return + } item := <-s.udpChn RETRY: + if s.isStop { + return + } if outConn == nil { for { + if s.isStop { + return + } outConn, ID, err = s.GetOutConn() if err != nil { outConn = nil @@ -345,10 +395,14 @@ func (s *MuxServer) UDPConnDeamon() { continue } else { go func(outConn net.Conn, ID string) { - go func() { - // outConn.Close() - }() + if s.udpConn != nil { + (*s.udpConn).Close() + } + s.udpConn = &outConn for { + if s.isStop { + return + } outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn) outConn.SetDeadline(time.Time{}) diff --git a/services/service.go b/services/service.go index b133210..8131a68 100644 --- a/services/service.go +++ b/services/service.go @@ -18,12 +18,18 @@ type ServiceItem struct { var servicesMap = map[string]*ServiceItem{} func Regist(name string, s Service, args interface{}) { + servicesMap[name] = &ServiceItem{ S: s, Args: args, Name: name, } } +func Stop(name string) { + if s, ok := servicesMap[name]; ok && s.S != nil { + s.S.Clean() + } +} func Run(name string, args ...interface{}) (service *ServiceItem, err error) { service, ok := servicesMap[name] if ok { diff --git a/services/socks.go b/services/socks.go index 02fcaa6..a79a6c7 100644 --- a/services/socks.go +++ b/services/socks.go @@ -23,7 +23,9 @@ type Socks struct { sshClient *ssh.Client lockChn chan bool udpSC utils.ServerChannel + sc *utils.ServerChannel domainResolver utils.DomainResolver + isStop bool } func NewSocks() Service { @@ -32,6 +34,7 @@ func NewSocks() Service { checker: utils.Checker{}, basicAuth: utils.BasicAuth{}, lockChn: make(chan bool, 1), + isStop: false, } } @@ -103,6 +106,9 @@ func (s *Socks) InitService() (err error) { go func() { //循环检查ssh网络连通性 for { + if s.isStop { + return + } conn, err := utils.ConnectHost(s.Resolve(*s.cfg.Parent), *s.cfg.Timeout*2) if err == nil { conn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) @@ -136,12 +142,25 @@ func (s *Socks) InitService() (err error) { return } func (s *Socks) StopService() { + defer func() { + e := recover() + if e != nil { + log.Printf("stop socks service crashed,%s", e) + } else { + log.Printf("service socks stoped,%s", e) + } + }() + s.isStop = true + s.checker.Stop() if s.sshClient != nil { s.sshClient.Close() } if s.udpSC.UDPListener != nil { s.udpSC.UDPListener.Close() } + if s.sc != nil && (*s.sc).Listener != nil { + (*(*s.sc).Listener).Close() + } } func (s *Socks) Start(args interface{}) (err error) { //start() @@ -166,6 +185,7 @@ func (s *Socks) Start(args interface{}) (err error) { if err != nil { return } + s.sc = &sc log.Printf("%s socks proxy on %s", *s.cfg.LocalType, (*sc.Listener).Addr()) return } @@ -457,6 +477,9 @@ func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, reque return } for { + if s.isStop { + return + } if *s.cfg.Always { outConn, err = s.getOutConn(methodReq.Bytes(), request.Bytes(), request.Addr()) } else { @@ -563,7 +586,7 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n maxTryCount := 1 tryCount := 0 RETRY: - if tryCount >= maxTryCount { + if tryCount >= maxTryCount || s.isStop { return } wait := make(chan bool, 1) diff --git a/services/sps.go b/services/sps.go index a169aaa..ff02bfb 100644 --- a/services/sps.go +++ b/services/sps.go @@ -17,17 +17,19 @@ import ( ) type SPS struct { - outPool utils.OutPool + outPool utils.OutConn cfg SPSArgs domainResolver utils.DomainResolver basicAuth utils.BasicAuth + serverChannels []*utils.ServerChannel } func NewSPS() Service { return &SPS{ - outPool: utils.OutPool{}, - cfg: SPSArgs{}, - basicAuth: utils.BasicAuth{}, + outPool: utils.OutConn{}, + cfg: SPSArgs{}, + basicAuth: utils.BasicAuth{}, + serverChannels: []*utils.ServerChannel{}, } } func (s *SPS) CheckArgs() (err error) { @@ -66,7 +68,7 @@ func (s *SPS) InitOutConnPool() { 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.outPool = utils.NewOutConn( 0, *s.cfg.ParentType, s.cfg.KCP, @@ -80,8 +82,21 @@ func (s *SPS) InitOutConnPool() { } func (s *SPS) StopService() { - if s.outPool.Pool != nil { - s.outPool.Pool.ReleaseAll() + defer func() { + e := recover() + if e != nil { + log.Printf("stop sps service crashed,%s", e) + } else { + log.Printf("service sps stoped,%s", e) + } + }() + for _, sc := range s.serverChannels { + if sc.Listener != nil && *sc.Listener != nil { + (*sc.Listener).Close() + } + if sc.UDPListener != nil { + (*sc.UDPListener).Close() + } } } func (s *SPS) Start(args interface{}) (err error) { @@ -109,6 +124,7 @@ func (s *SPS) Start(args interface{}) (err error) { return } log.Printf("%s http(s)+socks proxy on %s", s.cfg.Protocol(), (*sc.Listener).Addr()) + s.serverChannels = append(s.serverChannels, &sc) } } return @@ -207,11 +223,7 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { } //connect to parent var outConn net.Conn - var _outConn interface{} - _outConn, err = s.outPool.Pool.Get() - if err == nil { - outConn = _outConn.(net.Conn) - } + outConn, err = s.outPool.Get() if err != nil { log.Printf("connect to %s , err:%s", *s.cfg.Parent, err) utils.CloseConn(inConn) diff --git a/services/tcp.go b/services/tcp.go index edf0689..176d289 100644 --- a/services/tcp.go +++ b/services/tcp.go @@ -14,14 +14,17 @@ import ( ) type TCP struct { - outPool utils.OutPool + outPool utils.OutConn cfg TCPArgs + sc *utils.ServerChannel + isStop bool } func NewTCP() Service { return &TCP{ - outPool: utils.OutPool{}, + outPool: utils.OutConn{}, cfg: TCPArgs{}, + isStop: false, } } func (s *TCP) CheckArgs() (err error) { @@ -46,8 +49,20 @@ func (s *TCP) InitService() (err error) { return } func (s *TCP) StopService() { - if s.outPool.Pool != nil { - s.outPool.Pool.ReleaseAll() + defer func() { + e := recover() + if e != nil { + log.Printf("stop tcp service crashed,%s", e) + } else { + log.Printf("service tcp stoped,%s", e) + } + }() + s.isStop = true + if s.sc.Listener != nil && *s.sc.Listener != nil { + (*s.sc.Listener).Close() + } + if s.sc.UDPListener != nil { + (*s.sc.UDPListener).Close() } } func (s *TCP) Start(args interface{}) (err error) { @@ -74,6 +89,7 @@ func (s *TCP) Start(args interface{}) (err error) { return } log.Printf("%s proxy on %s", s.cfg.Protocol(), (*sc.Listener).Addr()) + s.sc = &sc return } @@ -106,11 +122,7 @@ func (s *TCP) callback(inConn net.Conn) { } func (s *TCP) OutToTCP(inConn *net.Conn) (err error) { var outConn net.Conn - var _outConn interface{} - _outConn, err = s.outPool.Pool.Get() - if err == nil { - outConn = _outConn.(net.Conn) - } + outConn, err = s.outPool.Get() if err != nil { log.Printf("connect to %s , err:%s", *s.cfg.Parent, err) utils.CloseConn(inConn) @@ -129,6 +141,9 @@ func (s *TCP) OutToTCP(inConn *net.Conn) (err error) { func (s *TCP) OutToUDP(inConn *net.Conn) (err error) { log.Printf("conn created , remote : %s ", (*inConn).RemoteAddr()) for { + if s.isStop { + return + } srcAddr, body, err := utils.ReadUDPPacket(bufio.NewReader(*inConn)) if err == io.EOF || err == io.ErrUnexpectedEOF { //log.Printf("connection %s released", srcAddr) @@ -178,7 +193,7 @@ func (s *TCP) InitOutConnPool() { 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.outPool = utils.NewOutConn( *s.cfg.CheckParentInterval, *s.cfg.ParentType, s.cfg.KCP, diff --git a/services/tunnel_bridge.go b/services/tunnel_bridge.go index f7122b3..f9c6077 100644 --- a/services/tunnel_bridge.go +++ b/services/tunnel_bridge.go @@ -18,8 +18,7 @@ type TunnelBridge struct { cfg TunnelBridgeArgs serverConns utils.ConcurrentMap clientControlConns utils.ConcurrentMap - // cmServer utils.ConnManager - // cmClient utils.ConnManager + isStop bool } func NewTunnelBridge() Service { @@ -27,8 +26,7 @@ func NewTunnelBridge() Service { cfg: TunnelBridgeArgs{}, serverConns: utils.NewConcurrentMap(), clientControlConns: utils.NewConcurrentMap(), - // cmServer: utils.NewConnManager(), - // cmClient: utils.NewConnManager(), + isStop: false, } } @@ -44,7 +42,21 @@ func (s *TunnelBridge) CheckArgs() (err error) { return } func (s *TunnelBridge) StopService() { - + defer func() { + e := recover() + if e != nil { + log.Printf("stop tbridge service crashed,%s", e) + } else { + log.Printf("service tbridge stoped,%s", e) + } + }() + s.isStop = true + for _, sess := range s.clientControlConns.Items() { + (*sess.(*net.Conn)).Close() + } + for _, sess := range s.serverConns.Items() { + (*sess.(ServerConn).Conn).Close() + } } func (s *TunnelBridge) Start(args interface{}) (err error) { s.cfg = args.(TunnelBridgeArgs) @@ -85,6 +97,9 @@ func (s *TunnelBridge) Start(args interface{}) (err error) { Conn: &inConn, }) for { + if s.isStop { + return + } item, ok := s.clientControlConns.Get(key) if !ok { log.Printf("client %s control conn not exists", key) diff --git a/services/tunnel_client.go b/services/tunnel_client.go index 012756f..4f0f51a 100644 --- a/services/tunnel_client.go +++ b/services/tunnel_client.go @@ -14,12 +14,14 @@ type TunnelClient struct { cfg TunnelClientArgs // cm utils.ConnManager ctrlConn net.Conn + isStop bool } func NewTunnelClient() Service { return &TunnelClient{ cfg: TunnelClientArgs{}, // cm: utils.NewConnManager(), + isStop: false, } } @@ -42,7 +44,18 @@ func (s *TunnelClient) CheckArgs() (err error) { return } func (s *TunnelClient) StopService() { - // s.cm.RemoveAll() + defer func() { + e := recover() + if e != nil { + log.Printf("stop tclient service crashed,%s", e) + } else { + log.Printf("service tclient stoped,%s", e) + } + }() + s.isStop = true + if s.ctrlConn != nil { + s.ctrlConn.Close() + } } func (s *TunnelClient) Start(args interface{}) (err error) { s.cfg = args.(TunnelClientArgs) @@ -55,8 +68,9 @@ func (s *TunnelClient) Start(args interface{}) (err error) { log.Printf("proxy on tunnel client mode") for { - //close all conn - // s.cm.Remove(*s.cfg.Key) + if s.isStop { + return + } if s.ctrlConn != nil { s.ctrlConn.Close() } @@ -71,6 +85,9 @@ func (s *TunnelClient) Start(args interface{}) (err error) { continue } for { + if s.isStop { + return + } var ID, clientLocalAddr, serverID string err = utils.ReadPacketData(s.ctrlConn, &ID, &clientLocalAddr, &serverID) if err != nil { @@ -121,6 +138,9 @@ func (s *TunnelClient) ServeUDP(localAddr, ID, serverID string) { var err error // for { for { + if s.isStop { + return + } // s.cm.RemoveOne(*s.cfg.Key, ID) inConn, err = s.GetInConn(CONN_CLIENT, *s.cfg.Key, ID, serverID) if err != nil { @@ -136,6 +156,9 @@ func (s *TunnelClient) ServeUDP(localAddr, ID, serverID string) { log.Printf("conn %s created", ID) for { + if s.isStop { + return + } srcAddr, body, err := utils.ReadUDPPacket(inConn) if err == io.EOF || err == io.ErrUnexpectedEOF { log.Printf("connection %s released", ID) @@ -192,6 +215,9 @@ func (s *TunnelClient) ServeConn(localAddr, ID, serverID string) { var inConn, outConn net.Conn var err error for { + if s.isStop { + return + } inConn, err = s.GetInConn(CONN_CLIENT, *s.cfg.Key, ID, serverID) if err != nil { utils.CloseConn(&inConn) @@ -205,6 +231,9 @@ func (s *TunnelClient) ServeConn(localAddr, ID, serverID string) { i := 0 for { + if s.isStop { + return + } i++ outConn, err = utils.ConnectHost(localAddr, *s.cfg.Timeout) if err == nil || i == 3 { diff --git a/services/tunnel_server.go b/services/tunnel_server.go index e3ed0cf..d0dcc16 100644 --- a/services/tunnel_server.go +++ b/services/tunnel_server.go @@ -14,17 +14,18 @@ import ( ) type TunnelServer struct { - cfg TunnelServerArgs - udpChn chan UDPItem - sc utils.ServerChannel + cfg TunnelServerArgs + udpChn chan UDPItem + sc utils.ServerChannel + isStop bool + udpConn *net.Conn } type TunnelServerManager struct { cfg TunnelServerArgs udpChn chan UDPItem - sc utils.ServerChannel serverID string - // cm utils.ConnManager + servers []*Service } func NewTunnelServerManager() Service { @@ -32,7 +33,7 @@ func NewTunnelServerManager() Service { cfg: TunnelServerArgs{}, udpChn: make(chan UDPItem, 50000), serverID: utils.Uniqueid(), - // cm: utils.NewConnManager(), + servers: []*Service{}, } } func (s *TunnelServerManager) Start(args interface{}) (err error) { @@ -89,6 +90,7 @@ func (s *TunnelServerManager) Start(args interface{}) (err error) { if err != nil { return } + s.servers = append(s.servers, &server) } return } @@ -96,7 +98,9 @@ func (s *TunnelServerManager) Clean() { s.StopService() } func (s *TunnelServerManager) StopService() { - // s.cm.RemoveAll() + for _, server := range s.servers { + (*server).Clean() + } } func (s *TunnelServerManager) CheckArgs() (err error) { if *s.cfg.CertFile == "" || *s.cfg.KeyFile == "" { @@ -137,6 +141,7 @@ func NewTunnelServer() Service { return &TunnelServer{ cfg: TunnelServerArgs{}, udpChn: make(chan UDPItem, 50000), + isStop: false, } } @@ -146,6 +151,27 @@ type UDPItem struct { srcAddr *net.UDPAddr } +func (s *TunnelServer) StopService() { + defer func() { + e := recover() + if e != nil { + log.Printf("stop server service crashed,%s", e) + } else { + log.Printf("service server stoped,%s", e) + } + }() + s.isStop = true + + if s.sc.Listener != nil { + (*s.sc.Listener).Close() + } + if s.sc.UDPListener != nil { + (*s.sc.UDPListener).Close() + } + if s.udpConn != nil { + (*s.udpConn).Close() + } +} func (s *TunnelServer) InitService() (err error) { s.UDPConnDeamon() return @@ -191,6 +217,9 @@ func (s *TunnelServer) Start(args interface{}) (err error) { var outConn net.Conn var ID string for { + if s.isStop { + return + } outConn, ID, err = s.GetOutConn(CONN_SERVER) if err != nil { utils.CloseConn(&outConn) @@ -259,10 +288,19 @@ func (s *TunnelServer) UDPConnDeamon() { // var cmdChn = make(chan bool, 1000) var err error for { + if s.isStop { + return + } item := <-s.udpChn RETRY: + if s.isStop { + return + } if outConn == nil { for { + if s.isStop { + return + } outConn, ID, err = s.GetOutConn(CONN_SERVER) if err != nil { // cmdChn <- true @@ -273,11 +311,14 @@ func (s *TunnelServer) UDPConnDeamon() { continue } else { go func(outConn net.Conn, ID string) { - go func() { - // <-cmdChn - // outConn.Close() - }() + if s.udpConn != nil { + (*s.udpConn).Close() + } + s.udpConn = &outConn for { + if s.isStop { + return + } srcAddrFromConn, body, err := utils.ReadUDPPacket(outConn) if err == io.EOF || err == io.ErrUnexpectedEOF { log.Printf("UDP deamon connection %s exited", ID) diff --git a/services/udp.go b/services/udp.go index 400b252..09f6aeb 100644 --- a/services/udp.go +++ b/services/udp.go @@ -17,15 +17,17 @@ import ( type UDP struct { p utils.ConcurrentMap - outPool utils.OutPool + outPool utils.OutConn cfg UDPArgs sc *utils.ServerChannel + isStop bool } func NewUDP() Service { return &UDP{ - outPool: utils.OutPool{}, + outPool: utils.OutConn{}, p: utils.NewConcurrentMap(), + isStop: false, } } func (s *UDP) CheckArgs() (err error) { @@ -52,8 +54,20 @@ func (s *UDP) InitService() (err error) { return } func (s *UDP) StopService() { - if s.outPool.Pool != nil { - s.outPool.Pool.ReleaseAll() + defer func() { + e := recover() + if e != nil { + log.Printf("stop udp service crashed,%s", e) + } else { + log.Printf("service udp stoped,%s", e) + } + }() + s.isStop = true + if s.sc.Listener != nil && *s.sc.Listener != nil { + (*s.sc.Listener).Close() + } + if s.sc.UDPListener != nil { + (*s.sc.UDPListener).Close() } } func (s *UDP) Start(args interface{}) (err error) { @@ -105,7 +119,7 @@ func (s *UDP) GetConn(connKey string) (conn net.Conn, isNew bool, err error) { isNew = !s.p.Has(connKey) var _conn interface{} if isNew { - _conn, err = s.outPool.Pool.Get() + _conn, err = s.outPool.Get() if err != nil { return nil, false, err } @@ -138,6 +152,9 @@ func (s *UDP) OutToTCP(packet []byte, localAddr, srcAddr *net.UDPAddr) (err erro }() log.Printf("conn %d created , local: %s", connKey, srcAddr.String()) for { + if s.isStop { + return + } srcAddrFromConn, body, err := utils.ReadUDPPacket(bufio.NewReader(conn)) if err == io.EOF || err == io.ErrUnexpectedEOF { //log.Printf("connection %d released", connKey) @@ -216,7 +233,7 @@ func (s *UDP) InitOutConnPool() { if *s.cfg.ParentType == TYPE_TLS || *s.cfg.ParentType == TYPE_TCP { //dur int, isTLS bool, certBytes, keyBytes []byte, //parent string, timeout int, InitialCap int, MaxCap int - s.outPool = utils.NewOutPool( + s.outPool = utils.NewOutConn( *s.cfg.CheckParentInterval, *s.cfg.ParentType, kcpcfg.KCPConfigArgs{}, diff --git a/utils/pool.go b/utils/pool.go deleted file mode 100755 index ae30f6f..0000000 --- a/utils/pool.go +++ /dev/null @@ -1,145 +0,0 @@ -package utils - -import ( - "log" - "sync" - "time" -) - -//ConnPool to use -type ConnPool interface { - Get() (conn interface{}, err error) - Put(conn interface{}) - ReleaseAll() - Len() (length int) -} -type poolConfig struct { - Factory func() (interface{}, error) - IsActive func(interface{}) bool - Release func(interface{}) - InitialCap int - MaxCap int -} - -func NewConnPool(poolConfig poolConfig) (pool ConnPool, err error) { - p := netPool{ - config: poolConfig, - conns: make(chan interface{}, poolConfig.MaxCap), - lock: &sync.Mutex{}, - } - //log.Printf("pool MaxCap:%d", poolConfig.MaxCap) - if poolConfig.MaxCap > 0 { - err = p.initAutoFill(false) - if err == nil { - p.initAutoFill(true) - } - } - return &p, nil -} - -type netPool struct { - conns chan interface{} - lock *sync.Mutex - config poolConfig -} - -func (p *netPool) initAutoFill(async bool) (err error) { - var worker = func() (err error) { - for { - //log.Printf("pool fill: %v , len: %d", p.Len() <= p.config.InitialCap/2, p.Len()) - if p.Len() <= p.config.InitialCap/2 { - p.lock.Lock() - errN := 0 - for i := 0; i < p.config.InitialCap; i++ { - c, err := p.config.Factory() - if err != nil { - errN++ - if async { - continue - } else { - p.lock.Unlock() - return err - } - } - select { - case p.conns <- c: - default: - p.config.Release(c) - break - } - if p.Len() >= p.config.InitialCap { - break - } - } - if errN > 0 { - log.Printf("fill conn pool fail , ERRN:%d", errN) - } - p.lock.Unlock() - } - if !async { - return - } - time.Sleep(time.Second * 2) - } - } - if async { - go worker() - } else { - err = worker() - } - return - -} - -func (p *netPool) Get() (conn interface{}, err error) { - // defer func() { - // log.Printf("pool len : %d", p.Len()) - // }() - p.lock.Lock() - defer p.lock.Unlock() - // for { - select { - case conn = <-p.conns: - if p.config.IsActive(conn) { - return - } - p.config.Release(conn) - default: - conn, err = p.config.Factory() - if err != nil { - return nil, err - } - return conn, nil - } - // } - return -} - -func (p *netPool) Put(conn interface{}) { - if conn == nil { - return - } - p.lock.Lock() - defer p.lock.Unlock() - if !p.config.IsActive(conn) { - p.config.Release(conn) - } - select { - case p.conns <- conn: - default: - p.config.Release(conn) - } -} -func (p *netPool) ReleaseAll() { - p.lock.Lock() - defer p.lock.Unlock() - close(p.conns) - for c := range p.conns { - p.config.Release(c) - } - p.conns = make(chan interface{}, p.config.InitialCap) - -} -func (p *netPool) Len() (length int) { - return len(p.conns) -} diff --git a/utils/structs.go b/utils/structs.go index e7f3958..6279530 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/tls" "encoding/base64" - "encoding/binary" "errors" "fmt" "io" @@ -28,6 +27,7 @@ type Checker struct { directMap ConcurrentMap interval int64 timeout int + isStop bool } type CheckerItem struct { IsHTTPS bool @@ -48,6 +48,7 @@ func NewChecker(timeout int, interval int64, blockedFile, directFile string) Che data: NewConcurrentMap(), interval: interval, timeout: timeout, + isStop: false, } ch.blockedMap = ch.loadMap(blockedFile) ch.directMap = ch.loadMap(directFile) @@ -81,6 +82,9 @@ func (c *Checker) loadMap(f string) (dataMap ConcurrentMap) { } return } +func (c *Checker) Stop() { + c.isStop = true +} func (c *Checker) start() { go func() { //log.Printf("checker started") @@ -107,6 +111,9 @@ func (c *Checker) start() { }(v.(CheckerItem)) } time.Sleep(time.Second * time.Duration(c.interval)) + if c.isStop { + return + } } }() } @@ -498,8 +505,7 @@ func (req *HTTPRequest) addPortIfNot() (newHost string) { return } -type OutPool struct { - Pool ConnPool +type OutConn struct { dur int typ string certBytes []byte @@ -510,8 +516,8 @@ type OutPool struct { timeout int } -func NewOutPool(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyBytes, caCertBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutPool) { - op = OutPool{ +func NewOutConn(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyBytes, caCertBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutConn) { + return OutConn{ dur: dur, typ: typ, certBytes: certBytes, @@ -521,36 +527,8 @@ func NewOutPool(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyByt address: address, timeout: timeout, } - var err error - op.Pool, err = NewConnPool(poolConfig{ - IsActive: func(conn interface{}) bool { return true }, - Release: func(conn interface{}) { - if conn != nil { - conn.(net.Conn).SetDeadline(time.Now().Add(time.Millisecond)) - conn.(net.Conn).Close() - // log.Println("conn released") - } - }, - InitialCap: InitialCap, - MaxCap: MaxCap, - Factory: func() (conn interface{}, err error) { - conn, err = op.getConn() - return - }, - }) - if err != nil { - log.Fatalf("init conn pool fail ,%s", err) - } else { - if InitialCap > 0 { - log.Printf("init conn pool success") - op.initPoolDeamon() - } else { - log.Printf("conn pool closed") - } - } - return } -func (op *OutPool) getConn() (conn interface{}, err error) { +func (op *OutConn) Get() (conn net.Conn, err error) { if op.typ == "tls" { var _conn tls.Conn _conn, err = TlsConnectHost(op.address, op.timeout, op.certBytes, op.keyBytes, op.caCertBytes) @@ -565,176 +543,6 @@ func (op *OutPool) getConn() (conn interface{}, err error) { return } -func (op *OutPool) initPoolDeamon() { - go func() { - if op.dur <= 0 { - return - } - log.Printf("pool deamon started") - for { - time.Sleep(time.Second * time.Duration(op.dur)) - conn, err := op.getConn() - if err != nil { - log.Printf("pool deamon err %s , release pool", err) - op.Pool.ReleaseAll() - } else { - conn.(net.Conn).SetDeadline(time.Now().Add(time.Millisecond)) - conn.(net.Conn).Close() - } - } - }() -} - -type HeartbeatData struct { - Data []byte - N int - Error error -} -type HeartbeatReadWriter struct { - conn *net.Conn - // rchn chan HeartbeatData - l *sync.Mutex - dur int - errHandler func(err error, hb *HeartbeatReadWriter) - once *sync.Once - datachn chan byte - // rbuf bytes.Buffer - // signal chan bool - rerrchn chan error -} - -func NewHeartbeatReadWriter(conn *net.Conn, dur int, fn func(err error, hb *HeartbeatReadWriter)) (hrw HeartbeatReadWriter) { - hrw = HeartbeatReadWriter{ - conn: conn, - l: &sync.Mutex{}, - dur: dur, - // rchn: make(chan HeartbeatData, 10000), - // signal: make(chan bool, 1), - errHandler: fn, - datachn: make(chan byte, 4*1024), - once: &sync.Once{}, - rerrchn: make(chan error, 1), - // rbuf: bytes.Buffer{}, - } - hrw.heartbeat() - hrw.reader() - return -} - -func (rw *HeartbeatReadWriter) Close() { - CloseConn(rw.conn) -} -func (rw *HeartbeatReadWriter) reader() { - go func() { - //log.Printf("heartbeat read started") - for { - n, data, err := rw.read() - if n == -1 { - continue - } - //log.Printf("n:%d , data:%s ,err:%s", n, string(data), err) - if err == nil { - //fmt.Printf("write data %s\n", string(data)) - for _, b := range data { - rw.datachn <- b - } - } - if err != nil { - //log.Printf("heartbeat reader err: %s", err) - select { - case rw.rerrchn <- err: - default: - } - rw.once.Do(func() { - rw.errHandler(err, rw) - }) - break - } - } - //log.Printf("heartbeat read exited") - }() -} -func (rw *HeartbeatReadWriter) read() (n int, data []byte, err error) { - var typ uint8 - err = binary.Read((*rw.conn), binary.LittleEndian, &typ) - if err != nil { - return - } - if typ == 0 { - // log.Printf("heartbeat revecived") - n = -1 - return - } - var dataLength uint32 - binary.Read((*rw.conn), binary.LittleEndian, &dataLength) - _data := make([]byte, dataLength) - // log.Printf("dataLength:%d , data:%s", dataLength, string(data)) - n, err = (*rw.conn).Read(_data) - //log.Printf("n:%d , data:%s ,err:%s", n, string(data), err) - if err != nil { - return - } - if uint32(n) != dataLength { - err = fmt.Errorf("read short data body") - return - } - data = _data[:n] - return -} -func (rw *HeartbeatReadWriter) heartbeat() { - go func() { - //log.Printf("heartbeat started") - for { - if rw.conn == nil || *rw.conn == nil { - //log.Printf("heartbeat err: conn nil") - break - } - rw.l.Lock() - _, err := (*rw.conn).Write([]byte{0}) - rw.l.Unlock() - if err != nil { - //log.Printf("heartbeat err: %s", err) - rw.once.Do(func() { - rw.errHandler(err, rw) - }) - break - } else { - // log.Printf("heartbeat send ok") - } - time.Sleep(time.Second * time.Duration(rw.dur)) - } - //log.Printf("heartbeat exited") - }() -} -func (rw *HeartbeatReadWriter) Read(p []byte) (n int, err error) { - data := make([]byte, cap(p)) - for i := 0; i < cap(p); i++ { - data[i] = <-rw.datachn - n++ - //fmt.Printf("read %d %v\n", i, data[:n]) - if len(rw.datachn) == 0 { - n = i + 1 - copy(p, data[:n]) - return - } - } - return -} -func (rw *HeartbeatReadWriter) Write(p []byte) (n int, err error) { - defer rw.l.Unlock() - rw.l.Lock() - pkg := new(bytes.Buffer) - binary.Write(pkg, binary.LittleEndian, uint8(1)) - binary.Write(pkg, binary.LittleEndian, uint32(len(p))) - binary.Write(pkg, binary.LittleEndian, p) - bs := pkg.Bytes() - n, err = (*rw.conn).Write(bs) - if err == nil { - n = len(p) - } - return -} - type ConnManager struct { pool ConcurrentMap l *sync.Mutex From 7a1491c7b3b102dc5a17e2168c5d3be7b9b63a29 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 12 Apr 2018 16:32:56 +0800 Subject: [PATCH 43/76] android sdk Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 12 ++-- config.go | 3 - sdk/README.md | 77 ++++++++++++++++++++++ sdk/release.sh | 81 +++++++++++++---------- sdk/sdk.go | 133 ++++++++++++++++++++++++++++++++++---- services/args.go | 3 - services/http.go | 13 ++-- services/mux_bridge.go | 13 +++- services/mux_client.go | 2 +- services/mux_server.go | 2 +- services/service.go | 9 ++- services/socks.go | 12 +++- services/sps.go | 14 +++- services/tcp.go | 29 ++++++--- services/tunnel_bridge.go | 2 +- services/tunnel_client.go | 30 ++++++--- services/tunnel_server.go | 32 +++++---- services/udp.go | 7 +- utils/serve-channel.go | 2 +- utils/structs.go | 2 +- 20 files changed, 368 insertions(+), 110 deletions(-) create mode 100644 sdk/README.md diff --git a/CHANGELOG b/CHANGELOG index d4c7c1e..f412d48 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,13 +1,13 @@ proxy更新日志 v4.7 -1.优化了bridge的日志,增加了client和server的掉线日志. -2.优化了sps读取http(s)代理响应的缓冲大小,同时优化了CONNECT请求, +1.增加了基于gomobile的sdk,对安卓/IOS提供SDK支持. +2.优化了bridge的日志,增加了client和server的掉线日志. +3.优化了sps读取http(s)代理响应的缓冲大小,同时优化了CONNECT请求, 避免了某些代理服务器返回过多数据导致不能正常通讯的问题. -3.去除了鸡肋连接池功能. -4.增加了gomobile sdk,对安卓/IOS提供支持. +4.去除了鸡肋连接池功能. 5.优化了所有服务代码,方便对sdk提供支持. - - +6.增加了SDK手册. +7.增加了GUI客户端(windows/web/android/ios)介绍主页. v4.6 1.sps,http(s),socks5,内网穿透都做了大量的超时优化处理,更加稳定. diff --git a/config.go b/config.go index 70f0a4a..229ae33 100755 --- a/config.go +++ b/config.go @@ -86,7 +86,6 @@ func initConfig() (err error) { httpArgs.Direct = http.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String() httpArgs.AuthFile = http.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String() httpArgs.Auth = http.Flag("auth", "http basic auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings() - httpArgs.PoolSize = http.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() httpArgs.CheckParentInterval = http.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() httpArgs.Local = http.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String() httpArgs.SSHUser = http.Flag("ssh-user", "user for ssh").Short('u').Default("").String() @@ -109,7 +108,6 @@ func initConfig() (err error) { tcpArgs.Timeout = tcp.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('e').Default("2000").Int() tcpArgs.ParentType = tcp.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "udp", "kcp") tcpArgs.LocalType = tcp.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp", "kcp") - tcpArgs.PoolSize = tcp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() tcpArgs.CheckParentInterval = tcp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() tcpArgs.Local = tcp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() @@ -120,7 +118,6 @@ func initConfig() (err error) { udpArgs.KeyFile = udp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() udpArgs.Timeout = udp.Flag("timeout", "tcp timeout milliseconds when connect to parent proxy").Short('t').Default("2000").Int() udpArgs.ParentType = udp.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "udp") - udpArgs.PoolSize = udp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() udpArgs.CheckParentInterval = udp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() udpArgs.Local = udp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() diff --git a/sdk/README.md b/sdk/README.md new file mode 100644 index 0000000..899e048 --- /dev/null +++ b/sdk/README.md @@ -0,0 +1,77 @@ + +# Proxy SDK 使用说明 + +proxy使用gombile实现了一份go代码编译为android和ios平台下面可以直接调用的sdk类库, +基于这些类库,APP开发者可以轻松的开发出各种形式的代理工具. + +### 下面分平台介绍SDK的用法 + +#### Android SDK + +[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy/) [![license](https://img.shields.io/github/license/snail007/goproxy.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy/total.svg?style=plastic)](https://github.com/snail007/goproxy/releases) [![download](https://img.shields.io/github/release/snail007/goproxy.svg?style=plastic)](https://github.com/snail007/goproxy/releases) + +[点击下载Android-SDK](https://github.com/snail007/goproxy-sdk-android/releases) +在Android系统提供的sdk形式是一个后缀为.aar的类库文件,开发的时候只需要把arr类库文件引入android项目即可. + +### Android-SDK使用实例 + +#### 1.导入包 +```java +import snail007.proxy.Porxy +``` + +#### 2.启动一个服务 +```java +String args="http -p :8080" +String err=Proxy.start(args) +if (err.isEmpty()){ + //启动失败 + System.out.println("start fail,error:"+err) +}else{ + //启动成功 +} +``` +#### 3.判断一个服务是否在运行 + +```java +String args="http -p :8080" +boolean isRunning=Proxy.isRunning(args)//这里传递http也可以,最终使用的就是args里面的第一个参数http +if(isRunning){ + //正在运行 +}else{ + //没有运行 +} +``` +#### 4.停止一个服务 + +```java +String args="http -p :8080" +Proxy.stop(args)//这里传递http也可以,最终使用的就是args里面的第一个参数http +//停止完毕 + +``` + + +### IOS-SDK使用实例 + +#### todo + + +### 关于服务 +proxy的服务有11种,分别是: + +```shell +http +socks +sps +tcp +udp +bridge +server +client +tbridge +tserver +tclient +``` +每个服务只能启动一个,如果相同的服务启动多次,那么之前的服务会被停掉,后面启动的服务覆盖之前的服务. +上面这些服务的具体使用方式和具体参数,可以参考[proxy手册](https://github.com/snail007/goproxy/blob/master/README_ZH.md) \ No newline at end of file diff --git a/sdk/release.sh b/sdk/release.sh index a30863f..463f2d3 100755 --- a/sdk/release.sh +++ b/sdk/release.sh @@ -1,41 +1,56 @@ #/bin/bash VER="v4.7" -rm -rf proxy-sdk-release-* +rm -rf android +rm -rf ios +mkdir android +mkdir ios + #arm -gomobile bind -v -target=android/arm -mkdir proxy-sdk-arm -mv sdk.aar proxy-sdk-arm/proxy-sdk-arm.aar -mv sdk-sources.jar proxy-sdk-arm/proxy-sdk-arm-sources.jar -tar zcfv proxy-sdk-arm-${VER}.tar.gz proxy-sdk-arm -rm -rf proxy-sdk-arm +gomobile bind -v -target=android/arm -javapkg=snail007 -ldflags="-s -w" +mkdir arm +mv proxy.aar arm/snail007.goproxy.sdk.aar +mv proxy-sources.jar arm/snail007.goproxy.sdk-sources.jar +tar zcfv sdk-arm-${VER}.tar.gz arm +mv sdk-arm-${VER}.tar.gz android +rm -rf arm + + #arm64 -gomobile bind -v -target=android/arm64 -mkdir proxy-sdk-arm64 -mv sdk.aar proxy-sdk-arm64/proxy-sdk-arm64.aar -mv sdk-sources.jar proxy-sdk-arm64/proxy-sdk-arm64-sources.jar -tar zcfv proxy-sdk-arm64-${VER}.tar.gz proxy-sdk-arm64 -rm -rf proxy-sdk-arm64 +gomobile bind -v -target=android/arm64 -javapkg=snail007 -ldflags="-s -w" +mkdir arm64 +mv proxy.aar arm64/snail007.goproxy.sdk.aar +mv proxy-sources.jar arm64/snail007.goproxy.sdk-sources.jar +tar zcfv sdk-arm64-${VER}.tar.gz arm64 +mv sdk-arm64-${VER}.tar.gz android +rm -rf arm64 + + #386 -gomobile bind -v -target=android/386 -mkdir proxy-sdk-386 -mv sdk.aar proxy-sdk-386/proxy-sdk-386.aar -mv sdk-sources.jar proxy-sdk-386/proxy-sdk-386-sources.jar -tar zcfv proxy-sdk-386-${VER}.tar.gz proxy-sdk-386 -rm -rf proxy-sdk-386 +gomobile bind -v -target=android/386 -javapkg=snail007 -ldflags="-s -w" +mkdir 386 +mv proxy.aar 386/snail007.goproxy.sdk.aar +mv proxy-sources.jar 386/snail007.goproxy.sdk-sources.jar +tar zcfv sdk-386-${VER}.tar.gz 386 +mv sdk-386-${VER}.tar.gz android +rm -rf 386 + #amd64 -gomobile bind -v -target=android/amd64 -mkdir proxy-sdk-amd64 -mv sdk.aar proxy-sdk-amd64/proxy-sdk-amd64.aar -mv sdk-sources.jar proxy-sdk-amd64/proxy-sdk-amd64-sources.jar -tar zcfv proxy-sdk-amd64-${VER}.tar.gz proxy-sdk-amd64 -rm -rf proxy-sdk-amd64 +gomobile bind -v -target=android/amd64 -javapkg=snail007 -ldflags="-s -w" +mkdir amd64 +mv proxy.aar amd64/snail007.goproxy.sdk.aar +mv proxy-sources.jar amd64/snail007.goproxy.sdk-sources.jar +tar zcfv sdk-amd64-${VER}.tar.gz amd64 +mv sdk-amd64-${VER}.tar.gz android +rm -rf amd64 + + #all-in-one -gomobile bind -v -target=android -mkdir proxy-sdk-all -mv sdk.aar proxy-sdk-all/proxy-sdk-all.aar -mv sdk-sources.jar proxy-sdk-all/proxy-sdk-all-sources.jar -tar zcfv proxy-sdk-all-${VER}.tar.gz proxy-sdk-all -rm -rf proxy-sdk-all -mkdir proxy-sdk-release-${VER} -mv *.tar.gz proxy-sdk-release-${VER} +gomobile bind -v -target=android -javapkg=snail007 -ldflags="-s -w" +mkdir all +mv proxy.aar all/snail007.goproxy.sdk.aar +mv proxy-sources.jar all/snail007.goproxy.sdk-sources.jar +tar zcfv sdk-all-${VER}.tar.gz all +mv sdk-all-${VER}.tar.gz android +rm -rf all + echo "done." diff --git a/sdk/sdk.go b/sdk/sdk.go index ee881f4..5048dad 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -1,13 +1,16 @@ -package sdk +package proxy import ( "crypto/sha1" "fmt" "log" + "net" "os" "snail007/proxy/services" "snail007/proxy/services/kcpcfg" + "strconv" "strings" + "time" kcp "github.com/xtaci/kcp-go" "golang.org/x/crypto/pbkdf2" @@ -15,8 +18,7 @@ import ( ) var ( - app *kingpin.Application - service *services.ServiceItem + app *kingpin.Application ) //Start argsStr: is the whole command line args string @@ -47,7 +49,7 @@ func Start(argsStr string) (errStr string) { logfile := app.Flag("log", "log file path").Default("").String() kcpArgs.Key = app.Flag("kcp-key", "pre-shared secret between client and server").Default("secrect").String() kcpArgs.Crypt = app.Flag("kcp-method", "encrypt/decrypt method, can be: aes, aes-128, aes-192, salsa20, blowfish, twofish, cast5, 3des, tea, xtea, xor, sm4, none").Default("aes").Enum("aes", "aes-128", "aes-192", "salsa20", "blowfish", "twofish", "cast5", "3des", "tea", "xtea", "xor", "sm4", "none") - kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast").Enum("fast3", "fast2", "fast", "normal", "manual") + kcpArgs.Mode = app.Flag("kcp-mode", "profiles: fast3, fast2, fast, normal, manual").Default("fast3").Enum("fast3", "fast2", "fast", "normal", "manual") kcpArgs.MTU = app.Flag("kcp-mtu", "set maximum transmission unit for UDP packets").Default("1350").Int() kcpArgs.SndWnd = app.Flag("kcp-sndwnd", "set send window size(num of packets)").Default("1024").Int() kcpArgs.RcvWnd = app.Flag("kcp-rcvwnd", "set receive window size(num of packets)").Default("1024").Int() @@ -79,7 +81,6 @@ func Start(argsStr string) (errStr string) { httpArgs.Direct = http.Flag("direct", "direct domain file , one domain each line").Default("direct").Short('d').String() httpArgs.AuthFile = http.Flag("auth-file", "http basic auth file,\"username:password\" each line in file").Short('F').String() httpArgs.Auth = http.Flag("auth", "http basic auth username and password, mutiple user repeat -a ,such as: -a user1:pass1 -a user2:pass2").Short('a').Strings() - httpArgs.PoolSize = http.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() httpArgs.CheckParentInterval = http.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() httpArgs.Local = http.Flag("local", "local ip:port to listen,multiple address use comma split,such as: 0.0.0.0:80,0.0.0.0:443").Short('p').Default(":33080").String() httpArgs.SSHUser = http.Flag("ssh-user", "user for ssh").Short('u').Default("").String() @@ -102,7 +103,6 @@ func Start(argsStr string) (errStr string) { tcpArgs.Timeout = tcp.Flag("timeout", "tcp timeout milliseconds when connect to real server or parent proxy").Short('e').Default("2000").Int() tcpArgs.ParentType = tcp.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "udp", "kcp") tcpArgs.LocalType = tcp.Flag("local-type", "local protocol type ").Default("tcp").Short('t').Enum("tls", "tcp", "kcp") - tcpArgs.PoolSize = tcp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() tcpArgs.CheckParentInterval = tcp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() tcpArgs.Local = tcp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() @@ -113,7 +113,6 @@ func Start(argsStr string) (errStr string) { udpArgs.KeyFile = udp.Flag("key", "key file for tls").Short('K').Default("proxy.key").String() udpArgs.Timeout = udp.Flag("timeout", "tcp timeout milliseconds when connect to parent proxy").Short('t').Default("2000").Int() udpArgs.ParentType = udp.Flag("parent-type", "parent protocol type ").Short('T').Enum("tls", "tcp", "udp") - udpArgs.PoolSize = udp.Flag("pool-size", "conn pool size , which connect to parent proxy, zero: means turn off pool").Short('L').Default("0").Int() udpArgs.CheckParentInterval = udp.Flag("check-parent-interval", "check if proxy is okay every interval seconds,zero: means no check").Short('I').Default("3").Int() udpArgs.Local = udp.Flag("local", "local ip:port to listen").Short('p').Default(":33080").String() @@ -225,7 +224,11 @@ func Start(argsStr string) (errStr string) { spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int() spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String() //parse args - args := strings.Fields(strings.Trim(argsStr, " ")) + _args := strings.Fields(strings.Trim(argsStr, " ")) + args := []string{} + for _, a := range _args { + args = append(args, strings.Trim(a, "\"")) + } serviceName, err := app.Parse(args) if err != nil { return fmt.Sprintf("parse args fail,err: %s", err) @@ -323,16 +326,120 @@ func Start(argsStr string) (errStr string) { case "sps": services.Regist("sps", services.NewSPS(), spsArgs) } - - service, err = services.Run(serviceName) + _, err = services.Run(serviceName) if err != nil { return fmt.Sprintf("run service [%s] fail, ERR:%s", serviceName, err) } return } -func Stop() { - if service != nil && service.S != nil { - service.S.Clean() +func Stop(service string) { + s := getServiceName(service) + if s == "" { + return } + services.Stop(s) +} + +func IsRunning(service string) bool { + s := getServiceName(service) + if s == "" { + return false + } + srv := services.GetService(s) + if srv == nil { + return false + } + typ := "tcp" + addr := "" + route := "" + switch srv.Name { + case "http": + addr = *srv.Args.(services.HTTPArgs).Local + case "socks": + addr = *srv.Args.(services.SocksArgs).Local + case "sps": + addr = *srv.Args.(services.SPSArgs).Local + case "tcp": + addr = *srv.Args.(services.TCPArgs).Local + case "bridge": + addr = *srv.Args.(services.MuxBridgeArgs).Local + + case "tbridge": + addr = *srv.Args.(services.TunnelBridgeArgs).Local + case "server": + if len(*srv.Args.(services.MuxServerArgs).Route) > 0 { + route = (*srv.Args.(services.MuxServerArgs).Route)[0] + } + case "tserver": + if len(*srv.Args.(services.TunnelServerArgs).Route) > 0 { + route = (*srv.Args.(services.TunnelServerArgs).Route)[0] + } + case "client": + case "tclient": + case "udp": + typ = "udp" + } + if route != "" { + if strings.HasPrefix(route, "udp://") { + typ = "udp" + } + info := strings.TrimPrefix(route, "udp://") + info = strings.TrimPrefix(info, "tcp://") + _routeInfo := strings.Split(info, "@") + addr = _routeInfo[0] + } + a := strings.Split(addr, ",") + if len(a) > 0 { + return PortIsAlive(a[0], typ) == "" + } + return false +} + +func getServiceName(args string) string { + s := strings.Fields(strings.Trim(args, " \t")) + if len(s) == 0 { + return "" + } + return s[0] +} + +func PortIsAlive(address string, network ...string) string { + time.Sleep(time.Second) + n := "tcp" + if len(network) == 1 { + n = network[0] + } + if n == "tcp" { + conn, err := net.DialTimeout(n, address, time.Second) + if err != nil { + return fmt.Sprintf("connect %s is failed!,err:%v\n", address, err) + } + conn.Close() + } else { + ip, port, err := net.SplitHostPort(address) + if err != nil { + return err.Error() + } + portI, _ := strconv.Atoi(port) + dstAddr := &net.UDPAddr{IP: net.ParseIP(ip), Port: portI} + conn, err := net.DialUDP(n, &net.UDPAddr{IP: net.IPv4zero, Port: 0}, dstAddr) + if err != nil { + return err.Error() + } + conn.SetDeadline(time.Now().Add(time.Millisecond * 200)) + _, err = conn.Write([]byte{0x00}) + conn.SetDeadline(time.Now().Add(time.Millisecond * 200)) + b := make([]byte, 1) + _, err = conn.Read(b) + + if err != nil { + if strings.Contains(err.Error(), "refused") { + return err.Error() + } + } else { + conn.Close() + } + } + return "" } diff --git a/services/args.go b/services/args.go index 68ccb78..2f98140 100644 --- a/services/args.go +++ b/services/args.go @@ -111,7 +111,6 @@ type TCPArgs struct { ParentType *string LocalType *string Timeout *int - PoolSize *int CheckParentInterval *int KCP kcpcfg.KCPConfigArgs } @@ -139,7 +138,6 @@ type HTTPArgs struct { ParentType *string LocalType *string Timeout *int - PoolSize *int CheckParentInterval *int SSHKeyFile *string SSHKeyFileSalt *string @@ -161,7 +159,6 @@ type UDPArgs struct { Local *string ParentType *string Timeout *int - PoolSize *int CheckParentInterval *int } type SocksArgs struct { diff --git a/services/http.go b/services/http.go index c4da7fd..8d3c29e 100644 --- a/services/http.go +++ b/services/http.go @@ -25,6 +25,7 @@ type HTTP struct { domainResolver utils.DomainResolver isStop bool serverChannels []*utils.ServerChannel + userConns utils.ConcurrentMap } func NewHTTP() Service { @@ -36,6 +37,7 @@ func NewHTTP() Service { lockChn: make(chan bool, 1), isStop: false, serverChannels: []*utils.ServerChannel{}, + userConns: utils.NewConcurrentMap(), } } func (s *HTTP) CheckArgs() (err error) { @@ -139,7 +141,7 @@ func (s *HTTP) StopService() { if e != nil { log.Printf("stop http(s) service crashed,%s", e) } else { - log.Printf("service http(s) stoped,%s", e) + log.Printf("service http(s) stoped") } }() s.isStop = true @@ -230,7 +232,6 @@ func (s *HTTP) callback(inConn net.Conn) { log.Printf("use proxy : %v, %s", useProxy, address) err = s.OutToTCP(useProxy, address, &inConn, &req) - if err != nil { if *s.cfg.Parent == "" { log.Printf("connect to %s fail, ERR:%s", address, err) @@ -298,9 +299,13 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut utils.IoBind((*inConn), outConn, func(err interface{}) { log.Printf("conn %s - %s released [%s]", inAddr, outAddr, req.Host) + s.userConns.Remove(inAddr) }) log.Printf("conn %s - %s connected [%s]", inAddr, outAddr, req.Host) - + if c, ok := s.userConns.Get(inAddr); ok { + (*c.(*net.Conn)).Close() + } + s.userConns.Set(inAddr, inConn) return } @@ -372,8 +377,6 @@ func (s *HTTP) InitOutConnPool() { s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes, s.Resolve(*s.cfg.Parent), *s.cfg.Timeout, - *s.cfg.PoolSize, - *s.cfg.PoolSize*2, ) } } diff --git a/services/mux_bridge.go b/services/mux_bridge.go index e0f8fe7..8440d21 100644 --- a/services/mux_bridge.go +++ b/services/mux_bridge.go @@ -19,6 +19,7 @@ import ( type MuxBridge struct { cfg MuxBridgeArgs clientControlConns utils.ConcurrentMap + serverConns utils.ConcurrentMap router utils.ClientKeyRouter l *sync.Mutex isStop bool @@ -29,6 +30,7 @@ func NewMuxBridge() Service { b := &MuxBridge{ cfg: MuxBridgeArgs{}, clientControlConns: utils.NewConcurrentMap(), + serverConns: utils.NewConcurrentMap(), l: &sync.Mutex{}, isStop: false, } @@ -58,7 +60,7 @@ func (s *MuxBridge) StopService() { if e != nil { log.Printf("stop bridge service crashed,%s", e) } else { - log.Printf("service bridge stoped,%s", e) + log.Printf("service bridge stoped") } }() s.isStop = true @@ -70,6 +72,9 @@ func (s *MuxBridge) StopService() { (session.(*smux.Session)).Close() } } + for _, c := range s.serverConns.Items() { + (*c.(*net.Conn)).Close() + } } func (s *MuxBridge) Start(args interface{}) (err error) { s.cfg = args.(MuxBridgeArgs) @@ -116,6 +121,7 @@ func (s *MuxBridge) handler(inConn net.Conn) { switch connType { case CONN_SERVER: var serverID string + inAddr := inConn.RemoteAddr().String() inConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) err = utils.ReadPacketData(reader, &serverID) inConn.SetDeadline(time.Time{}) @@ -124,6 +130,10 @@ func (s *MuxBridge) handler(inConn net.Conn) { return } log.Printf("server connection %s %s connected", serverID, key) + if c, ok := s.serverConns.Get(inAddr); ok { + (*c.(*net.Conn)).Close() + } + s.serverConns.Set(inAddr, &inConn) session, err := smux.Server(inConn, nil) if err != nil { utils.CloseConn(&inConn) @@ -138,6 +148,7 @@ func (s *MuxBridge) handler(inConn net.Conn) { if err != nil { session.Close() utils.CloseConn(&inConn) + s.serverConns.Remove(inAddr) log.Printf("server connection %s %s released", serverID, key) return } diff --git a/services/mux_client.go b/services/mux_client.go index b233598..1c8d772 100644 --- a/services/mux_client.go +++ b/services/mux_client.go @@ -56,7 +56,7 @@ func (s *MuxClient) StopService() { if e != nil { log.Printf("stop client service crashed,%s", e) } else { - log.Printf("service client stoped,%s", e) + log.Printf("service client stoped") } }() s.isStop = true diff --git a/services/mux_server.go b/services/mux_server.go index 27dde50..d5a4860 100644 --- a/services/mux_server.go +++ b/services/mux_server.go @@ -155,7 +155,7 @@ func (s *MuxServer) StopService() { if e != nil { log.Printf("stop server service crashed,%s", e) } else { - log.Printf("service server stoped,%s", e) + log.Printf("service server stoped") } }() s.isStop = true diff --git a/services/service.go b/services/service.go index 8131a68..4d06d40 100644 --- a/services/service.go +++ b/services/service.go @@ -18,13 +18,20 @@ type ServiceItem struct { var servicesMap = map[string]*ServiceItem{} func Regist(name string, s Service, args interface{}) { - + Stop(name) servicesMap[name] = &ServiceItem{ S: s, Args: args, Name: name, } } +func GetService(name string) *ServiceItem { + if s, ok := servicesMap[name]; ok && s.S != nil { + return s + } + return nil + +} func Stop(name string) { if s, ok := servicesMap[name]; ok && s.S != nil { s.S.Clean() diff --git a/services/socks.go b/services/socks.go index a79a6c7..e3ee6d2 100644 --- a/services/socks.go +++ b/services/socks.go @@ -26,6 +26,7 @@ type Socks struct { sc *utils.ServerChannel domainResolver utils.DomainResolver isStop bool + userConns utils.ConcurrentMap } func NewSocks() Service { @@ -35,6 +36,7 @@ func NewSocks() Service { basicAuth: utils.BasicAuth{}, lockChn: make(chan bool, 1), isStop: false, + userConns: utils.NewConcurrentMap(), } } @@ -147,7 +149,7 @@ func (s *Socks) StopService() { if e != nil { log.Printf("stop socks service crashed,%s", e) } else { - log.Printf("service socks stoped,%s", e) + log.Printf("service socks stoped") } }() s.isStop = true @@ -161,6 +163,9 @@ func (s *Socks) StopService() { if s.sc != nil && (*s.sc).Listener != nil { (*(*s.sc).Listener).Close() } + for _, c := range s.userConns.Items() { + (*c.(*net.Conn)).Close() + } } func (s *Socks) Start(args interface{}) (err error) { //start() @@ -526,6 +531,11 @@ func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, reque utils.IoBind(*inConn, outConn, func(err interface{}) { log.Printf("conn %s - %s released", inAddr, request.Addr()) }) + if c, ok := s.userConns.Get(inAddr); ok { + (*c.(*net.Conn)).Close() + s.userConns.Remove(inAddr) + } + s.userConns.Set(inAddr, inConn) } func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn net.Conn, err interface{}) { switch *s.cfg.ParentType { diff --git a/services/sps.go b/services/sps.go index ff02bfb..139ff88 100644 --- a/services/sps.go +++ b/services/sps.go @@ -22,6 +22,7 @@ type SPS struct { domainResolver utils.DomainResolver basicAuth utils.BasicAuth serverChannels []*utils.ServerChannel + userConns utils.ConcurrentMap } func NewSPS() Service { @@ -30,6 +31,7 @@ func NewSPS() Service { cfg: SPSArgs{}, basicAuth: utils.BasicAuth{}, serverChannels: []*utils.ServerChannel{}, + userConns: utils.NewConcurrentMap(), } } func (s *SPS) CheckArgs() (err error) { @@ -75,8 +77,6 @@ func (s *SPS) InitOutConnPool() { s.cfg.CertBytes, s.cfg.KeyBytes, nil, *s.cfg.Parent, *s.cfg.Timeout, - 0, - 0, ) } } @@ -87,7 +87,7 @@ func (s *SPS) StopService() { if e != nil { log.Printf("stop sps service crashed,%s", e) } else { - log.Printf("service sps stoped,%s", e) + log.Printf("service sps stoped") } }() for _, sc := range s.serverChannels { @@ -98,6 +98,9 @@ func (s *SPS) StopService() { (*sc.UDPListener).Close() } } + for _, c := range s.userConns.Items() { + (*c.(*net.Conn)).Close() + } } func (s *SPS) Start(args interface{}) (err error) { s.cfg = args.(SPSArgs) @@ -304,8 +307,13 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { outAddr := outConn.RemoteAddr().String() utils.IoBind((*inConn), outConn, func(err interface{}) { log.Printf("conn %s - %s released", inAddr, outAddr) + s.userConns.Remove(inAddr) }) log.Printf("conn %s - %s connected", inAddr, outAddr) + if c, ok := s.userConns.Get(inAddr); ok { + (*c.(*net.Conn)).Close() + } + s.userConns.Set(inAddr, &inConn) return } func (s *SPS) InitBasicAuth() (err error) { diff --git a/services/tcp.go b/services/tcp.go index 176d289..fde2849 100644 --- a/services/tcp.go +++ b/services/tcp.go @@ -14,17 +14,19 @@ import ( ) type TCP struct { - outPool utils.OutConn - cfg TCPArgs - sc *utils.ServerChannel - isStop bool + outPool utils.OutConn + cfg TCPArgs + sc *utils.ServerChannel + isStop bool + userConns utils.ConcurrentMap } func NewTCP() Service { return &TCP{ - outPool: utils.OutConn{}, - cfg: TCPArgs{}, - isStop: false, + outPool: utils.OutConn{}, + cfg: TCPArgs{}, + isStop: false, + userConns: utils.NewConcurrentMap(), } } func (s *TCP) CheckArgs() (err error) { @@ -54,7 +56,7 @@ func (s *TCP) StopService() { if e != nil { log.Printf("stop tcp service crashed,%s", e) } else { - log.Printf("service tcp stoped,%s", e) + log.Printf("service tcp stoped") } }() s.isStop = true @@ -64,6 +66,9 @@ func (s *TCP) StopService() { if s.sc.UDPListener != nil { (*s.sc.UDPListener).Close() } + for _, c := range s.userConns.Items() { + (*c.(*net.Conn)).Close() + } } func (s *TCP) Start(args interface{}) (err error) { s.cfg = args.(TCPArgs) @@ -134,14 +139,20 @@ func (s *TCP) OutToTCP(inConn *net.Conn) (err error) { //outLocalAddr := outConn.LocalAddr().String() utils.IoBind((*inConn), outConn, func(err interface{}) { log.Printf("conn %s - %s released", inAddr, outAddr) + s.userConns.Remove(inAddr) }) log.Printf("conn %s - %s connected", inAddr, outAddr) + if c, ok := s.userConns.Get(inAddr); ok { + (*c.(*net.Conn)).Close() + } + s.userConns.Set(inAddr, &inConn) return } func (s *TCP) OutToUDP(inConn *net.Conn) (err error) { log.Printf("conn created , remote : %s ", (*inConn).RemoteAddr()) for { if s.isStop { + (*inConn).Close() return } srcAddr, body, err := utils.ReadUDPPacket(bufio.NewReader(*inConn)) @@ -200,8 +211,6 @@ func (s *TCP) InitOutConnPool() { s.cfg.CertBytes, s.cfg.KeyBytes, nil, *s.cfg.Parent, *s.cfg.Timeout, - *s.cfg.PoolSize, - *s.cfg.PoolSize*2, ) } } diff --git a/services/tunnel_bridge.go b/services/tunnel_bridge.go index f9c6077..b3d4c7d 100644 --- a/services/tunnel_bridge.go +++ b/services/tunnel_bridge.go @@ -47,7 +47,7 @@ func (s *TunnelBridge) StopService() { if e != nil { log.Printf("stop tbridge service crashed,%s", e) } else { - log.Printf("service tbridge stoped,%s", e) + log.Printf("service tbridge stoped") } }() s.isStop = true diff --git a/services/tunnel_client.go b/services/tunnel_client.go index 4f0f51a..d7f9ed8 100644 --- a/services/tunnel_client.go +++ b/services/tunnel_client.go @@ -11,17 +11,17 @@ import ( ) type TunnelClient struct { - cfg TunnelClientArgs - // cm utils.ConnManager - ctrlConn net.Conn - isStop bool + cfg TunnelClientArgs + ctrlConn net.Conn + isStop bool + userConns utils.ConcurrentMap } func NewTunnelClient() Service { return &TunnelClient{ - cfg: TunnelClientArgs{}, - // cm: utils.NewConnManager(), - isStop: false, + cfg: TunnelClientArgs{}, + userConns: utils.NewConcurrentMap(), + isStop: false, } } @@ -49,13 +49,16 @@ func (s *TunnelClient) StopService() { if e != nil { log.Printf("stop tclient service crashed,%s", e) } else { - log.Printf("service tclient stoped,%s", e) + log.Printf("service tclient stoped") } }() s.isStop = true if s.ctrlConn != nil { s.ctrlConn.Close() } + for _, c := range s.userConns.Items() { + (*c.(*net.Conn)).Close() + } } func (s *TunnelClient) Start(args interface{}) (err error) { s.cfg = args.(TunnelClientArgs) @@ -139,6 +142,9 @@ func (s *TunnelClient) ServeUDP(localAddr, ID, serverID string) { // for { for { if s.isStop { + if inConn != nil { + inConn.Close() + } return } // s.cm.RemoveOne(*s.cfg.Key, ID) @@ -252,10 +258,14 @@ func (s *TunnelClient) ServeConn(localAddr, ID, serverID string) { log.Printf("build connection error, err: %s", err) return } + inAddr := inConn.RemoteAddr().String() utils.IoBind(inConn, outConn, func(err interface{}) { log.Printf("conn %s released", ID) - // s.cm.RemoveOne(*s.cfg.Key, ID) + s.userConns.Remove(inAddr) }) - // s.cm.Add(*s.cfg.Key, ID, &inConn) + if c, ok := s.userConns.Get(inAddr); ok { + (*c.(*net.Conn)).Close() + } + s.userConns.Set(inAddr, &inConn) log.Printf("conn %s created", ID) } diff --git a/services/tunnel_server.go b/services/tunnel_server.go index d0dcc16..c82170f 100644 --- a/services/tunnel_server.go +++ b/services/tunnel_server.go @@ -14,11 +14,12 @@ import ( ) type TunnelServer struct { - cfg TunnelServerArgs - udpChn chan UDPItem - sc utils.ServerChannel - isStop bool - udpConn *net.Conn + cfg TunnelServerArgs + udpChn chan UDPItem + sc utils.ServerChannel + isStop bool + udpConn *net.Conn + userConns utils.ConcurrentMap } type TunnelServerManager struct { @@ -139,9 +140,10 @@ func (s *TunnelServerManager) GetConn() (conn net.Conn, err error) { } func NewTunnelServer() Service { return &TunnelServer{ - cfg: TunnelServerArgs{}, - udpChn: make(chan UDPItem, 50000), - isStop: false, + cfg: TunnelServerArgs{}, + udpChn: make(chan UDPItem, 50000), + isStop: false, + userConns: utils.NewConcurrentMap(), } } @@ -157,7 +159,7 @@ func (s *TunnelServer) StopService() { if e != nil { log.Printf("stop server service crashed,%s", e) } else { - log.Printf("service server stoped,%s", e) + log.Printf("service server stoped") } }() s.isStop = true @@ -171,6 +173,9 @@ func (s *TunnelServer) StopService() { if s.udpConn != nil { (*s.udpConn).Close() } + for _, c := range s.userConns.Items() { + (*c.(*net.Conn)).Close() + } } func (s *TunnelServer) InitService() (err error) { s.UDPConnDeamon() @@ -230,12 +235,15 @@ func (s *TunnelServer) Start(args interface{}) (err error) { break } } + inAddr := inConn.RemoteAddr().String() utils.IoBind(inConn, outConn, func(err interface{}) { - // s.cfg.Mgr.cm.RemoveOne(s.cfg.Mgr.serverID, ID) + s.userConns.Remove(inAddr) log.Printf("%s conn %s released", *s.cfg.Key, ID) }) - //add conn - // s.cfg.Mgr.cm.Add(s.cfg.Mgr.serverID, ID, &inConn) + if c, ok := s.userConns.Get(inAddr); ok { + (*c.(*net.Conn)).Close() + } + s.userConns.Set(inAddr, &inConn) log.Printf("%s conn %s created", *s.cfg.Key, ID) }) if err != nil { diff --git a/services/udp.go b/services/udp.go index 09f6aeb..52bab39 100644 --- a/services/udp.go +++ b/services/udp.go @@ -59,7 +59,7 @@ func (s *UDP) StopService() { if e != nil { log.Printf("stop udp service crashed,%s", e) } else { - log.Printf("service udp stoped,%s", e) + log.Printf("service udp stoped") } }() s.isStop = true @@ -133,7 +133,7 @@ func (s *UDP) GetConn(connKey string) (conn net.Conn, isNew bool, err error) { func (s *UDP) OutToTCP(packet []byte, localAddr, srcAddr *net.UDPAddr) (err error) { numLocal := crc32.ChecksumIEEE([]byte(localAddr.String())) numSrc := crc32.ChecksumIEEE([]byte(srcAddr.String())) - mod := uint32(*s.cfg.PoolSize) + mod := uint32(10) if mod == 0 { mod = 10 } @@ -153,6 +153,7 @@ func (s *UDP) OutToTCP(packet []byte, localAddr, srcAddr *net.UDPAddr) (err erro log.Printf("conn %d created , local: %s", connKey, srcAddr.String()) for { if s.isStop { + conn.Close() return } srcAddrFromConn, body, err := utils.ReadUDPPacket(bufio.NewReader(conn)) @@ -240,8 +241,6 @@ func (s *UDP) InitOutConnPool() { s.cfg.CertBytes, s.cfg.KeyBytes, nil, *s.cfg.Parent, *s.cfg.Timeout, - *s.cfg.PoolSize, - *s.cfg.PoolSize*2, ) } } diff --git a/utils/serve-channel.go b/utils/serve-channel.go index 596cede..8547441 100644 --- a/utils/serve-channel.go +++ b/utils/serve-channel.go @@ -24,7 +24,7 @@ func NewServerChannel(ip string, port int) ServerChannel { ip: ip, port: port, errAcceptHandler: func(err error) { - fmt.Printf("accept error , ERR:%s", err) + log.Printf("accept error , ERR:%s", err) }, } } diff --git a/utils/structs.go b/utils/structs.go index 6279530..30f6bc3 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -516,7 +516,7 @@ type OutConn struct { timeout int } -func NewOutConn(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyBytes, caCertBytes []byte, address string, timeout int, InitialCap int, MaxCap int) (op OutConn) { +func NewOutConn(dur int, typ string, kcp kcpcfg.KCPConfigArgs, certBytes, keyBytes, caCertBytes []byte, address string, timeout int) (op OutConn) { return OutConn{ dur: dur, typ: typ, From d48f3b3323ff14ed89019eeef5199f9345b19c71 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 12 Apr 2018 16:36:36 +0800 Subject: [PATCH 44/76] Signed-off-by: arraykeys@gmail.com --- sdk/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/README.md b/sdk/README.md index 899e048..39b555d 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -22,11 +22,11 @@ import snail007.proxy.Porxy #### 2.启动一个服务 ```java -String args="http -p :8080" -String err=Proxy.start(args) +String args="http -p :8080"; +String err=Proxy.start(args); if (err.isEmpty()){ //启动失败 - System.out.println("start fail,error:"+err) + System.out.println("start fail,error:"+err); }else{ //启动成功 } @@ -34,8 +34,8 @@ if (err.isEmpty()){ #### 3.判断一个服务是否在运行 ```java -String args="http -p :8080" -boolean isRunning=Proxy.isRunning(args)//这里传递http也可以,最终使用的就是args里面的第一个参数http +String args="http -p :8080"; +boolean isRunning=Proxy.isRunning(args);//这里传递http也可以,最终使用的就是args里面的第一个参数http if(isRunning){ //正在运行 }else{ @@ -45,8 +45,8 @@ if(isRunning){ #### 4.停止一个服务 ```java -String args="http -p :8080" -Proxy.stop(args)//这里传递http也可以,最终使用的就是args里面的第一个参数http +String args="http -p :8080"; +Proxy.stop(args);//这里传递http也可以,最终使用的就是args里面的第一个参数http //停止完毕 ``` From 350eb5b6ed5a5badf097f41c7f2630f73068e786 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 12 Apr 2018 16:41:03 +0800 Subject: [PATCH 45/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 54de418..6ba336c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -219,9 +219,6 @@ proxy会fork子进程,然后监控子进程,如果子进程异常退出,5秒后 #### **1.2.普通二级HTTP代理** 使用本地端口8090,假设上级HTTP代理是`22.22.22.22:8080` `./proxy http -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" ` -默认关闭了连接池,如果要加快访问速度,-L可以开启连接池,10就是连接池大小,0为关闭, -开启连接池在网络不好的情况下,稳定不是很好. -`./proxy http -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -L 10` 我们还可以指定网站域名的黑白名单文件,一行一个域名,匹配规则是最右匹配,比如:baidu.com,匹配的是*.*.baidu.com,黑名单的域名域名直接走上级代理,白名单的域名不走上级代理. `./proxy http -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -b blocked.txt -d direct.txt` From 5a68fb3c3da24ff45140dcb58ac2bb7c5595e13b Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 12 Apr 2018 16:42:28 +0800 Subject: [PATCH 46/76] Signed-off-by: arraykeys@gmail.com --- sdk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/README.md b/sdk/README.md index 39b555d..6054304 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -24,7 +24,7 @@ import snail007.proxy.Porxy ```java String args="http -p :8080"; String err=Proxy.start(args); -if (err.isEmpty()){ +if (!err.isEmpty()){ //启动失败 System.out.println("start fail,error:"+err); }else{ From 6f1d826ef54748169ddab8d175d21c80482c5c64 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 12 Apr 2018 16:45:39 +0800 Subject: [PATCH 47/76] Signed-off-by: arraykeys@gmail.com --- sdk/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/README.md b/sdk/README.md index 6054304..4b6941a 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -74,4 +74,5 @@ tserver tclient ``` 每个服务只能启动一个,如果相同的服务启动多次,那么之前的服务会被停掉,后面启动的服务覆盖之前的服务. -上面这些服务的具体使用方式和具体参数,可以参考[proxy手册](https://github.com/snail007/goproxy/blob/master/README_ZH.md) \ No newline at end of file +上面这些服务的具体使用方式和具体参数,可以参考[proxy手册](https://github.com/snail007/goproxy/blob/master/README_ZH.md) +sdk里面的服务不支持手册里面的:--daemon和--forever参数. \ No newline at end of file From 16bb452640c99a07a07febcc0d498cfb04f0836b Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 12 Apr 2018 17:03:16 +0800 Subject: [PATCH 48/76] Signed-off-by: arraykeys@gmail.com --- sdk/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/README.md b/sdk/README.md index 4b6941a..e8b020f 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -42,6 +42,9 @@ if(isRunning){ //没有运行 } ``` + +由于tclient和client服务的特性,目前这个方法对于服务tclient和client永远返回false. + #### 4.停止一个服务 ```java From cf22866b2a959615933bd95e1b40c2f944324dc9 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 13 Apr 2018 14:12:05 +0800 Subject: [PATCH 49/76] Signed-off-by: arraykeys@gmail.com --- sdk/release.sh | 51 +++++--------------------------------------------- 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/sdk/release.sh b/sdk/release.sh index 463f2d3..6b0879c 100755 --- a/sdk/release.sh +++ b/sdk/release.sh @@ -5,52 +5,11 @@ rm -rf ios mkdir android mkdir ios -#arm -gomobile bind -v -target=android/arm -javapkg=snail007 -ldflags="-s -w" -mkdir arm -mv proxy.aar arm/snail007.goproxy.sdk.aar -mv proxy-sources.jar arm/snail007.goproxy.sdk-sources.jar -tar zcfv sdk-arm-${VER}.tar.gz arm -mv sdk-arm-${VER}.tar.gz android -rm -rf arm - - -#arm64 -gomobile bind -v -target=android/arm64 -javapkg=snail007 -ldflags="-s -w" -mkdir arm64 -mv proxy.aar arm64/snail007.goproxy.sdk.aar -mv proxy-sources.jar arm64/snail007.goproxy.sdk-sources.jar -tar zcfv sdk-arm64-${VER}.tar.gz arm64 -mv sdk-arm64-${VER}.tar.gz android -rm -rf arm64 - - -#386 -gomobile bind -v -target=android/386 -javapkg=snail007 -ldflags="-s -w" -mkdir 386 -mv proxy.aar 386/snail007.goproxy.sdk.aar -mv proxy-sources.jar 386/snail007.goproxy.sdk-sources.jar -tar zcfv sdk-386-${VER}.tar.gz 386 -mv sdk-386-${VER}.tar.gz android -rm -rf 386 - -#amd64 -gomobile bind -v -target=android/amd64 -javapkg=snail007 -ldflags="-s -w" -mkdir amd64 -mv proxy.aar amd64/snail007.goproxy.sdk.aar -mv proxy-sources.jar amd64/snail007.goproxy.sdk-sources.jar -tar zcfv sdk-amd64-${VER}.tar.gz amd64 -mv sdk-amd64-${VER}.tar.gz android -rm -rf amd64 - - #all-in-one gomobile bind -v -target=android -javapkg=snail007 -ldflags="-s -w" -mkdir all -mv proxy.aar all/snail007.goproxy.sdk.aar -mv proxy-sources.jar all/snail007.goproxy.sdk-sources.jar -tar zcfv sdk-all-${VER}.tar.gz all -mv sdk-all-${VER}.tar.gz android -rm -rf all - +mv proxy.aar android/snail007.goproxy.sdk.aar +mv proxy-sources.jar android/snail007.goproxy.sdk-sources.jar +cp README.md android +tar zcfv sdk-android-${VER}.tar.gz android +rm -rf android echo "done." From 0998c06195a55a2b3068d3c360e4cd32e1e7208f Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 13 Apr 2018 17:23:42 +0800 Subject: [PATCH 50/76] Add compress and encryt support on (tcp|tls|kcp) transport layer Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 5 ++ config.go | 14 ++++++ sdk/sdk.go | 14 ++++++ services/args.go | 12 +++++ services/http.go | 18 +++++++ services/socks.go | 18 +++++++ services/sps.go | 18 ++++++- utils/conncrypt/conncrypt.go | 95 ++++++++++++++++++++++++++++++++++++ utils/structs.go | 8 +++ 9 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 utils/conncrypt/conncrypt.go diff --git a/CHANGELOG b/CHANGELOG index f412d48..0e5522a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,11 @@ v4.7 5.优化了所有服务代码,方便对sdk提供支持. 6.增加了SDK手册. 7.增加了GUI客户端(windows/web/android/ios)介绍主页. +8.SPS\HTTP(s)\Socks代理增加了自定义加密传输,只需要通过参数-z和-Z设置一个密码即可. +9.SPS\HTTP(s)\Socks代理增加了压缩传输,只需要通过参数-m和-M设置即可. +10.手册增加了SPS\HTTP(s)\Socks自定义加密的使用示例. +11.手册增加了SPS\HTTP(s)\Socks压缩传输的使用示例. + v4.6 1.sps,http(s),socks5,内网穿透都做了大量的超时优化处理,更加稳定. diff --git a/config.go b/config.go index 229ae33..dcdfaea 100755 --- a/config.go +++ b/config.go @@ -99,6 +99,10 @@ func initConfig() (err error) { httpArgs.AuthURLRetry = http.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("1").Int() httpArgs.DNSAddress = http.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() httpArgs.DNSTTL = http.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() + httpArgs.LocalKey = http.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String() + httpArgs.ParentKey = http.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String() + httpArgs.LocalCompress = http.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool() + httpArgs.ParentCompress = http.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool() //########tcp######### tcp := app.Command("tcp", "proxy on tcp mode") @@ -207,6 +211,11 @@ func initConfig() (err error) { socksArgs.AuthURLRetry = socks.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int() socksArgs.DNSAddress = socks.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() socksArgs.DNSTTL = socks.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() + socksArgs.LocalKey = socks.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String() + socksArgs.ParentKey = socks.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String() + socksArgs.LocalCompress = socks.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool() + socksArgs.ParentCompress = socks.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool() + //########socks+http(s)######### sps := app.Command("sps", "proxy on socks+http(s) mode") spsArgs.Parent = sps.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() @@ -228,6 +237,11 @@ func initConfig() (err error) { spsArgs.AuthURLOkCode = sps.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int() spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int() spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String() + spsArgs.LocalKey = sps.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String() + 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() + //parse args serviceName := kingpin.MustParse(app.Parse(os.Args[1:])) diff --git a/sdk/sdk.go b/sdk/sdk.go index 5048dad..1942bc5 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -94,6 +94,10 @@ func Start(argsStr string) (errStr string) { httpArgs.AuthURLRetry = http.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("1").Int() httpArgs.DNSAddress = http.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() httpArgs.DNSTTL = http.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() + httpArgs.LocalKey = http.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String() + httpArgs.ParentKey = http.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String() + httpArgs.LocalCompress = http.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool() + httpArgs.ParentCompress = http.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool() //########tcp######### tcp := app.Command("tcp", "proxy on tcp mode") @@ -202,6 +206,11 @@ func Start(argsStr string) (errStr string) { socksArgs.AuthURLRetry = socks.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int() socksArgs.DNSAddress = socks.Flag("dns-address", "if set this, proxy will use this dns for resolve doamin").Short('q').Default("").String() socksArgs.DNSTTL = socks.Flag("dns-ttl", "caching seconds of dns query result").Short('e').Default("300").Int() + socksArgs.LocalKey = socks.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String() + socksArgs.ParentKey = socks.Flag("parent-key", "the password for auto encrypt/decrypt parent connection data").Short('Z').Default("").String() + socksArgs.LocalCompress = socks.Flag("local-compress", "auto compress/decompress data on local connection").Short('m').Default("false").Bool() + socksArgs.ParentCompress = socks.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool() + //########socks+http(s)######### sps := app.Command("sps", "proxy on socks+http(s) mode") spsArgs.Parent = sps.Flag("parent", "parent address, such as: \"23.32.32.19:28008\"").Default("").Short('P').String() @@ -223,6 +232,11 @@ func Start(argsStr string) (errStr string) { spsArgs.AuthURLOkCode = sps.Flag("auth-code", "access 'auth-url' success http code").Default("204").Int() spsArgs.AuthURLRetry = sps.Flag("auth-retry", "access 'auth-url' fail and retry count").Default("0").Int() spsArgs.ParentAuth = sps.Flag("parent-auth", "parent socks auth username and password, such as: -A user1:pass1").Short('A').String() + spsArgs.LocalKey = sps.Flag("local-key", "the password for auto encrypt/decrypt local connection data").Short('z').Default("").String() + 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() + //parse args _args := strings.Fields(strings.Trim(argsStr, " ")) args := []string{} diff --git a/services/args.go b/services/args.go index 2f98140..2403cc6 100644 --- a/services/args.go +++ b/services/args.go @@ -149,6 +149,10 @@ type HTTPArgs struct { LocalIPS *[]string DNSAddress *string DNSTTL *int + LocalKey *string + ParentKey *string + LocalCompress *bool + ParentCompress *bool } type UDPArgs struct { Parent *string @@ -195,6 +199,10 @@ type SocksArgs struct { LocalIPS *[]string DNSAddress *string DNSTTL *int + LocalKey *string + ParentKey *string + LocalCompress *bool + ParentCompress *bool } type SPSArgs struct { Parent *string @@ -220,6 +228,10 @@ type SPSArgs struct { AuthURLRetry *int LocalIPS *[]string ParentAuth *string + LocalKey *string + ParentKey *string + LocalCompress *bool + ParentCompress *bool } func (a *SPSArgs) Protocol() string { diff --git a/services/http.go b/services/http.go index 8d3c29e..4ca6203 100644 --- a/services/http.go +++ b/services/http.go @@ -8,6 +8,7 @@ import ( "net" "runtime/debug" "snail007/proxy/utils" + "snail007/proxy/utils/conncrypt" "strconv" "strings" "time" @@ -201,6 +202,14 @@ func (s *HTTP) callback(inConn net.Conn) { log.Printf("http(s) conn handler crashed with err : %s \nstack: %s", err, string(debug.Stack())) } }() + if *s.cfg.LocalCompress { + inConn = utils.NewCompConn(inConn) + } + if *s.cfg.LocalKey != "" { + inConn = conncrypt.New(inConn, &conncrypt.Config{ + Password: *s.cfg.LocalKey, + }) + } var err interface{} var req utils.HTTPRequest req, err = utils.NewHTTPRequest(&inConn, 4096, s.IsBasicAuth(), &s.basicAuth) @@ -280,6 +289,15 @@ func (s *HTTP) OutToTCP(useProxy bool, address string, inConn *net.Conn, req *ut utils.CloseConn(inConn) return } + if *s.cfg.ParentCompress { + outConn = utils.NewCompConn(outConn) + } + if *s.cfg.ParentKey != "" { + outConn = conncrypt.New(outConn, &conncrypt.Config{ + Password: *s.cfg.ParentKey, + }) + } + outAddr := outConn.RemoteAddr().String() //outLocalAddr := outConn.LocalAddr().String() if req.IsHTTPS() && (!useProxy || *s.cfg.ParentType == "ssh") { diff --git a/services/socks.go b/services/socks.go index e3ee6d2..4affee3 100644 --- a/services/socks.go +++ b/services/socks.go @@ -9,6 +9,7 @@ import ( "runtime/debug" "snail007/proxy/utils" "snail007/proxy/utils/aes" + "snail007/proxy/utils/conncrypt" "snail007/proxy/utils/socks" "strings" "time" @@ -361,6 +362,14 @@ func (s *Socks) socksConnCallback(inConn net.Conn) { inConn.Close() } }() + if *s.cfg.LocalCompress { + inConn = utils.NewCompConn(inConn) + } + if *s.cfg.LocalKey != "" { + inConn = conncrypt.New(inConn, &conncrypt.Config{ + Password: *s.cfg.LocalKey, + }) + } //协商开始 //method select request @@ -521,6 +530,7 @@ func (s *Socks) proxyTCP(inConn *net.Conn, methodReq socks.MethodsRequest, reque request.TCPReply(socks.REP_NETWOR_UNREACHABLE) return } + log.Printf("use proxy %v : %s", useProxy, request.Addr()) request.TCPReply(socks.REP_SUCCESS) @@ -557,6 +567,14 @@ func (s *Socks) getOutConn(methodBytes, reqBytes []byte, host string) (outConn n err = fmt.Errorf("connect fail,%s", err) return } + if *s.cfg.ParentCompress { + outConn = utils.NewCompConn(outConn) + } + if *s.cfg.ParentKey != "" { + outConn = conncrypt.New(outConn, &conncrypt.Config{ + Password: *s.cfg.ParentKey, + }) + } var buf = make([]byte, 1024) //var n int outConn.SetDeadline(time.Now().Add(time.Millisecond * time.Duration(*s.cfg.Timeout))) diff --git a/services/sps.go b/services/sps.go index 139ff88..4bf5375 100644 --- a/services/sps.go +++ b/services/sps.go @@ -10,6 +10,7 @@ import ( "net" "runtime/debug" "snail007/proxy/utils" + "snail007/proxy/utils/conncrypt" "snail007/proxy/utils/socks" "strconv" "strings" @@ -142,6 +143,14 @@ func (s *SPS) callback(inConn net.Conn) { log.Printf("%s conn handler crashed with err : %s \nstack: %s", s.cfg.Protocol(), err, string(debug.Stack())) } }() + if *s.cfg.LocalCompress { + inConn = utils.NewCompConn(inConn) + } + if *s.cfg.LocalKey != "" { + inConn = conncrypt.New(inConn, &conncrypt.Config{ + Password: *s.cfg.LocalKey, + }) + } var err error switch *s.cfg.ParentType { case TYPE_KCP: @@ -232,7 +241,14 @@ func (s *SPS) OutToTCP(inConn *net.Conn) (err error) { utils.CloseConn(inConn) return } - + if *s.cfg.ParentCompress { + outConn = utils.NewCompConn(outConn) + } + if *s.cfg.ParentKey != "" { + outConn = conncrypt.New(outConn, &conncrypt.Config{ + Password: *s.cfg.ParentKey, + }) + } //ask parent for connect to target address if *s.cfg.ParentServiceType == "http" { //http parent diff --git a/utils/conncrypt/conncrypt.go b/utils/conncrypt/conncrypt.go new file mode 100644 index 0000000..f3115e7 --- /dev/null +++ b/utils/conncrypt/conncrypt.go @@ -0,0 +1,95 @@ +package conncrypt + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "hash" + "io" + "net" + + "golang.org/x/crypto/pbkdf2" +) + +//Confg defaults +const DefaultIterations = 2048 +const DefaultKeySize = 32 //256bits +var DefaultHashFunc = sha256.New +var DefaultSalt = []byte(` +(;QUHj.BQ?RXzYSO]ifkXp/G!kFmWyXyEV6Nt!d|@bo+N$L9+SOd,6acYKY_ec+(x"R";\'4&fTAVu92GVA-wxBptOTM^2,iP5%)wnhW +hwk=]Snsgymt!3gbP2pe=J//}1a?lp9ej=&TB!C_V(cT2?z8wyoL_-13fd[] +`) //salt must be predefined in order to derive the same key + +//Config stores the PBKDF2 key generation parameters +type Config struct { + Password string + Salt []byte + Iterations int + KeySize int + HashFunc func() hash.Hash +} + +//New creates an AES encrypted net.Conn by generating +//a key using PBKDF2 with the provided configuration +func New(conn net.Conn, c *Config) net.Conn { + //set defaults + if len(c.Salt) == 0 { + c.Salt = DefaultSalt + } + if c.Iterations == 0 { + c.Iterations = DefaultIterations + } + if c.KeySize != 16 && c.KeySize != 24 && c.KeySize != 32 { + c.KeySize = DefaultKeySize + } + if c.HashFunc == nil { + c.HashFunc = DefaultHashFunc + } + + //generate key + key := pbkdf2.Key([]byte(c.Password), c.Salt, c.Iterations, c.KeySize, c.HashFunc) + + // could use scrypt, but it's a bit slow... + // dk, err := scrypt.Key([]byte(c.Password), c.Salt, 16384, 8, 1, 32) + + //key will be always be the correct size so this will never error + conn, _ = NewFromKey(conn, key) + return conn +} + +//NewFromKey creates an AES encrypted net.Conn using the provided key +func NewFromKey(conn net.Conn, key []byte) (net.Conn, error) { + block, err := aes.NewCipher([]byte(key)) + if err != nil { + return nil, err + } + //hash(key) -> read IV + riv := DefaultHashFunc().Sum(key) + rstream := cipher.NewCFBDecrypter(block, riv[:aes.BlockSize]) + reader := &cipher.StreamReader{S: rstream, R: conn} + //hash(read IV) -> write IV + wiv := DefaultHashFunc().Sum(riv) + wstream := cipher.NewCFBEncrypter(block, wiv[:aes.BlockSize]) + writer := &cipher.StreamWriter{S: wstream, W: conn} + + return &cryptoConn{ + Conn: conn, + r: reader, + w: writer, + }, nil +} + +type cryptoConn struct { + net.Conn + r io.Reader + w io.Writer +} + +//replace read and write methods +func (c *cryptoConn) Read(p []byte) (int, error) { + return c.r.Read(p) +} +func (c *cryptoConn) Write(p []byte) (int, error) { + return c.w.Write(p) +} diff --git a/utils/structs.go b/utils/structs.go index 30f6bc3..dd8daf7 100644 --- a/utils/structs.go +++ b/utils/structs.go @@ -749,8 +749,16 @@ func NewCompStream(conn net.Conn) *CompStream { c.r = snappy.NewReader(conn) return c } +func NewCompConn(conn net.Conn) net.Conn { + c := CompStream{} + c.conn = conn + c.w = snappy.NewBufferedWriter(conn) + c.r = snappy.NewReader(conn) + return &c +} type CompStream struct { + net.Conn conn net.Conn w *snappy.Writer r *snappy.Reader From eb2f055e07ee5dc46a5d197dad1335847ddbdefb Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 13 Apr 2018 18:14:32 +0800 Subject: [PATCH 51/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 172 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 166 insertions(+), 6 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index 6ba336c..e50a63b 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -85,7 +85,9 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - [1.9 HTTP(S)反向代理](#19-https反向代理) - [1.10 HTTP(S)透明代理](#110-https透明代理) - [1.11 自定义DNS](#111-自定义dns) - - [1.12 查看帮助](#112-查看帮助) + - [1.12 自定义加密](#112-自定义加密) + - [1.13 压缩传输](#113-压缩传输) + - [1.14 查看帮助](#114-查看帮助) - [2. TCP代理](#2tcp代理) - [2.1 普通一级TCP代理](#21普通一级tcp代理) - [2.2 普通二级TCP代理](#22普通二级tcp代理) @@ -121,7 +123,9 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - [5.7 认证](#57认证) - [5.8 KCP协议传输](#58kcp协议传输) - [5.9 自定义DNS](#59自定义dns) - - [5.10 查看帮助](#510查看帮助) + - [5.10 自定义加密](#510-自定义加密) + - [5.11 压缩传输](#511-压缩传输) + - [5.12 查看帮助](#512查看帮助) - [6. 代理协议转换](#6代理协议转换) - [6.1 功能介绍](#61-功能介绍) - [6.2 HTTP(S)转HTTP(S)+SOCKS5](#62-https转httpssocks5) @@ -129,7 +133,9 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - [6.4 链式连接](#64-链式连接) - [6.5 监听多个端口](#65-监听多个端口) - [6.6 认证功能](#66-认证功能) - - [6.7 查看帮助](#67-查看帮助) + - [6.7 自定义加密](#67-自定义加密) + - [6.8 压缩传输](#68-压缩传输) + - [6.9 查看帮助](#69-查看帮助) - [7. KCP配置](#7kcp配置) - [7.1 配置介绍](#71-配置介绍) - [7.2 详细配置](#72-详细配置) @@ -358,7 +364,53 @@ iptables -t nat -A OUTPUT -p tcp -j PROXY 比如: `./proxy http -p ":33080" --dns-address "8.8.8.8:53" --dns-ttl 300` -#### **1.12 查看帮助** +#### **1.12 自定义加密** +proxy的http(s)代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之前进行自定义 +加密,也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可, +加密分为两个部分,一部分是本地(-z)是否加密解密,一部分是与上级(-Z)传输是否加密解密. +自定义加密要求两端都是proxy才可以,下面分别用二级,三级为例: + +**二级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy http -t tcp -z demo_password -p :7777` +本地二级执行: +`proxy http -T tcp -P 2.2.2.2:777 -Z demo_password -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级加密传输访问目标网站. + + +**三级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy http -t tcp -z demo_password -p :7777` +二级vps(ip:3.3.3.3)上执行: +`proxy http -T tcp -P 2.2.2.2:7777 -Z demo_password -t tcp -z other_password -p :8888` +本地三级执行: +`proxy http -T tcp -P 3.3.3.3:8888 -Z other_password -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级加密传输访问目标网站. + +#### **1.13 压缩传输** +proxy的http(s)代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,在加密之前还可以对数据进行压缩, +也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的,压缩分为两个部分,一部分是本地(-m)是否压缩传输, +一部分是与上级(-M)传输是否压缩. +压缩要求两端都是proxy才可以,压缩也在一定程度上保护了(加密)数据,下面分别用二级,三级为例: + +**二级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy http -t tcp -m -p :7777` +本地二级执行: +`proxy http -T tcp -P 2.2.2.2:777 -M -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级压缩传输访问目标网站. + + +**三级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy http -t tcp -m -p :7777` +二级vps(ip:3.3.3.3)上执行: +`proxy http -T tcp -P 2.2.2.2:7777 -M -t tcp -m -p :8888` +本地三级执行: +`proxy http -T tcp -P 3.3.3.3:8888 -M -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级压缩传输访问目标网站. + +#### **1.14 查看帮助** `./proxy help http` ### **2.TCP代理** @@ -678,7 +730,59 @@ KCP协议需要--kcp-key参数设置一个密码用于加密解密数据 比如: `./proxy socks -p ":33080" --dns-address "8.8.8.8:53" --dns-ttl 300` -#### **5.10.查看帮助** +#### **5.10 自定义加密** +proxy的socks代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之前进行自定义加密, +也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可, +加密分为两个部分,一部分是本地(-z)是否加密解密,一部分是与上级(-Z)传输是否加密解密. + +自定义加密要求两端都是proxy才可以. + +下面分别用二级,三级为例: + +**二级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy socks -t tcp -z demo_password -p :7777` +本地二级执行: +`proxy socks -T tcp -P 2.2.2.2:777 -Z demo_password -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级加密传输访问目标网站. + + +**三级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy socks -t tcp -z demo_password -p :7777` +二级vps(ip:3.3.3.3)上执行: +`proxy socks -T tcp -P 2.2.2.2:7777 -Z demo_password -t tcp -z other_password -p :8888` +本地三级执行: +`proxy socks -T tcp -P 3.3.3.3:8888 -Z other_password -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级加密传输访问目标网站. + +#### **5.11 压缩传输** +proxy的socks代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在加密之前还可以 +对数据进行压缩,也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的,压缩分为两个部分, +一部分是本地(-m)是否压缩传输,一部分是与上级(-M)传输是否压缩. + +压缩要求两端都是proxy才可以,压缩也在一定程度上保护了(加密)数据. + +下面分别用二级,三级为例: + +**二级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy socks -t tcp -m -p :7777` +本地二级执行: +`proxy socks -T tcp -P 2.2.2.2:777 -M -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级压缩传输访问目标网站. + + +**三级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy socks -t tcp -m -p :7777` +二级vps(ip:3.3.3.3)上执行: +`proxy socks -T tcp -P 2.2.2.2:7777 -M -t tcp -m -p :8888` +本地三级执行: +`proxy socks -T tcp -P 3.3.3.3:8888 -M -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级压缩传输访问目标网站. + +#### **5.12.查看帮助** `./proxy help socks` ### **6.代理协议转换** @@ -777,7 +881,63 @@ target:如果客户端是http(s)代理请求,这里代表的是请求的完整ur 如果没有-a或-F或--auth-url参数,就是关闭本地认证. 如果没有-A参数,连接上级不使用认证. -#### **6.7 查看帮助** + +#### **6.7 自定义加密** +proxy的sps代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之前进行自定义加密, +也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可, +加密分为两个部分,一部分是本地(-z)是否加密解密,一部分是与上级(-Z)传输是否加密解密. + +自定义加密要求两端都是proxy才可以. + +下面分别用二级,三级为例: + +假设已经存在一个http(s)代理:`6.6.6.6:6666` + +**二级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy sps -S http -T tcp -P 6.6.6.6:6666 -t tcp -z demo_password -p :7777` +本地二级执行: +`proxy sps -T tcp -P 2.2.2.2:777 -Z demo_password -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级加密传输访问目标网站. + + +**三级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy sps -S http -T tcp -P 6.6.6.6:6666 -t tcp -z demo_password -p :7777` +二级vps(ip:3.3.3.3)上执行: +`proxy sps -T tcp -P 2.2.2.2:7777 -Z demo_password -t tcp -z other_password -p :8888` +本地三级执行: +`proxy sps -T tcp -P 3.3.3.3:8888 -Z other_password -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级加密传输访问目标网站. + +#### **6.8 压缩传输** +proxy的sps代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在加密之前还可以 +对数据进行压缩,也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的,压缩分为两个部分, +一部分是本地(-m)是否压缩传输,一部分是与上级(-M)传输是否压缩. + +压缩要求两端都是proxy才可以,压缩也在一定程度上保护了(加密)数据. + +下面分别用二级,三级为例: + +**二级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy sps -t tcp -m -p :7777` +本地二级执行: +`proxy sps -T tcp -P 2.2.2.2:777 -M -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级压缩传输访问目标网站. + + +**三级实例** +一级vps(ip:2.2.2.2)上执行: +`proxy sps -t tcp -m -p :7777` +二级vps(ip:3.3.3.3)上执行: +`proxy sps -T tcp -P 2.2.2.2:7777 -M -t tcp -m -p :8888` +本地三级执行: +`proxy sps -T tcp -P 3.3.3.3:8888 -M -t tcp -p :8080` +这样通过本地代理8080访问网站的时候就是通过与上级压缩传输访问目标网站. + + +#### **6.9 查看帮助** `./proxy help sps` ### **7.KCP配置** From 9aff5eda3881d11e25698fb8e1609f3fd9b4c857 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 13 Apr 2018 18:21:26 +0800 Subject: [PATCH 52/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_ZH.md b/README_ZH.md index e50a63b..c65e12e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -24,6 +24,8 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - 反向代理,支持直接把域名解析到proxy监听的ip,然后proxy就会帮你代理访问需要访问的HTTP(S)网站. - 透明HTTP(S)代理,配合iptables,在网关直接把出去的80,443方向的流量转发到proxy,就能实现无感知的智能路由器代理. - 协议转换,可以把已经存在的HTTP(S)或SOCKS5代理转换为一个端口同时支持HTTP(S)和SOCKS5代理,转换后的SOCKS5代理不支持UDP功能,同时支持强大的级联认证功能。 +- 自定义底层加密传输,http(s)\sps\socks代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之前进行自定义加密,也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可。 +- 底层压缩高效传输,http(s)\sps\socks代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在加密之前还可以对数据进行压缩,也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的。 ### Why need these? - 当由于某某原因,我们不能访问我们在其它地方的服务,我们可以通过多个相连的proxy节点建立起一个安全的隧道访问我们的服务. From 47790d1d584336c960193cf08937f5f1023f9715 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 13 Apr 2018 18:23:26 +0800 Subject: [PATCH 53/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index c65e12e..fa9e6e4 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -24,7 +24,7 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - 反向代理,支持直接把域名解析到proxy监听的ip,然后proxy就会帮你代理访问需要访问的HTTP(S)网站. - 透明HTTP(S)代理,配合iptables,在网关直接把出去的80,443方向的流量转发到proxy,就能实现无感知的智能路由器代理. - 协议转换,可以把已经存在的HTTP(S)或SOCKS5代理转换为一个端口同时支持HTTP(S)和SOCKS5代理,转换后的SOCKS5代理不支持UDP功能,同时支持强大的级联认证功能。 -- 自定义底层加密传输,http(s)\sps\socks代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之前进行自定义加密,也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可。 +- 自定义底层加密传输,http(s)\sps\socks代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之后进行自定义加密,也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可。 - 底层压缩高效传输,http(s)\sps\socks代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在加密之前还可以对数据进行压缩,也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的。 ### Why need these? @@ -367,7 +367,7 @@ iptables -t nat -A OUTPUT -p tcp -j PROXY `./proxy http -p ":33080" --dns-address "8.8.8.8:53" --dns-ttl 300` #### **1.12 自定义加密** -proxy的http(s)代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之前进行自定义 +proxy的http(s)代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之后进行自定义 加密,也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可, 加密分为两个部分,一部分是本地(-z)是否加密解密,一部分是与上级(-Z)传输是否加密解密. 自定义加密要求两端都是proxy才可以,下面分别用二级,三级为例: @@ -733,7 +733,7 @@ KCP协议需要--kcp-key参数设置一个密码用于加密解密数据 `./proxy socks -p ":33080" --dns-address "8.8.8.8:53" --dns-ttl 300` #### **5.10 自定义加密** -proxy的socks代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之前进行自定义加密, +proxy的socks代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之后进行自定义加密, 也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可, 加密分为两个部分,一部分是本地(-z)是否加密解密,一部分是与上级(-Z)传输是否加密解密. @@ -885,7 +885,7 @@ target:如果客户端是http(s)代理请求,这里代表的是请求的完整ur #### **6.7 自定义加密** -proxy的sps代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之前进行自定义加密, +proxy的sps代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之后进行自定义加密, 也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可, 加密分为两个部分,一部分是本地(-z)是否加密解密,一部分是与上级(-Z)传输是否加密解密. From f0ed6d73e460312313c2a8201719cbd9c5bd33e0 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 13 Apr 2018 18:31:12 +0800 Subject: [PATCH 54/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README_ZH.md b/README_ZH.md index fa9e6e4..b8deaab 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -25,7 +25,7 @@ Proxy是golang实现的高性能http,https,websocket,tcp,udp,socks5代理服务 - 透明HTTP(S)代理,配合iptables,在网关直接把出去的80,443方向的流量转发到proxy,就能实现无感知的智能路由器代理. - 协议转换,可以把已经存在的HTTP(S)或SOCKS5代理转换为一个端口同时支持HTTP(S)和SOCKS5代理,转换后的SOCKS5代理不支持UDP功能,同时支持强大的级联认证功能。 - 自定义底层加密传输,http(s)\sps\socks代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之后进行自定义加密,也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可。 -- 底层压缩高效传输,http(s)\sps\socks代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在加密之前还可以对数据进行压缩,也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的。 +- 底层压缩高效传输,http(s)\sps\socks代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在加密之后还可以对数据进行压缩,也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的。 ### Why need these? - 当由于某某原因,我们不能访问我们在其它地方的服务,我们可以通过多个相连的proxy节点建立起一个安全的隧道访问我们的服务. @@ -390,7 +390,7 @@ proxy的http(s)代理在tcp之上可以通过tls标准加密以及kcp协议加 这样通过本地代理8080访问网站的时候就是通过与上级加密传输访问目标网站. #### **1.13 压缩传输** -proxy的http(s)代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,在加密之前还可以对数据进行压缩, +proxy的http(s)代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,在自定义加密之前还可以对数据进行压缩, 也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的,压缩分为两个部分,一部分是本地(-m)是否压缩传输, 一部分是与上级(-M)传输是否压缩. 压缩要求两端都是proxy才可以,压缩也在一定程度上保护了(加密)数据,下面分别用二级,三级为例: @@ -733,8 +733,7 @@ KCP协议需要--kcp-key参数设置一个密码用于加密解密数据 `./proxy socks -p ":33080" --dns-address "8.8.8.8:53" --dns-ttl 300` #### **5.10 自定义加密** -proxy的socks代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之后进行自定义加密, -也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可, +proxy的socks代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之后进行自定义加密,也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可, 加密分为两个部分,一部分是本地(-z)是否加密解密,一部分是与上级(-Z)传输是否加密解密. 自定义加密要求两端都是proxy才可以. @@ -759,7 +758,7 @@ proxy的socks代理在tcp之上可以通过tls标准加密以及kcp协议加密t 这样通过本地代理8080访问网站的时候就是通过与上级加密传输访问目标网站. #### **5.11 压缩传输** -proxy的socks代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在加密之前还可以 +proxy的socks代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在自定义加密之前还可以 对数据进行压缩,也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的,压缩分为两个部分, 一部分是本地(-m)是否压缩传输,一部分是与上级(-M)传输是否压缩. @@ -885,9 +884,9 @@ target:如果客户端是http(s)代理请求,这里代表的是请求的完整ur #### **6.7 自定义加密** -proxy的sps代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之后进行自定义加密, -也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义一个密码即可, -加密分为两个部分,一部分是本地(-z)是否加密解密,一部分是与上级(-Z)传输是否加密解密. +proxy的sps代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp数据,除此之外还支持在tls和kcp之后进行 +自定义加密,也就是说自定义加密和tls|kcp是可以联合使用的,内部采用AES256加密,使用的时候只需要自己定义 +一个密码即可,加密分为两个部分,一部分是本地(-z)是否加密解密,一部分是与上级(-Z)传输是否加密解密. 自定义加密要求两端都是proxy才可以. @@ -913,7 +912,7 @@ proxy的sps代理在tcp之上可以通过tls标准加密以及kcp协议加密tcp 这样通过本地代理8080访问网站的时候就是通过与上级加密传输访问目标网站. #### **6.8 压缩传输** -proxy的sps代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在加密之前还可以 +proxy的sps代理在tcp之上可以通过自定义加密和tls标准加密以及kcp协议加密tcp数据,在自定义加密之前还可以 对数据进行压缩,也就是说压缩功能和自定义加密和tls|kcp是可以联合使用的,压缩分为两个部分, 一部分是本地(-m)是否压缩传输,一部分是与上级(-M)传输是否压缩. From 19baccb91afdecf2c2bab200d1b67cad6e6e58bf Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Mon, 16 Apr 2018 18:33:10 +0800 Subject: [PATCH 55/76] add linux .so and windows .dll sdk support, Signed-off-by: arraykeys@gmail.com --- sdk/README.md | 63 +++++++++++++++++++++++++++++-- sdk/{ => android-ios}/release.sh | 10 ++++- sdk/{ => android-ios}/sdk.go | 0 sdk/windows-linux/ieshims.dll | Bin 0 -> 197632 bytes sdk/windows-linux/release.sh | 26 +++++++++++++ sdk/windows-linux/sdk.go | 26 +++++++++++++ 6 files changed, 119 insertions(+), 6 deletions(-) rename sdk/{ => android-ios}/release.sh (74%) rename sdk/{ => android-ios}/sdk.go (100%) create mode 100644 sdk/windows-linux/ieshims.dll create mode 100755 sdk/windows-linux/release.sh create mode 100644 sdk/windows-linux/sdk.go diff --git a/sdk/README.md b/sdk/README.md index e8b020f..a238c7f 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -1,12 +1,18 @@ # Proxy SDK 使用说明 -proxy使用gombile实现了一份go代码编译为android和ios平台下面可以直接调用的sdk类库, +支持以下平台: +- Android,.arr库 +- IOS, +- Windows,.dll库 +- Linux,.so库 + +proxy使用gombile实现了一份go代码编译为android和ios和windows平台下面可以直接调用的sdk类库, 基于这些类库,APP开发者可以轻松的开发出各种形式的代理工具. -### 下面分平台介绍SDK的用法 +# 下面分平台介绍SDK的用法 -#### Android SDK +## Android SDK [![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy/) [![license](https://img.shields.io/github/license/snail007/goproxy.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy/total.svg?style=plastic)](https://github.com/snail007/goproxy/releases) [![download](https://img.shields.io/github/release/snail007/goproxy.svg?style=plastic)](https://github.com/snail007/goproxy/releases) @@ -53,12 +59,61 @@ Proxy.stop(args);//这里传递http也可以,最终使用的就是args里面的 //停止完毕 ``` - +## IOS SDK + +[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy/) [![license](https://img.shields.io/github/license/snail007/goproxy.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy/total.svg?style=plastic)](https://github.com/snail007/goproxy/releases) [![download](https://img.shields.io/github/release/snail007/goproxy.svg?style=plastic)](https://github.com/snail007/goproxy/releases) + +[点击下载IOS-SDK](https://github.com/snail007/goproxy-sdk-ios/releases) +在IOS系统提供的sdk形式是一个后缀为.framework的类库文件夹,开发的时候只需要把类库文件引入项目,然后调用方法即可. ### IOS-SDK使用实例 #### todo +## Windows SDK +[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy/) [![license](https://img.shields.io/github/license/snail007/goproxy.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy/total.svg?style=plastic)](https://github.com/snail007/goproxy/releases) [![download](https://img.shields.io/github/release/snail007/goproxy.svg?style=plastic)](https://github.com/snail007/goproxy/releases) + +[点击下载Windows-SDK](https://github.com/snail007/goproxy-sdk-windows/releases) +在Windows系统提供的sdk形式是一个后缀为.dll的类库文件,开发的时候只需要把dll类库文件加载,然后调用方法即可. + +### Windows-SDK使用实例 + +#### todo + +## Linux SDK +[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy/) [![license](https://img.shields.io/github/license/snail007/goproxy.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy/total.svg?style=plastic)](https://github.com/snail007/goproxy/releases) [![download](https://img.shields.io/github/release/snail007/goproxy.svg?style=plastic)](https://github.com/snail007/goproxy/releases) + +[点击下载Linux-SDK](https://github.com/snail007/goproxy-sdk-linux/releases) +在Linux系统提供的sdk形式是一个后缀为.so的类库文件,开发的时候只需要把so类库加载,调用方法即可. + +### Linux-SDK使用实例 +Linux下面使用的sdk是so文件即proxy-sdk.so,下面写一个简单的C程序示例,调用so库里面的方法. + +`vi test-proxy.c` + +```c +#include +#include "proxy-sdk.h" + +int main() { + printf("This is demo application.\n"); + char *str = "http -t tcp -p :38080"; + //启动服务,返回空字符串说明启动成功;返回非空字符串说明启动失败,返回的字符串是错误原因 + printf("start result %s\n",Start(str)); + //停止服务,没有返回值 + Stop(str); + //服务是否在运行,返回0是没有运行,返回1正在运行 + printf("is running result %d\n",IsRunning(str)); + return 0; +} +``` + +#### 编译test-proxy.c #### +`export LD_LIBRARY_PATH=./ && gcc -o test-proxy test.c proxy-sdk.so` + +#### 执行 #### +`./test-proxy` + ### 关于服务 proxy的服务有11种,分别是: diff --git a/sdk/release.sh b/sdk/android-ios/release.sh similarity index 74% rename from sdk/release.sh rename to sdk/android-ios/release.sh index 6b0879c..04bcdf9 100755 --- a/sdk/release.sh +++ b/sdk/android-ios/release.sh @@ -5,11 +5,17 @@ rm -rf ios mkdir android mkdir ios -#all-in-one +#android gomobile bind -v -target=android -javapkg=snail007 -ldflags="-s -w" mv proxy.aar android/snail007.goproxy.sdk.aar mv proxy-sources.jar android/snail007.goproxy.sdk-sources.jar -cp README.md android +cp ../README.md android tar zcfv sdk-android-${VER}.tar.gz android rm -rf android + +#ios XCode required +#gomobile bind -v -target=ios -ldflags="-s -w" +#proxy + + echo "done." diff --git a/sdk/sdk.go b/sdk/android-ios/sdk.go similarity index 100% rename from sdk/sdk.go rename to sdk/android-ios/sdk.go diff --git a/sdk/windows-linux/ieshims.dll b/sdk/windows-linux/ieshims.dll new file mode 100644 index 0000000000000000000000000000000000000000..88b03f0f29176b60f88e4bbf61c0a1ddd97c0e14 GIT binary patch literal 197632 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 Date: Tue, 17 Apr 2018 13:36:19 +0800 Subject: [PATCH 56/76] Signed-off-by: arraykeys@gmail.com --- sdk/android-ios/release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/android-ios/release.sh b/sdk/android-ios/release.sh index 04bcdf9..219f8c4 100755 --- a/sdk/android-ios/release.sh +++ b/sdk/android-ios/release.sh @@ -15,7 +15,7 @@ rm -rf android #ios XCode required #gomobile bind -v -target=ios -ldflags="-s -w" -#proxy - +tar zcfv sdk-ios-${VER}.tar.gz Proxy.framework +rm -rf Proxy.framework echo "done." From 9d4930b29d0a64e1b9a2b7a38259cb085cc3c0f0 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 17 Apr 2018 13:45:31 +0800 Subject: [PATCH 57/76] Signed-off-by: arraykeys@gmail.com --- .gitignore | 1 + sdk/android-ios/.gitignore | 7 ++++ sdk/windows-linux/.gitignore | 6 +++ vendor/github.com/golang/snappy/.gitignore | 16 ++++++++ vendor/github.com/miekg/dns/.gitignore | 4 ++ vendor/github.com/pkg/errors/.gitignore | 24 +++++++++++ .../github.com/templexxx/cpufeat/.gitignore | 14 +++++++ .../templexxx/reedsolomon/.gitignore | 40 +++++++++++++++++++ vendor/github.com/templexxx/xor/.gitignore | 18 +++++++++ vendor/github.com/xtaci/kcp-go/.gitignore | 24 +++++++++++ vendor/github.com/xtaci/smux/.gitignore | 24 +++++++++++ 11 files changed, 178 insertions(+) create mode 100644 sdk/android-ios/.gitignore create mode 100644 sdk/windows-linux/.gitignore create mode 100644 vendor/github.com/golang/snappy/.gitignore create mode 100644 vendor/github.com/miekg/dns/.gitignore create mode 100755 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/templexxx/cpufeat/.gitignore create mode 100644 vendor/github.com/templexxx/reedsolomon/.gitignore create mode 100644 vendor/github.com/templexxx/xor/.gitignore create mode 100644 vendor/github.com/xtaci/kcp-go/.gitignore create mode 100644 vendor/github.com/xtaci/smux/.gitignore diff --git a/.gitignore b/.gitignore index e802753..2ec2aa7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ proxy *.exe *.exe~ .* +!.gitignore release-* proxy.crt proxy.key diff --git a/sdk/android-ios/.gitignore b/sdk/android-ios/.gitignore new file mode 100644 index 0000000..6e5e3cc --- /dev/null +++ b/sdk/android-ios/.gitignore @@ -0,0 +1,7 @@ +*.jar +*.aar +*.tar.gz +ios +android +Proxy.framework + \ No newline at end of file diff --git a/sdk/windows-linux/.gitignore b/sdk/windows-linux/.gitignore new file mode 100644 index 0000000..b3d0383 --- /dev/null +++ b/sdk/windows-linux/.gitignore @@ -0,0 +1,6 @@ +proxy-sdk.dll +proxy-sdk.h +proxy-sdk.so +proxy-sdk.a +*.tar.gz +test.c diff --git a/vendor/github.com/golang/snappy/.gitignore b/vendor/github.com/golang/snappy/.gitignore new file mode 100644 index 0000000..042091d --- /dev/null +++ b/vendor/github.com/golang/snappy/.gitignore @@ -0,0 +1,16 @@ +cmd/snappytool/snappytool +testdata/bench + +# These explicitly listed benchmark data files are for an obsolete version of +# snappy_test.go. +testdata/alice29.txt +testdata/asyoulik.txt +testdata/fireworks.jpeg +testdata/geo.protodata +testdata/html +testdata/html_x_4 +testdata/kppkn.gtb +testdata/lcet10.txt +testdata/paper-100k.pdf +testdata/plrabn12.txt +testdata/urls.10K diff --git a/vendor/github.com/miekg/dns/.gitignore b/vendor/github.com/miekg/dns/.gitignore new file mode 100644 index 0000000..776cd95 --- /dev/null +++ b/vendor/github.com/miekg/dns/.gitignore @@ -0,0 +1,4 @@ +*.6 +tags +test.out +a.out diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore new file mode 100755 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/templexxx/cpufeat/.gitignore b/vendor/github.com/templexxx/cpufeat/.gitignore new file mode 100644 index 0000000..a1338d6 --- /dev/null +++ b/vendor/github.com/templexxx/cpufeat/.gitignore @@ -0,0 +1,14 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ diff --git a/vendor/github.com/templexxx/reedsolomon/.gitignore b/vendor/github.com/templexxx/reedsolomon/.gitignore new file mode 100644 index 0000000..902cec0 --- /dev/null +++ b/vendor/github.com/templexxx/reedsolomon/.gitignore @@ -0,0 +1,40 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +/.idea +/backup +/loopunroll/ +cpu.out +mathtool/galois/ +mathtool/matrix/ +mem.out +/examples/ +/.DS_Store +/mathtool/cntinverse +/invert +/bakcup +/buf.svg +*.svg +*.out +/escape diff --git a/vendor/github.com/templexxx/xor/.gitignore b/vendor/github.com/templexxx/xor/.gitignore new file mode 100644 index 0000000..bccf12c --- /dev/null +++ b/vendor/github.com/templexxx/xor/.gitignore @@ -0,0 +1,18 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 +.glide/ +/backup/ +/backup2/ +/.idea +/backup3/ diff --git a/vendor/github.com/xtaci/kcp-go/.gitignore b/vendor/github.com/xtaci/kcp-go/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/xtaci/kcp-go/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/xtaci/smux/.gitignore b/vendor/github.com/xtaci/smux/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/xtaci/smux/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof From 57935c2296f76ddb394baad1b07d902ccf539ce1 Mon Sep 17 00:00:00 2001 From: arraykeys Date: Tue, 17 Apr 2018 13:57:52 +0800 Subject: [PATCH 58/76] no message --- sdk/android-ios/release.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sdk/android-ios/release.sh b/sdk/android-ios/release.sh index 219f8c4..3f6a2c8 100755 --- a/sdk/android-ios/release.sh +++ b/sdk/android-ios/release.sh @@ -14,8 +14,9 @@ tar zcfv sdk-android-${VER}.tar.gz android rm -rf android #ios XCode required -#gomobile bind -v -target=ios -ldflags="-s -w" -tar zcfv sdk-ios-${VER}.tar.gz Proxy.framework -rm -rf Proxy.framework +gomobile bind -v -target=ios -ldflags="-s -w" +mv Proxy.framework ios +tar zcfv sdk-ios-${VER}.tar.gz ios +rm -rf ios echo "done." From 0aa0e7c5506fd3c264ef92ade5da105561016db3 Mon Sep 17 00:00:00 2001 From: arraykeys Date: Tue, 17 Apr 2018 17:44:16 +0800 Subject: [PATCH 59/76] add windows sdk demo --- sdk/README.md | 159 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 146 insertions(+), 13 deletions(-) diff --git a/sdk/README.md b/sdk/README.md index a238c7f..0469f0c 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -2,19 +2,19 @@ # Proxy SDK 使用说明 支持以下平台: -- Android,.arr库 -- IOS, -- Windows,.dll库 -- Linux,.so库 +- Android,`.arr`库 +- IOS,`.framework`库 +- Windows,`.dll`库 +- Linux,`.so`库 -proxy使用gombile实现了一份go代码编译为android和ios和windows平台下面可以直接调用的sdk类库, -基于这些类库,APP开发者可以轻松的开发出各种形式的代理工具. +proxy使用gombile实现了一份go代码编译为android和ios平台下面可以直接调用的sdk类库, +另外还为linux和windows提供sdk支持,基于这些类库,APP开发者可以轻松的开发出各种形式的代理工具。 # 下面分平台介绍SDK的用法 ## Android SDK -[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy/) [![license](https://img.shields.io/github/license/snail007/goproxy.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy/total.svg?style=plastic)](https://github.com/snail007/goproxy/releases) [![download](https://img.shields.io/github/release/snail007/goproxy.svg?style=plastic)](https://github.com/snail007/goproxy/releases) +[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-android/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-android.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-android/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-android/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-android.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-android/releases) [点击下载Android-SDK](https://github.com/snail007/goproxy-sdk-android/releases) 在Android系统提供的sdk形式是一个后缀为.aar的类库文件,开发的时候只需要把arr类库文件引入android项目即可. @@ -22,11 +22,13 @@ proxy使用gombile实现了一份go代码编译为android和ios和windows平台 ### Android-SDK使用实例 #### 1.导入包 + ```java import snail007.proxy.Porxy ``` #### 2.启动一个服务 + ```java String args="http -p :8080"; String err=Proxy.start(args); @@ -59,29 +61,158 @@ Proxy.stop(args);//这里传递http也可以,最终使用的就是args里面的 //停止完毕 ``` + ## IOS SDK -[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy/) [![license](https://img.shields.io/github/license/snail007/goproxy.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy/total.svg?style=plastic)](https://github.com/snail007/goproxy/releases) [![download](https://img.shields.io/github/release/snail007/goproxy.svg?style=plastic)](https://github.com/snail007/goproxy/releases) +[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-ios/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-ios.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-ios/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-ios/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-ios.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-ios/releases) [点击下载IOS-SDK](https://github.com/snail007/goproxy-sdk-ios/releases) 在IOS系统提供的sdk形式是一个后缀为.framework的类库文件夹,开发的时候只需要把类库文件引入项目,然后调用方法即可. ### IOS-SDK使用实例 -#### todo +#### 导入包 + +```objc +#import +``` + +#### 2.启动一个服务 + +```objc +-(IBAction)doStart:(id)sender +{ + NSString *args = @"http -p :8080"; + NSString *error = ProxyStart(args); + + if (error != nil && error.length > 0) + { + NSLog(@"start error %@",error); + }else{ + NSLog(@"启动成功"); + } +} +``` + +#### 3.判断一个服务是否在运行 + +```objc +-(IBAction)hasRunning:(id)sender; +{ + NSString *args = @"http -p :8080"; + if (ProxyIsRunning(args))//这里传递http也可以,最终使用的就是args里面的第一个参数http + { + NSLog(@"正在运行"); + }else{ + NSLog(@"没有运行"); + } +} +``` + +由于tclient和client服务的特性,目前这个方法对于服务tclient和client永远返回false. + +#### 4.停止一个服务 + +```objc +-(IBAction)doStop:(id)sender +{ + NSString *args = @"http -p :8080"; + ProxyStop(args);//这里传递http也可以,最终使用的就是args里面的第一个参数http + //停止完毕 +} +``` + ## Windows SDK -[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy/) [![license](https://img.shields.io/github/license/snail007/goproxy.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy/total.svg?style=plastic)](https://github.com/snail007/goproxy/releases) [![download](https://img.shields.io/github/release/snail007/goproxy.svg?style=plastic)](https://github.com/snail007/goproxy/releases) +[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-windows/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-windows.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-windows/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-windows/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-windows.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-windows/releases) [点击下载Windows-SDK](https://github.com/snail007/goproxy-sdk-windows/releases) 在Windows系统提供的sdk形式是一个后缀为.dll的类库文件,开发的时候只需要把dll类库文件加载,然后调用方法即可. ### Windows-SDK使用实例 +C++示例,不需要包含头文件,只需要加载proxy-sdk.dll即可,ieshims.dll需要和proxy-sdk.dll在一起。 +作者:[yjbdsky](https://github.com/yjbdsky) -#### todo +```cpp +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef char *(*GOSTART)(char *s); +typedef char *(*GOSTOP)(char *s); +typedef int(*GOISRUN)(char *s); +HMODULE GODLL = LoadLibrary("proxy-sdk.dll"); + +char * Start(char * p) +{ + if (GODLL != NULL) + { + GOSTART gostart = *(GOSTART)(GetProcAddress(GODLL, "Start")); + if (gostart != NULL){ + printf("%s\n", p); + char *ret = gostart(p); + return ret; + } + } + return "Cannot Find dll"; +} +char * Stop(char * p) +{ + if (GODLL != NULL) + { + GOSTOP gostop = *(GOSTOP)(GetProcAddress(GODLL, "Stop")); + if (gostop != NULL){ + printf("%s\n", p); + char *ret = gostop(p); + return ret; + } + } + return "Cannot Find dll"; +} + +int IsRunning(char * p) +{ + + if (GODLL != NULL) + { + GOISRUN isrun = *(GOISRUN)(GetProcAddress(GODLL, "IsRunning")); + if (isrun != NULL){ + int ret = isrun(p); + return ret; + } + FreeLibrary(GODLL); + } + return 0; +} + +int main() +{ + char *p = "http -t tcp -p :38080"; + printf("This is demo application.\n"); + char *str = "http -t tcp -p :38080"; + //启动服务,返回空字符串说明启动成功;返回非空字符串说明启动失败,返回的字符串是错误原因 + printf("start result %s\n", Start(str)); + //停止服务,没有返回值 + Stop(str); + //服务是否在运行,返回0是没有运行,返回1正在运行 + printf("is running result %d\n", IsRunning(str)); + return 0; +} + + +#ifdef __cplusplus +} +#endif +``` ## Linux SDK -[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy/) [![license](https://img.shields.io/github/license/snail007/goproxy.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy/total.svg?style=plastic)](https://github.com/snail007/goproxy/releases) [![download](https://img.shields.io/github/release/snail007/goproxy.svg?style=plastic)](https://github.com/snail007/goproxy/releases) +[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-linux/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-linux.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-linux/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-linux/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-linux.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-linux/releases) [点击下载Linux-SDK](https://github.com/snail007/goproxy-sdk-linux/releases) 在Linux系统提供的sdk形式是一个后缀为.so的类库文件,开发的时候只需要把so类库加载,调用方法即可. @@ -133,4 +264,6 @@ tclient ``` 每个服务只能启动一个,如果相同的服务启动多次,那么之前的服务会被停掉,后面启动的服务覆盖之前的服务. 上面这些服务的具体使用方式和具体参数,可以参考[proxy手册](https://github.com/snail007/goproxy/blob/master/README_ZH.md) -sdk里面的服务不支持手册里面的:--daemon和--forever参数. \ No newline at end of file +sdk里面的服务不支持手册里面的:--daemon和--forever参数. + + From d2051e6e37580498d04750ea713e908abe3aa025 Mon Sep 17 00:00:00 2001 From: arraykeys Date: Tue, 17 Apr 2018 18:24:01 +0800 Subject: [PATCH 60/76] no message --- sdk/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/README.md b/sdk/README.md index 0469f0c..46157c0 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -211,6 +211,8 @@ int main() #endif ``` +C++示例2,请移步:[GoProxyForC](https://github.com/SuperPowerLF2/GoProxyForC) + ## Linux SDK [![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-linux/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-linux.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-linux/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-linux/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-linux.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-linux/releases) From 17335eb92b1e5e89824562a94da2b5b1063f3091 Mon Sep 17 00:00:00 2001 From: arraykeys Date: Tue, 17 Apr 2018 22:25:10 +0800 Subject: [PATCH 61/76] no message --- sdk/android-ios/sdk.go | 58 +++++++++++++++------------------------- sdk/windows-linux/sdk.go | 12 ++++----- 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/sdk/android-ios/sdk.go b/sdk/android-ios/sdk.go index 1942bc5..4eacbe5 100644 --- a/sdk/android-ios/sdk.go +++ b/sdk/android-ios/sdk.go @@ -21,14 +21,16 @@ var ( app *kingpin.Application ) -//Start argsStr: is the whole command line args string +//Start +//serviceID : is service identify id,different service's id should be difference +//serviceArgsStr: is the whole command line args string //such as : //1."http -t tcp -p :8989" //2."socks -t tcp -p :8989" //and so on. //if an error occured , errStr will be the error reason //if start success, errStr is empty. -func Start(argsStr string) (errStr string) { +func Start(serviceID,serviceArgsStr string) (errStr string) { //define args tcpArgs := services.TCPArgs{} httpArgs := services.HTTPArgs{} @@ -238,7 +240,7 @@ func Start(argsStr string) (errStr string) { spsArgs.ParentCompress = sps.Flag("parent-compress", "auto compress/decompress data on parent connection").Short('M').Default("false").Bool() //parse args - _args := strings.Fields(strings.Trim(argsStr, " ")) + _args := strings.Fields(strings.Trim(serviceArgsStr, " ")) args := []string{} for _, a := range _args { args = append(args, strings.Trim(a, "\"")) @@ -318,49 +320,41 @@ func Start(argsStr string) (errStr string) { //regist services and run service switch serviceName { case "http": - services.Regist("http", services.NewHTTP(), httpArgs) + services.Regist(serviceID, services.NewHTTP(), httpArgs) case "tcp": - services.Regist("tcp", services.NewTCP(), tcpArgs) + services.Regist(serviceID, services.NewTCP(), tcpArgs) case "udp": - services.Regist("udp", services.NewUDP(), udpArgs) + services.Regist(serviceID, services.NewUDP(), udpArgs) case "tserver": - services.Regist("tserver", services.NewTunnelServerManager(), tunnelServerArgs) + services.Regist(serviceID, services.NewTunnelServerManager(), tunnelServerArgs) case "tclient": - services.Regist("tclient", services.NewTunnelClient(), tunnelClientArgs) + services.Regist(serviceID, services.NewTunnelClient(), tunnelClientArgs) case "tbridge": - services.Regist("tbridge", services.NewTunnelBridge(), tunnelBridgeArgs) + services.Regist(serviceID, services.NewTunnelBridge(), tunnelBridgeArgs) case "server": - services.Regist("server", services.NewMuxServerManager(), muxServerArgs) + services.Regist(serviceID, services.NewMuxServerManager(), muxServerArgs) case "client": - services.Regist("client", services.NewMuxClient(), muxClientArgs) + services.Regist(serviceID, services.NewMuxClient(), muxClientArgs) case "bridge": - services.Regist("bridge", services.NewMuxBridge(), muxBridgeArgs) + services.Regist(serviceID, services.NewMuxBridge(), muxBridgeArgs) case "socks": - services.Regist("socks", services.NewSocks(), socksArgs) + services.Regist(serviceID, services.NewSocks(), socksArgs) case "sps": - services.Regist("sps", services.NewSPS(), spsArgs) + services.Regist(serviceID, services.NewSPS(), spsArgs) } - _, err = services.Run(serviceName) + _, err = services.Run(serviceID) if err != nil { - return fmt.Sprintf("run service [%s] fail, ERR:%s", serviceName, err) + return fmt.Sprintf("run service [%s:%s] fail, ERR:%s",serviceID, serviceName, err) } return } -func Stop(service string) { - s := getServiceName(service) - if s == "" { - return - } - services.Stop(s) +func Stop(serviceID string) { + services.Stop(serviceID) } -func IsRunning(service string) bool { - s := getServiceName(service) - if s == "" { - return false - } - srv := services.GetService(s) +func IsRunning(serviceID string) bool { + srv := services.GetService(serviceID) if srv == nil { return false } @@ -410,14 +404,6 @@ func IsRunning(service string) bool { return false } -func getServiceName(args string) string { - s := strings.Fields(strings.Trim(args, " \t")) - if len(s) == 0 { - return "" - } - return s[0] -} - func PortIsAlive(address string, network ...string) string { time.Sleep(time.Second) n := "tcp" diff --git a/sdk/windows-linux/sdk.go b/sdk/windows-linux/sdk.go index e4801b3..eeb33c4 100644 --- a/sdk/windows-linux/sdk.go +++ b/sdk/windows-linux/sdk.go @@ -6,18 +6,18 @@ import ( ) //export Start -func Start(argsStr *C.char) (errStr *C.char) { - return C.CString(sdk.Start(C.GoString(argsStr))) +func Start(serviceID *C.char,serviceArgsStr *C.char) (errStr *C.char) { + return C.CString(sdk.Start(C.GoString(serviceID),C.GoString(serviceArgsStr))) } //export Stop -func Stop(service *C.char) { - sdk.Stop(C.GoString(service)) +func Stop(serviceID *C.char) { + sdk.Stop(C.GoString(serviceID)) } //export IsRunning -func IsRunning(service *C.char) C.int { - if sdk.IsRunning(C.GoString(service)) { +func IsRunning(serviceID *C.char) C.int { + if sdk.IsRunning(C.GoString(serviceID)) { return 1 } return 0 From e8acbbfabf546e0a315823a011ddd86f67c45f14 Mon Sep 17 00:00:00 2001 From: arraykeys Date: Wed, 18 Apr 2018 10:52:57 +0800 Subject: [PATCH 62/76] mac sdk --- sdk/windows-linux/release.sh | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100755 sdk/windows-linux/release.sh diff --git a/sdk/windows-linux/release.sh b/sdk/windows-linux/release.sh deleted file mode 100755 index fa1b31e..0000000 --- a/sdk/windows-linux/release.sh +++ /dev/null @@ -1,26 +0,0 @@ -#/bin/bash -VER="v4.7" - -rm -rf *.tar.gz -rm -rf proxy-sdk.so proxy-sdk.h proxy-sdk.a proxy-sdk.dll - -#windows -#apt-get install gcc-multilib -#apt-get install gcc-mingw-w64 -#32bit CC=i686-w64-mingw32-gcc-win32 GOARCH=386 -#64bit CC=x86_64-w64-mingw32-gcc GOARCH=amd64 -CC=i686-w64-mingw32-gcc-win32 GOARCH=386 CGO_ENABLED=1 GOOS=windows go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.dll sdk.go -cp ../README.md . -tar zcf sdk-windows-${VER}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll -rm -rf proxy-sdk.h proxy-sdk.dll - - -#linux -CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o proxy-sdk.a sdk.go -CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.so sdk.go -cp ../README.md . -tar zcf sdk-linux-${VER}.tar.gz README.md proxy-sdk.so proxy-sdk.a proxy-sdk.h - -rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a - -echo "done." From 8dc206e2d694b9bf5659e38b46d173db8c31e986 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 18 Apr 2018 10:53:14 +0800 Subject: [PATCH 63/76] Signed-off-by: arraykeys@gmail.com --- sdk/README.md | 54 ++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/sdk/README.md b/sdk/README.md index 46157c0..c7ba9fb 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -30,8 +30,9 @@ import snail007.proxy.Porxy #### 2.启动一个服务 ```java -String args="http -p :8080"; -String err=Proxy.start(args); +String serviceID="http01";//这里serviceID是自定义的唯一标识字符串,保证每个启动的服务不一样即可 +String serviceArgs="http -p :8080"; +String err=Proxy.start(serviceID,serviceArgs); if (!err.isEmpty()){ //启动失败 System.out.println("start fail,error:"+err); @@ -42,8 +43,8 @@ if (!err.isEmpty()){ #### 3.判断一个服务是否在运行 ```java -String args="http -p :8080"; -boolean isRunning=Proxy.isRunning(args);//这里传递http也可以,最终使用的就是args里面的第一个参数http +String serviceID="http01"; +boolean isRunning=Proxy.isRunning(serviceID); if(isRunning){ //正在运行 }else{ @@ -56,8 +57,8 @@ if(isRunning){ #### 4.停止一个服务 ```java -String args="http -p :8080"; -Proxy.stop(args);//这里传递http也可以,最终使用的就是args里面的第一个参数http +String serviceID="http01"; +Proxy.stop(serviceID); //停止完毕 ``` @@ -82,8 +83,10 @@ Proxy.stop(args);//这里传递http也可以,最终使用的就是args里面的 ```objc -(IBAction)doStart:(id)sender { - NSString *args = @"http -p :8080"; - NSString *error = ProxyStart(args); + //这里serviceID是自定义的唯一标识字符串,保证每个启动的服务不一样 + NSString *serviceID = @"http01"; + NSString *serviceArgs = @"http -p :8080"; + NSString *error = ProxyStart(serviceID,serviceArgs); if (error != nil && error.length > 0) { @@ -99,8 +102,8 @@ Proxy.stop(args);//这里传递http也可以,最终使用的就是args里面的 ```objc -(IBAction)hasRunning:(id)sender; { - NSString *args = @"http -p :8080"; - if (ProxyIsRunning(args))//这里传递http也可以,最终使用的就是args里面的第一个参数http + NSString *serviceID = @"http01"; + if (ProxyIsRunning(serviceID)) { NSLog(@"正在运行"); }else{ @@ -116,8 +119,8 @@ Proxy.stop(args);//这里传递http也可以,最终使用的就是args里面的 ```objc -(IBAction)doStop:(id)sender { - NSString *args = @"http -p :8080"; - ProxyStop(args);//这里传递http也可以,最终使用的就是args里面的第一个参数http + NSString *serviceID = @"http01"; + ProxyStop(serviceID); //停止完毕 } ``` @@ -149,14 +152,14 @@ typedef char *(*GOSTOP)(char *s); typedef int(*GOISRUN)(char *s); HMODULE GODLL = LoadLibrary("proxy-sdk.dll"); -char * Start(char * p) +char * Start(char * p0,char * p1) { if (GODLL != NULL) { GOSTART gostart = *(GOSTART)(GetProcAddress(GODLL, "Start")); if (gostart != NULL){ - printf("%s\n", p); - char *ret = gostart(p); + printf("%s:%s\n",p0, p1); + char *ret = gostart(p0,p1); return ret; } } @@ -193,15 +196,16 @@ int IsRunning(char * p) int main() { - char *p = "http -t tcp -p :38080"; + //这里p0是自定义的唯一标识字符串,保证每个启动的服务不一样 + char *p0 = "http01"; + char *p1 = "http -t tcp -p :38080"; printf("This is demo application.\n"); - char *str = "http -t tcp -p :38080"; //启动服务,返回空字符串说明启动成功;返回非空字符串说明启动失败,返回的字符串是错误原因 - printf("start result %s\n", Start(str)); + printf("start result %s\n", Start(p0,p1)); //停止服务,没有返回值 - Stop(str); + Stop(p0); //服务是否在运行,返回0是没有运行,返回1正在运行 - printf("is running result %d\n", IsRunning(str)); + printf("is running result %d\n", IsRunning(p0)); return 0; } @@ -230,13 +234,15 @@ Linux下面使用的sdk是so文件即proxy-sdk.so,下面写一个简单的C程 int main() { printf("This is demo application.\n"); - char *str = "http -t tcp -p :38080"; + //这里p0是自定义的唯一标识字符串,保证每个启动的服务不一样 + char *p0 = "http01"; + char *p1 = "http -t tcp -p :38080"; //启动服务,返回空字符串说明启动成功;返回非空字符串说明启动失败,返回的字符串是错误原因 - printf("start result %s\n",Start(str)); + printf("start result %s\n",Start(p0,p1)); //停止服务,没有返回值 - Stop(str); + Stop(p0); //服务是否在运行,返回0是没有运行,返回1正在运行 - printf("is running result %d\n",IsRunning(str)); + printf("is running result %d\n",IsRunning(p0)); return 0; } ``` From d5a460bd09ce0efc3fa4c03a92146336b26966b0 Mon Sep 17 00:00:00 2001 From: arraykeys Date: Wed, 18 Apr 2018 10:55:54 +0800 Subject: [PATCH 64/76] no message --- sdk/windows-linux/release_mac.sh | 13 +++++++++++++ sdk/windows-linux/release_ubuntu.sh | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100755 sdk/windows-linux/release_mac.sh create mode 100755 sdk/windows-linux/release_ubuntu.sh diff --git a/sdk/windows-linux/release_mac.sh b/sdk/windows-linux/release_mac.sh new file mode 100755 index 0000000..8079ae6 --- /dev/null +++ b/sdk/windows-linux/release_mac.sh @@ -0,0 +1,13 @@ +#/bin/bash +VER="v4.7" + +rm -rf *.tar.gz +rm -rf README.md proxy-sdk.dylib proxy-sdk.h + +#mac , macos required +CGO_ENABLED=1 GOARCH=amd64 GOOS=darwin go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.dylib sdk.go +cp ../README.md . +tar zcf sdk-mac-${VER}.tar.gz README.md proxy-sdk.dylib proxy-sdk.h +rm -rf README.md proxy-sdk.dylib proxy-sdk.h + +echo "done." diff --git a/sdk/windows-linux/release_ubuntu.sh b/sdk/windows-linux/release_ubuntu.sh new file mode 100755 index 0000000..ee944fb --- /dev/null +++ b/sdk/windows-linux/release_ubuntu.sh @@ -0,0 +1,25 @@ +#/bin/bash +VER="v4.7" + +rm -rf *.tar.gz +rm -rf proxy-sdk.so proxy-sdk.h proxy-sdk.a proxy-sdk.dll + +#windows +#apt-get install gcc-multilib +#apt-get install gcc-mingw-w64 +#32bit CC=i686-w64-mingw32-gcc-win32 GOARCH=386 +#64bit CC=x86_64-w64-mingw32-gcc GOARCH=amd64 +CC=i686-w64-mingw32-gcc-win32 GOARCH=386 CGO_ENABLED=1 GOOS=windows go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.dll sdk.go +cp ../README.md . +tar zcf sdk-windows-${VER}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll +rm -rf README.md proxy-sdk.h proxy-sdk.dll + + +#linux +CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o proxy-sdk.a sdk.go +CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.so sdk.go +cp ../README.md . +tar zcf sdk-linux-${VER}.tar.gz README.md proxy-sdk.so proxy-sdk.a proxy-sdk.h +rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a + +echo "done." From 98e0154baa7ed5550c970983acc5f8c9c40273fa Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 18 Apr 2018 11:19:51 +0800 Subject: [PATCH 65/76] Signed-off-by: arraykeys@gmail.com --- sdk/README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sdk/README.md b/sdk/README.md index c7ba9fb..d71dcd0 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -6,6 +6,7 @@ - IOS,`.framework`库 - Windows,`.dll`库 - Linux,`.so`库 +- MacOS,`.dylib`库 proxy使用gombile实现了一份go代码编译为android和ios平台下面可以直接调用的sdk类库, 另外还为linux和windows提供sdk支持,基于这些类库,APP开发者可以轻松的开发出各种形式的代理工具。 @@ -253,6 +254,18 @@ int main() { #### 执行 #### `./test-proxy` +## MacOS SDK +[![stable](https://img.shields.io/badge/stable-stable-green.svg)](https://github.com/snail007/goproxy-sdk-mac/) [![license](https://img.shields.io/github/license/snail007/goproxy-sdk-mac.svg?style=plastic)]() [![download_count](https://img.shields.io/github/downloads/snail007/goproxy-sdk-mac/total.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-mac/releases) [![download](https://img.shields.io/github/release/snail007/goproxy-sdk-mac.svg?style=plastic)](https://github.com/snail007/goproxy-sdk-mac/releases) + +[点击下载MacOS-SDK](https://github.com/snail007/goproxy-sdk-mac/releases) +在MacOS系统提供的sdk形式是一个后缀为.dylib的类库文件,开发的时候只需要把so类库加载,调用方法即可. + +### MacOS-SDK使用实例 +MacOS下面使用的sdk是dylib文件即proxy-sdk.dylib,下面写一个简单的C程序示例,调用dylib库里面的方法. + +```objc + +``` ### 关于服务 proxy的服务有11种,分别是: @@ -270,7 +283,8 @@ tbridge tserver tclient ``` -每个服务只能启动一个,如果相同的服务启动多次,那么之前的服务会被停掉,后面启动的服务覆盖之前的服务. +服务启动时,如果存在正在运行的相同ID的服务,那么之前的服务会被停掉,后面启动的服务覆盖之前的服务. +所以要保证每次启动服务的时候,第一个ID参数唯一. 上面这些服务的具体使用方式和具体参数,可以参考[proxy手册](https://github.com/snail007/goproxy/blob/master/README_ZH.md) sdk里面的服务不支持手册里面的:--daemon和--forever参数. From bef385cfd1f7ae700f2b80cc58385e9e36c14eda Mon Sep 17 00:00:00 2001 From: arraykeys Date: Wed, 18 Apr 2018 13:11:43 +0800 Subject: [PATCH 66/76] no message --- sdk/README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/sdk/README.md b/sdk/README.md index d71dcd0..6c85321 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -261,10 +261,37 @@ int main() { 在MacOS系统提供的sdk形式是一个后缀为.dylib的类库文件,开发的时候只需要把so类库加载,调用方法即可. ### MacOS-SDK使用实例 -MacOS下面使用的sdk是dylib文件即proxy-sdk.dylib,下面写一个简单的C程序示例,调用dylib库里面的方法. +MacOS下面使用的sdk是dylib文件即proxy-sdk.dylib,下面写一个简单的Obj-C程序示例,调用dylib库里面的方法. ```objc +#import "proxy-sdk.h" +-(IBAction)doStart:(id)sender +{ + char *result = Start("http01", "http -t tcp -p :38080"); + + if (result) + { + printf("started"); + }else{ + printf("not started"); + } + +} +-(IBAction)doStop:(id)sender +{ + Stop("http01"); +} +-(IBAction)doCheck:(id)sender +{ + if (IsRunning("http01")) + { + printf("running"); + }else{ + printf("not running"); + + } +} ``` ### 关于服务 From 27896a0563555df68ac09165c3a9f76eb543d3f5 Mon Sep 17 00:00:00 2001 From: arraykeys Date: Wed, 18 Apr 2018 13:22:44 +0800 Subject: [PATCH 67/76] no message --- .../{release.sh => release_android.sh} | 9 +-------- sdk/android-ios/release_ios.sh | 13 +++++++++++++ sdk/windows-linux/release_linux.sh | 14 ++++++++++++++ .../{release_ubuntu.sh => release_windows.sh} | 15 ++++++--------- 4 files changed, 34 insertions(+), 17 deletions(-) rename sdk/android-ios/{release.sh => release_android.sh} (67%) create mode 100755 sdk/android-ios/release_ios.sh create mode 100755 sdk/windows-linux/release_linux.sh rename sdk/windows-linux/{release_ubuntu.sh => release_windows.sh} (52%) diff --git a/sdk/android-ios/release.sh b/sdk/android-ios/release_android.sh similarity index 67% rename from sdk/android-ios/release.sh rename to sdk/android-ios/release_android.sh index 3f6a2c8..342cc45 100755 --- a/sdk/android-ios/release.sh +++ b/sdk/android-ios/release_android.sh @@ -1,9 +1,8 @@ #/bin/bash VER="v4.7" +rm -rf sdk-android-*.tar.gz rm -rf android -rm -rf ios mkdir android -mkdir ios #android gomobile bind -v -target=android -javapkg=snail007 -ldflags="-s -w" @@ -13,10 +12,4 @@ cp ../README.md android tar zcfv sdk-android-${VER}.tar.gz android rm -rf android -#ios XCode required -gomobile bind -v -target=ios -ldflags="-s -w" -mv Proxy.framework ios -tar zcfv sdk-ios-${VER}.tar.gz ios -rm -rf ios - echo "done." diff --git a/sdk/android-ios/release_ios.sh b/sdk/android-ios/release_ios.sh new file mode 100755 index 0000000..3ecbf12 --- /dev/null +++ b/sdk/android-ios/release_ios.sh @@ -0,0 +1,13 @@ +#/bin/bash +VER="v4.7" +rm -rf sdk-ios-*.tar.gz +rm -rf ios +mkdir ios + +#ios XCode required +gomobile bind -v -target=ios -ldflags="-s -w" +mv Proxy.framework ios +tar zcfv sdk-ios-${VER}.tar.gz ios +rm -rf ios + +echo "done." diff --git a/sdk/windows-linux/release_linux.sh b/sdk/windows-linux/release_linux.sh new file mode 100755 index 0000000..80db6bc --- /dev/null +++ b/sdk/windows-linux/release_linux.sh @@ -0,0 +1,14 @@ +#/bin/bash +VER="v4.7" + +rm -rf sdk-linux-*.tar.gz +rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a + +#linux +CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o proxy-sdk.a sdk.go +CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.so sdk.go +cp ../README.md . +tar zcf sdk-linux-${VER}.tar.gz README.md proxy-sdk.so proxy-sdk.a proxy-sdk.h +rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a + +echo "done." diff --git a/sdk/windows-linux/release_ubuntu.sh b/sdk/windows-linux/release_windows.sh similarity index 52% rename from sdk/windows-linux/release_ubuntu.sh rename to sdk/windows-linux/release_windows.sh index ee944fb..dc8f27f 100755 --- a/sdk/windows-linux/release_ubuntu.sh +++ b/sdk/windows-linux/release_windows.sh @@ -1,8 +1,10 @@ #/bin/bash VER="v4.7" -rm -rf *.tar.gz -rm -rf proxy-sdk.so proxy-sdk.h proxy-sdk.a proxy-sdk.dll +sudo rm /usr/local/go +sudo ln -s /usr/local/go1.10.1 /usr/local/go +rm -rf sdk-windows-*.tar.gz +rm -rf README.md proxy-sdk.h proxy-sdk.dll #windows #apt-get install gcc-multilib @@ -14,12 +16,7 @@ cp ../README.md . tar zcf sdk-windows-${VER}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll rm -rf README.md proxy-sdk.h proxy-sdk.dll - -#linux -CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o proxy-sdk.a sdk.go -CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.so sdk.go -cp ../README.md . -tar zcf sdk-linux-${VER}.tar.gz README.md proxy-sdk.so proxy-sdk.a proxy-sdk.h -rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a +sudo rm /usr/local/go +sudo ln -s /usr/local/go1.8.5 /usr/local/go echo "done." From 1597363dd1ef21a25d9da6256cc5ccf6dd782283 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Wed, 18 Apr 2018 15:27:32 +0800 Subject: [PATCH 68/76] Signed-off-by: arraykeys@gmail.com --- services/tunnel_bridge.go | 179 +++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 89 deletions(-) diff --git a/services/tunnel_bridge.go b/services/tunnel_bridge.go index b3d4c7d..ba4d0a8 100644 --- a/services/tunnel_bridge.go +++ b/services/tunnel_bridge.go @@ -70,95 +70,7 @@ func (s *TunnelBridge) Start(args interface{}) (err error) { p, _ := strconv.Atoi(port) sc := utils.NewServerChannel(host, p) - err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, func(inConn net.Conn) { - //log.Printf("connection from %s ", inConn.RemoteAddr()) - - reader := bufio.NewReader(inConn) - var err error - var connType uint8 - err = utils.ReadPacket(reader, &connType) - if err != nil { - log.Printf("read error,ERR:%s", err) - return - } - switch connType { - case CONN_SERVER: - var key, ID, clientLocalAddr, serverID string - err = utils.ReadPacketData(reader, &key, &ID, &clientLocalAddr, &serverID) - if err != nil { - log.Printf("read error,ERR:%s", err) - return - } - packet := utils.BuildPacketData(ID, clientLocalAddr, serverID) - log.Printf("server connection, key: %s , id: %s %s %s", key, ID, clientLocalAddr, serverID) - - //addr := clientLocalAddr + "@" + ID - s.serverConns.Set(ID, ServerConn{ - Conn: &inConn, - }) - for { - if s.isStop { - return - } - item, ok := s.clientControlConns.Get(key) - if !ok { - log.Printf("client %s control conn not exists", key) - time.Sleep(time.Second * 3) - continue - } - (*item.(*net.Conn)).SetWriteDeadline(time.Now().Add(time.Second * 3)) - _, err := (*item.(*net.Conn)).Write(packet) - (*item.(*net.Conn)).SetWriteDeadline(time.Time{}) - if err != nil { - log.Printf("%s client control conn write signal fail, err: %s, retrying...", key, err) - time.Sleep(time.Second * 3) - continue - } else { - // s.cmServer.Add(serverID, ID, &inConn) - break - } - } - case CONN_CLIENT: - var key, ID, serverID string - err = utils.ReadPacketData(reader, &key, &ID, &serverID) - if err != nil { - log.Printf("read error,ERR:%s", err) - return - } - log.Printf("client connection , key: %s , id: %s, server id:%s", key, ID, serverID) - - serverConnItem, ok := s.serverConns.Get(ID) - if !ok { - inConn.Close() - log.Printf("server conn %s exists", ID) - return - } - serverConn := serverConnItem.(ServerConn).Conn - utils.IoBind(*serverConn, inConn, func(err interface{}) { - s.serverConns.Remove(ID) - // s.cmClient.RemoveOne(key, ID) - // s.cmServer.RemoveOne(serverID, ID) - log.Printf("conn %s released", ID) - }) - // s.cmClient.Add(key, ID, &inConn) - log.Printf("conn %s created", ID) - - case CONN_CLIENT_CONTROL: - var key string - err = utils.ReadPacketData(reader, &key) - if err != nil { - log.Printf("read error,ERR:%s", err) - return - } - log.Printf("client control connection, key: %s", key) - if s.clientControlConns.Has(key) { - item, _ := s.clientControlConns.Get(key) - (*item.(*net.Conn)).Close() - } - s.clientControlConns.Set(key, &inConn) - log.Printf("set client %s control conn", key) - } - }) + err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.callback) if err != nil { return } @@ -168,3 +80,92 @@ func (s *TunnelBridge) Start(args interface{}) (err error) { func (s *TunnelBridge) Clean() { s.StopService() } +func (s *TunnelBridge) callback(inConn net.Conn) { + //log.Printf("connection from %s ", inConn.RemoteAddr()) + + reader := bufio.NewReader(inConn) + var err error + var connType uint8 + err = utils.ReadPacket(reader, &connType) + if err != nil { + log.Printf("read error,ERR:%s", err) + return + } + switch connType { + case CONN_SERVER: + var key, ID, clientLocalAddr, serverID string + err = utils.ReadPacketData(reader, &key, &ID, &clientLocalAddr, &serverID) + if err != nil { + log.Printf("read error,ERR:%s", err) + return + } + packet := utils.BuildPacketData(ID, clientLocalAddr, serverID) + log.Printf("server connection, key: %s , id: %s %s %s", key, ID, clientLocalAddr, serverID) + + //addr := clientLocalAddr + "@" + ID + s.serverConns.Set(ID, ServerConn{ + Conn: &inConn, + }) + for { + if s.isStop { + return + } + item, ok := s.clientControlConns.Get(key) + if !ok { + log.Printf("client %s control conn not exists", key) + time.Sleep(time.Second * 3) + continue + } + (*item.(*net.Conn)).SetWriteDeadline(time.Now().Add(time.Second * 3)) + _, err := (*item.(*net.Conn)).Write(packet) + (*item.(*net.Conn)).SetWriteDeadline(time.Time{}) + if err != nil { + log.Printf("%s client control conn write signal fail, err: %s, retrying...", key, err) + time.Sleep(time.Second * 3) + continue + } else { + // s.cmServer.Add(serverID, ID, &inConn) + break + } + } + case CONN_CLIENT: + var key, ID, serverID string + err = utils.ReadPacketData(reader, &key, &ID, &serverID) + if err != nil { + log.Printf("read error,ERR:%s", err) + return + } + log.Printf("client connection , key: %s , id: %s, server id:%s", key, ID, serverID) + + serverConnItem, ok := s.serverConns.Get(ID) + if !ok { + inConn.Close() + log.Printf("server conn %s exists", ID) + return + } + serverConn := serverConnItem.(ServerConn).Conn + utils.IoBind(*serverConn, inConn, func(err interface{}) { + s.serverConns.Remove(ID) + // s.cmClient.RemoveOne(key, ID) + // s.cmServer.RemoveOne(serverID, ID) + log.Printf("conn %s released", ID) + }) + // s.cmClient.Add(key, ID, &inConn) + log.Printf("conn %s created", ID) + + case CONN_CLIENT_CONTROL: + var key string + err = utils.ReadPacketData(reader, &key) + if err != nil { + log.Printf("read error,ERR:%s", err) + return + } + log.Printf("client control connection, key: %s", key) + if s.clientControlConns.Has(key) { + item, _ := s.clientControlConns.Get(key) + (*item.(*net.Conn)).Close() + } + s.clientControlConns.Set(key, &inConn) + log.Printf("set client %s control conn", key) + } +} From 4ef33d0ffdd9966c247e17ee44fc4e7adeff5454 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Thu, 19 Apr 2018 14:55:56 +0800 Subject: [PATCH 69/76] fix #56 Signed-off-by: arraykeys@gmail.com --- services/sps.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/sps.go b/services/sps.go index 4bf5375..327b532 100644 --- a/services/sps.go +++ b/services/sps.go @@ -75,7 +75,7 @@ func (s *SPS) InitOutConnPool() { 0, *s.cfg.ParentType, s.cfg.KCP, - s.cfg.CertBytes, s.cfg.KeyBytes, nil, + s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes, *s.cfg.Parent, *s.cfg.Timeout, ) @@ -120,7 +120,7 @@ func (s *SPS) Start(args interface{}) (err error) { if *s.cfg.LocalType == TYPE_TCP { err = sc.ListenTCP(s.callback) } else if *s.cfg.LocalType == TYPE_TLS { - err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, nil, s.callback) + err = sc.ListenTls(s.cfg.CertBytes, s.cfg.KeyBytes, s.cfg.CaCertBytes, s.callback) } else if *s.cfg.LocalType == TYPE_KCP { err = sc.ListenKCP(s.cfg.KCP, s.callback) } From 905bfff92bc82abf3c8dc04c7295d8a468fd121a Mon Sep 17 00:00:00 2001 From: arraykeys Date: Fri, 20 Apr 2018 13:23:08 +0800 Subject: [PATCH 70/76] no message --- sdk/android-ios/release_ios.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/android-ios/release_ios.sh b/sdk/android-ios/release_ios.sh index 3ecbf12..499971b 100755 --- a/sdk/android-ios/release_ios.sh +++ b/sdk/android-ios/release_ios.sh @@ -7,6 +7,7 @@ mkdir ios #ios XCode required gomobile bind -v -target=ios -ldflags="-s -w" mv Proxy.framework ios +cp ../README.md ios tar zcfv sdk-ios-${VER}.tar.gz ios rm -rf ios From c6f62665921ac5ee6e6ffa27447b02ba9e8c23a8 Mon Sep 17 00:00:00 2001 From: arraykeys Date: Fri, 20 Apr 2018 13:31:58 +0800 Subject: [PATCH 71/76] no message --- sdk/README.md | 63 ++------------------------------------------------- 1 file changed, 2 insertions(+), 61 deletions(-) diff --git a/sdk/README.md b/sdk/README.md index 6c85321..9e64c6e 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -41,21 +41,8 @@ if (!err.isEmpty()){ //启动成功 } ``` -#### 3.判断一个服务是否在运行 -```java -String serviceID="http01"; -boolean isRunning=Proxy.isRunning(serviceID); -if(isRunning){ - //正在运行 -}else{ - //没有运行 -} -``` - -由于tclient和client服务的特性,目前这个方法对于服务tclient和client永远返回false. - -#### 4.停止一个服务 +#### 3.停止一个服务 ```java String serviceID="http01"; @@ -98,24 +85,7 @@ Proxy.stop(serviceID); } ``` -#### 3.判断一个服务是否在运行 - -```objc --(IBAction)hasRunning:(id)sender; -{ - NSString *serviceID = @"http01"; - if (ProxyIsRunning(serviceID)) - { - NSLog(@"正在运行"); - }else{ - NSLog(@"没有运行"); - } -} -``` - -由于tclient和client服务的特性,目前这个方法对于服务tclient和client永远返回false. - -#### 4.停止一个服务 +#### 3.停止一个服务 ```objc -(IBAction)doStop:(id)sender @@ -180,21 +150,6 @@ char * Stop(char * p) return "Cannot Find dll"; } -int IsRunning(char * p) -{ - - if (GODLL != NULL) - { - GOISRUN isrun = *(GOISRUN)(GetProcAddress(GODLL, "IsRunning")); - if (isrun != NULL){ - int ret = isrun(p); - return ret; - } - FreeLibrary(GODLL); - } - return 0; -} - int main() { //这里p0是自定义的唯一标识字符串,保证每个启动的服务不一样 @@ -205,8 +160,6 @@ int main() printf("start result %s\n", Start(p0,p1)); //停止服务,没有返回值 Stop(p0); - //服务是否在运行,返回0是没有运行,返回1正在运行 - printf("is running result %d\n", IsRunning(p0)); return 0; } @@ -242,8 +195,6 @@ int main() { printf("start result %s\n",Start(p0,p1)); //停止服务,没有返回值 Stop(p0); - //服务是否在运行,返回0是没有运行,返回1正在运行 - printf("is running result %d\n",IsRunning(p0)); return 0; } ``` @@ -282,16 +233,6 @@ MacOS下面使用的sdk是dylib文件即proxy-sdk.dylib,下面写一个简单的 Stop("http01"); } --(IBAction)doCheck:(id)sender -{ - if (IsRunning("http01")) - { - printf("running"); - }else{ - printf("not running"); - - } -} ``` ### 关于服务 From fbd8c67649856116337301538ed2cc771a5cdfcd Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Fri, 20 Apr 2018 13:35:05 +0800 Subject: [PATCH 72/76] Signed-off-by: arraykeys@gmail.com --- sdk/android-ios/sdk.go | 98 +--------------------------------------- sdk/windows-linux/sdk.go | 7 --- 2 files changed, 2 insertions(+), 103 deletions(-) diff --git a/sdk/android-ios/sdk.go b/sdk/android-ios/sdk.go index 4eacbe5..68043c6 100644 --- a/sdk/android-ios/sdk.go +++ b/sdk/android-ios/sdk.go @@ -4,13 +4,10 @@ import ( "crypto/sha1" "fmt" "log" - "net" "os" "snail007/proxy/services" "snail007/proxy/services/kcpcfg" - "strconv" "strings" - "time" kcp "github.com/xtaci/kcp-go" "golang.org/x/crypto/pbkdf2" @@ -30,7 +27,7 @@ var ( //and so on. //if an error occured , errStr will be the error reason //if start success, errStr is empty. -func Start(serviceID,serviceArgsStr string) (errStr string) { +func Start(serviceID, serviceArgsStr string) (errStr string) { //define args tcpArgs := services.TCPArgs{} httpArgs := services.HTTPArgs{} @@ -344,7 +341,7 @@ func Start(serviceID,serviceArgsStr string) (errStr string) { } _, err = services.Run(serviceID) if err != nil { - return fmt.Sprintf("run service [%s:%s] fail, ERR:%s",serviceID, serviceName, err) + return fmt.Sprintf("run service [%s:%s] fail, ERR:%s", serviceID, serviceName, err) } return } @@ -352,94 +349,3 @@ func Start(serviceID,serviceArgsStr string) (errStr string) { func Stop(serviceID string) { services.Stop(serviceID) } - -func IsRunning(serviceID string) bool { - srv := services.GetService(serviceID) - if srv == nil { - return false - } - typ := "tcp" - addr := "" - route := "" - switch srv.Name { - case "http": - addr = *srv.Args.(services.HTTPArgs).Local - case "socks": - addr = *srv.Args.(services.SocksArgs).Local - case "sps": - addr = *srv.Args.(services.SPSArgs).Local - case "tcp": - addr = *srv.Args.(services.TCPArgs).Local - case "bridge": - addr = *srv.Args.(services.MuxBridgeArgs).Local - - case "tbridge": - addr = *srv.Args.(services.TunnelBridgeArgs).Local - case "server": - if len(*srv.Args.(services.MuxServerArgs).Route) > 0 { - route = (*srv.Args.(services.MuxServerArgs).Route)[0] - } - case "tserver": - if len(*srv.Args.(services.TunnelServerArgs).Route) > 0 { - route = (*srv.Args.(services.TunnelServerArgs).Route)[0] - } - case "client": - case "tclient": - case "udp": - typ = "udp" - } - if route != "" { - if strings.HasPrefix(route, "udp://") { - typ = "udp" - } - info := strings.TrimPrefix(route, "udp://") - info = strings.TrimPrefix(info, "tcp://") - _routeInfo := strings.Split(info, "@") - addr = _routeInfo[0] - } - a := strings.Split(addr, ",") - if len(a) > 0 { - return PortIsAlive(a[0], typ) == "" - } - return false -} - -func PortIsAlive(address string, network ...string) string { - time.Sleep(time.Second) - n := "tcp" - if len(network) == 1 { - n = network[0] - } - if n == "tcp" { - conn, err := net.DialTimeout(n, address, time.Second) - if err != nil { - return fmt.Sprintf("connect %s is failed!,err:%v\n", address, err) - } - conn.Close() - } else { - ip, port, err := net.SplitHostPort(address) - if err != nil { - return err.Error() - } - portI, _ := strconv.Atoi(port) - dstAddr := &net.UDPAddr{IP: net.ParseIP(ip), Port: portI} - conn, err := net.DialUDP(n, &net.UDPAddr{IP: net.IPv4zero, Port: 0}, dstAddr) - if err != nil { - return err.Error() - } - conn.SetDeadline(time.Now().Add(time.Millisecond * 200)) - _, err = conn.Write([]byte{0x00}) - conn.SetDeadline(time.Now().Add(time.Millisecond * 200)) - b := make([]byte, 1) - _, err = conn.Read(b) - - if err != nil { - if strings.Contains(err.Error(), "refused") { - return err.Error() - } - } else { - conn.Close() - } - } - return "" -} diff --git a/sdk/windows-linux/sdk.go b/sdk/windows-linux/sdk.go index eeb33c4..beb32d6 100644 --- a/sdk/windows-linux/sdk.go +++ b/sdk/windows-linux/sdk.go @@ -15,12 +15,5 @@ func Stop(serviceID *C.char) { sdk.Stop(C.GoString(serviceID)) } -//export IsRunning -func IsRunning(serviceID *C.char) C.int { - if sdk.IsRunning(C.GoString(serviceID)) { - return 1 - } - return 0 -} func main() { } From 8b5cc3fb8988a9990fc5f20df6e4f3d63b6f9bd3 Mon Sep 17 00:00:00 2001 From: arraykeys Date: Sat, 21 Apr 2018 11:21:04 +0800 Subject: [PATCH 73/76] no message --- sdk/README.md | 10 +++++----- sdk/windows-linux/release_linux.sh | 10 +++++----- sdk/windows-linux/release_mac.sh | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/sdk/README.md b/sdk/README.md index 9e64c6e..5955be2 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -178,13 +178,13 @@ C++示例2,请移步:[GoProxyForC](https://github.com/SuperPowerLF2/GoProxyF 在Linux系统提供的sdk形式是一个后缀为.so的类库文件,开发的时候只需要把so类库加载,调用方法即可. ### Linux-SDK使用实例 -Linux下面使用的sdk是so文件即proxy-sdk.so,下面写一个简单的C程序示例,调用so库里面的方法. +Linux下面使用的sdk是so文件即libproxy-sdk.so,下面写一个简单的C程序示例,调用so库里面的方法. `vi test-proxy.c` ```c #include -#include "proxy-sdk.h" +#include "libproxy-sdk.h" int main() { printf("This is demo application.\n"); @@ -200,7 +200,7 @@ int main() { ``` #### 编译test-proxy.c #### -`export LD_LIBRARY_PATH=./ && gcc -o test-proxy test.c proxy-sdk.so` +`export LD_LIBRARY_PATH=./ && gcc -o test-proxy test.c libproxy-sdk.so` #### 执行 #### `./test-proxy` @@ -212,10 +212,10 @@ int main() { 在MacOS系统提供的sdk形式是一个后缀为.dylib的类库文件,开发的时候只需要把so类库加载,调用方法即可. ### MacOS-SDK使用实例 -MacOS下面使用的sdk是dylib文件即proxy-sdk.dylib,下面写一个简单的Obj-C程序示例,调用dylib库里面的方法. +MacOS下面使用的sdk是dylib文件即libproxy-sdk.dylib,下面写一个简单的Obj-C程序示例,调用dylib库里面的方法. ```objc -#import "proxy-sdk.h" +#import "libproxy-sdk.h" -(IBAction)doStart:(id)sender { char *result = Start("http01", "http -t tcp -p :38080"); diff --git a/sdk/windows-linux/release_linux.sh b/sdk/windows-linux/release_linux.sh index 80db6bc..5d6bc33 100755 --- a/sdk/windows-linux/release_linux.sh +++ b/sdk/windows-linux/release_linux.sh @@ -2,13 +2,13 @@ VER="v4.7" rm -rf sdk-linux-*.tar.gz -rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a +rm -rf README.md libproxy-sdk.so libproxy-sdk.h libproxy-sdk.a #linux -CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o proxy-sdk.a sdk.go -CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.so sdk.go +CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o libproxy-sdk.a sdk.go +CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o libproxy-sdk.so sdk.go cp ../README.md . -tar zcf sdk-linux-${VER}.tar.gz README.md proxy-sdk.so proxy-sdk.a proxy-sdk.h -rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a +tar zcf sdk-linux-${VER}.tar.gz README.md libproxy-sdk.so libproxy-sdk.a libproxy-sdk.h +rm -rf README.md libproxy-sdk.so libproxy-sdk.h libproxy-sdk.a echo "done." diff --git a/sdk/windows-linux/release_mac.sh b/sdk/windows-linux/release_mac.sh index 8079ae6..275bea9 100755 --- a/sdk/windows-linux/release_mac.sh +++ b/sdk/windows-linux/release_mac.sh @@ -2,12 +2,12 @@ VER="v4.7" rm -rf *.tar.gz -rm -rf README.md proxy-sdk.dylib proxy-sdk.h +rm -rf README.md libproxy-sdk.dylib libproxy-sdk.h #mac , macos required -CGO_ENABLED=1 GOARCH=amd64 GOOS=darwin go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.dylib sdk.go +CGO_ENABLED=1 GOARCH=amd64 GOOS=darwin go build -buildmode=c-shared -ldflags "-s -w" -o libproxy-sdk.dylib sdk.go cp ../README.md . -tar zcf sdk-mac-${VER}.tar.gz README.md proxy-sdk.dylib proxy-sdk.h -rm -rf README.md proxy-sdk.dylib proxy-sdk.h +tar zcf sdk-mac-${VER}.tar.gz README.md libproxy-sdk.dylib libproxy-sdk.h +rm -rf README.md libproxy-sdk.dylib libproxy-sdk.h echo "done." From b59cf1f144f7ea1b3d8bf48228710d72bcdd8a08 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 24 Apr 2018 18:09:33 +0800 Subject: [PATCH 74/76] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=A4=A7?= =?UTF-8?q?=E9=87=8F=E6=8F=92=E5=9B=BE,=E4=BC=98=E5=8C=96=E4=BA=86?= =?UTF-8?q?=E5=A4=9A=E9=93=BE=E6=8E=A5=E7=89=88=E6=9C=AC=E5=86=85=E7=BD=91?= =?UTF-8?q?=E7=A9=BF=E9=80=8F,=E5=AE=9E=E7=8E=B0=E4=BA=86=E5=BF=83?= =?UTF-8?q?=E8=B7=B3=E6=9C=BA=E5=88=B6.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: arraykeys@gmail.com --- CHANGELOG | 4 +++- README_ZH.md | 32 +++++++++++++++++++++------ docs/images/fxdl.png | Bin 0 -> 34259 bytes docs/images/http-1.png | Bin 0 -> 18703 bytes docs/images/http-2.png | Bin 0 -> 22114 bytes docs/images/http-kcp.png | Bin 0 -> 26026 bytes docs/images/http-ssh-1.png | Bin 0 -> 29754 bytes docs/images/http-tls-2.png | Bin 0 -> 25236 bytes docs/images/http-tls-3.png | Bin 0 -> 30369 bytes docs/images/socks-2.png | Bin 0 -> 17966 bytes docs/images/socks-ssh.png | Bin 0 -> 25054 bytes docs/images/socks-tls-2.png | Bin 0 -> 19518 bytes docs/images/socks-tls-3.png | Bin 0 -> 25257 bytes docs/images/sps-tls.png | Bin 0 -> 24354 bytes docs/images/tcp-1.png | Bin 0 -> 14313 bytes docs/images/tcp-2.png | Bin 0 -> 17857 bytes docs/images/tcp-3.png | Bin 0 -> 22383 bytes docs/images/tcp-tls-2.png | Bin 0 -> 19670 bytes docs/images/tcp-tls-3.png | Bin 0 -> 25192 bytes docs/images/udp-1.png | Bin 0 -> 13117 bytes docs/images/udp-2.png | Bin 0 -> 20625 bytes docs/images/udp-3.png | Bin 0 -> 23409 bytes docs/images/udp-tls-2.png | Bin 0 -> 22593 bytes docs/images/udp-tls-3.png | Bin 0 -> 26224 bytes sdk/windows-linux/release_linux.sh | 12 ++++++++-- sdk/windows-linux/release_windows.sh | 14 ++++++++---- services/tunnel_bridge.go | 24 ++++++++++++++++---- services/tunnel_client.go | 14 ++++++++++++ services/tunnel_server.go | 14 ++++++++++++ 29 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 docs/images/fxdl.png create mode 100644 docs/images/http-1.png create mode 100644 docs/images/http-2.png create mode 100644 docs/images/http-kcp.png create mode 100644 docs/images/http-ssh-1.png create mode 100644 docs/images/http-tls-2.png create mode 100644 docs/images/http-tls-3.png create mode 100644 docs/images/socks-2.png create mode 100644 docs/images/socks-ssh.png create mode 100644 docs/images/socks-tls-2.png create mode 100644 docs/images/socks-tls-3.png create mode 100644 docs/images/sps-tls.png create mode 100644 docs/images/tcp-1.png create mode 100644 docs/images/tcp-2.png create mode 100644 docs/images/tcp-3.png create mode 100644 docs/images/tcp-tls-2.png create mode 100644 docs/images/tcp-tls-3.png create mode 100644 docs/images/udp-1.png create mode 100644 docs/images/udp-2.png create mode 100644 docs/images/udp-3.png create mode 100644 docs/images/udp-tls-2.png create mode 100644 docs/images/udp-tls-3.png diff --git a/CHANGELOG b/CHANGELOG index 0e5522a..bc7648b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,7 +12,9 @@ v4.7 9.SPS\HTTP(s)\Socks代理增加了压缩传输,只需要通过参数-m和-M设置即可. 10.手册增加了SPS\HTTP(s)\Socks自定义加密的使用示例. 11.手册增加了SPS\HTTP(s)\Socks压缩传输的使用示例. - +12.优化了多链接版本的内网穿透,融合了多链接和smux的优点,即能够拥有大的吞吐量, + 同时又具备mux的心跳机制保证了链接的稳定性. +13.手册增加了大量配图. v4.6 1.sps,http(s),socks5,内网穿透都做了大量的超时优化处理,更加稳定. diff --git a/README_ZH.md b/README_ZH.md index b8deaab..b294c8e 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -221,16 +221,18 @@ proxy会fork子进程,然后监控子进程,如果子进程异常退出,5秒后 ### **1.HTTP代理** #### **1.1.普通HTTP代理** -![1.1](/docs/images/1.1.jpg) +![1.1](/docs/images/http-1.png) `./proxy http -t tcp -p "0.0.0.0:38080"` #### **1.2.普通二级HTTP代理** +![1.2](/docs/images/http-2.png) 使用本地端口8090,假设上级HTTP代理是`22.22.22.22:8080` `./proxy http -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" ` 我们还可以指定网站域名的黑白名单文件,一行一个域名,匹配规则是最右匹配,比如:baidu.com,匹配的是*.*.baidu.com,黑名单的域名域名直接走上级代理,白名单的域名不走上级代理. `./proxy http -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -b blocked.txt -d direct.txt` #### **1.3.HTTP二级代理(加密)** +![1.3](/docs/images/http-tls-2.png) 一级HTTP代理(VPS,IP:22.22.22.22) `./proxy http -t tls -p ":38080" -C proxy.crt -K proxy.key` @@ -243,6 +245,7 @@ proxy会fork子进程,然后监控子进程,如果子进程异常退出,5秒后 然后设置你的windos系统中,需要通过代理上网的程序的代理为http模式,地址为:127.0.0.1,端口为:8080,程序即可通过加密通道通过vps上网。 #### **1.4.HTTP三级代理(加密)** +![1.3](/docs/images/http-tls-3.png) 一级HTTP代理VPS_01,IP:22.22.22.22 `./proxy http -t tls -p ":38080" -C proxy.crt -K proxy.key` 二级HTTP代理VPS_02,IP:33.33.33.33 @@ -278,6 +281,7 @@ target:用户访问的URL,比如:http://demo.com:80/1.html或https://www.baidu.c `./proxy http --always -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key` #### **1.7.HTTP(S)通过SSH中转** +![1.7](/docs/images/http-ssh-1.png) 说明:ssh中转的原理是利用了ssh的转发功能,就是你连接上ssh之后,可以通过ssh代理访问目标地址. 假设有:vps - IP是2.2.2.2, ssh端口是22, ssh用户名是:user, ssh用户密码是:demo @@ -291,6 +295,7 @@ target:用户访问的URL,比如:http://demo.com:80/1.html或https://www.baidu.c `./proxy http -T ssh -P "2.2.2.2:22" -u user -S user.key -t tcp -p ":28080"` #### **1.8.KCP协议传输** +![1.8](/docs/images/http-kcp.png) KCP协议需要--kcp-key参数设置一个密码用于加密解密数据 一级HTTP代理(VPS,IP:22.22.22.22) @@ -300,7 +305,8 @@ KCP协议需要--kcp-key参数设置一个密码用于加密解密数据 `./proxy http -t tcp -p ":8080" -T kcp -P "22.22.22.22:38080" --kcp-key mypassword` 那么访问本地的8080端口就是访问VPS上面的代理端口38080,数据通过kcp协议传输. -#### **1.9 HTTP(S)反向代理** +#### **1.9 HTTP(S)反向代理** +![1.9](/docs/images/fxdl.png) proxy不仅支持在其他软件里面通过设置代理的方式,为其他软件提供代理服务,而且支持直接把请求的网站域名解析到proxy监听的ip上,然后proxy监听80和443端口,那么proxy就会自动为你代理访问需要访问的HTTP(S)网站. 使用方式: @@ -313,7 +319,7 @@ proxy不仅支持在其他软件里面通过设置代理的方式,为其他软 `./proxy http -t tcp -p :80,:443 -T tls -P "2.2.2.2:33080" -C proxy.crt -K proxy.key` 注意: -proxy所在的服务器的DNS解析结果不能受到自定义的解析影响,不然就死循环了. +proxy所在的服务器的DNS解析结果不能受到自定义的解析影响,不然就死循环了,proxy代理最好指定`--dns 8.8.8.8`参数. #### **1.10 HTTP(S)透明代理** 该模式需要具有一定的网络基础,相关概念不懂的请自行搜索解决. @@ -418,13 +424,13 @@ proxy的http(s)代理在tcp之上可以通过tls标准加密以及kcp协议加 ### **2.TCP代理** #### **2.1.普通一级TCP代理** -![2.1](/docs/images/2.1.png) +![2.1](/docs/images/tcp-1.png) 本地执行: `./proxy tcp -p ":33080" -T tcp -P "192.168.22.33:22"` 那么访问本地33080端口就是访问192.168.22.33的22端口. #### **2.2.普通二级TCP代理** -![2.2](/docs/images/2.2.png) +![2.2](/docs/images/tcp-2.png) VPS(IP:22.22.22.33)执行: `./proxy tcp -p ":33080" -T tcp -P "127.0.0.1:8080"` 本地执行: @@ -432,6 +438,7 @@ VPS(IP:22.22.22.33)执行: 那么访问本地23080端口就是访问22.22.22.33的8080端口. #### **2.3.普通三级TCP代理** +![2.3](/docs/images/tcp-3.png) 一级TCP代理VPS_01,IP:22.22.22.22 `./proxy tcp -p ":38080" -T tcp -P "66.66.66.66:8080"` 二级TCP代理VPS_02,IP:33.33.33.33 @@ -441,6 +448,7 @@ VPS(IP:22.22.22.33)执行: 那么访问本地8080端口就是通过加密TCP隧道访问66.66.66.66的8080端口. #### **2.4.加密二级TCP代理** +![2.4](/docs/images/tcp-tls-2.png) VPS(IP:22.22.22.33)执行: `./proxy tcp -t tls -p ":33080" -T tcp -P "127.0.0.1:8080" -C proxy.crt -K proxy.key` 本地执行: @@ -448,6 +456,7 @@ VPS(IP:22.22.22.33)执行: 那么访问本地23080端口就是通过加密TCP隧道访问22.22.22.33的8080端口. #### **2.5.加密三级TCP代理** +![2.5](/docs/images/tcp-tls-3.png) 一级TCP代理VPS_01,IP:22.22.22.22 `./proxy tcp -t tls -p ":38080" -T tcp -P "66.66.66.66:8080" -C proxy.crt -K proxy.key` 二级TCP代理VPS_02,IP:33.33.33.33 @@ -462,11 +471,13 @@ VPS(IP:22.22.22.33)执行: ### **3.UDP代理** #### **3.1.普通一级UDP代理** +![3.1](/docs/images/udp-1.png) 本地执行: `./proxy udp -p ":5353" -T udp -P "8.8.8.8:53"` 那么访问本地UDP:5353端口就是访问8.8.8.8的UDP:53端口. #### **3.2.普通二级UDP代理** +![3.2](/docs/images/udp-2.png) VPS(IP:22.22.22.33)执行: `./proxy tcp -p ":33080" -T udp -P "8.8.8.8:53"` 本地执行: @@ -474,6 +485,7 @@ VPS(IP:22.22.22.33)执行: 那么访问本地UDP:5353端口就是通过TCP隧道,通过VPS访问8.8.8.8的UDP:53端口. #### **3.3.普通三级UDP代理** +![3.3](/docs/images/udp-3.png) 一级TCP代理VPS_01,IP:22.22.22.22 `./proxy tcp -p ":38080" -T udp -P "8.8.8.8:53"` 二级TCP代理VPS_02,IP:33.33.33.33 @@ -483,6 +495,7 @@ VPS(IP:22.22.22.33)执行: 那么访问本地5353端口就是通过TCP隧道,通过VPS访问8.8.8.8的53端口. #### **3.4.加密二级UDP代理** +![3.4](/docs/images/udp-tls-2.png) VPS(IP:22.22.22.33)执行: `./proxy tcp -t tls -p ":33080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key` 本地执行: @@ -490,6 +503,7 @@ VPS(IP:22.22.22.33)执行: 那么访问本地UDP:5353端口就是通过加密TCP隧道,通过VPS访问8.8.8.8的UDP:53端口. #### **3.5.加密三级UDP代理** +![3.5](/docs/images/udp-tls-3.png) 一级TCP代理VPS_01,IP:22.22.22.22 `./proxy tcp -t tls -p ":38080" -T udp -P "8.8.8.8:53" -C proxy.crt -K proxy.key` 二级TCP代理VPS_02,IP:33.33.33.33 @@ -649,13 +663,14 @@ server连接到bridge的时候,如果同时有多个client连接到同一个brid `./proxy socks -t tcp -p "0.0.0.0:38080"` #### **5.2.普通二级SOCKS5代理** -![5.2](/docs/images/5.2.png) +![5.2](/docs/images/socks-2.png) 使用本地端口8090,假设上级SOCKS5代理是`22.22.22.22:8080` `./proxy socks -t tcp -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" ` 我们还可以指定网站域名的黑白名单文件,一行一个域名,匹配规则是最右匹配,比如:baidu.com,匹配的是*.*.baidu.com,黑名单的域名域名直接走上级代理,白名单的域名不走上级代理;如果域名即在黑名单又在白名单中,那么黑名单起作用. `./proxy socks -p "0.0.0.0:8090" -T tcp -P "22.22.22.22:8080" -b blocked.txt -d direct.txt` #### **5.3.SOCKS二级代理(加密)** +![5.3](/docs/images/socks-tls-2.png) 一级SOCKS代理(VPS,IP:22.22.22.22) `./proxy socks -t tls -p ":38080" -C proxy.crt -K proxy.key` @@ -668,6 +683,7 @@ server连接到bridge的时候,如果同时有多个client连接到同一个brid 然后设置你的windos系统中,需要通过代理上网的程序的代理为socks5模式,地址为:127.0.0.1,端口为:8080,程序即可通过加密通道通过vps上网。 #### **5.4.SOCKS三级代理(加密)** +![5.4](/docs/images/socks-tls-3.png) 一级SOCKS代理VPS_01,IP:22.22.22.22 `./proxy socks -t tls -p ":38080" -C proxy.crt -K proxy.key` 二级SOCKS代理VPS_02,IP:33.33.33.33 @@ -681,6 +697,7 @@ server连接到bridge的时候,如果同时有多个client连接到同一个brid `./proxy socks --always -t tls -p ":28080" -T tls -P "22.22.22.22:38080" -C proxy.crt -K proxy.key` #### **5.6.SOCKS通过SSH中转** +![5.6](/docs/images/socks-ssh.png) 说明:ssh中转的原理是利用了ssh的转发功能,就是你连接上ssh之后,可以通过ssh代理访问目标地址. 假设有:vps - IP是2.2.2.2, ssh端口是22, ssh用户名是:user, ssh用户密码是:demo @@ -817,7 +834,8 @@ proxy的socks代理在tcp之上可以通过自定义加密和tls标准加密以 命令如下: `./proxy sps -S socks -T kcp -P 127.0.0.1:8080 -t tcp -p :18080 --kcp-key demo123` -#### **6.4 链式连接** +#### **6.4 链式连接** +![6.4](/docs/images/sps-tls.png) 上面提过多个sps结点可以层级连接构建加密通道,假设有如下vps和家里的pc电脑。 vps01:2.2.2.2 vps02:3.3.3.3 diff --git a/docs/images/fxdl.png b/docs/images/fxdl.png new file mode 100644 index 0000000000000000000000000000000000000000..297ad5ed357ad5f392ae58aa64b85e36c163b49c GIT binary patch literal 34259 zcmeFZbx>CA*EW37odSwTii&iobcfO*Aq~<@-mW|ZlBgBTn#QQ&EahIV&O_l zHgl-hwb<*A`)B#R-W{uAwaqRo{Zp=2J!orBn|o%PSdmp4{!MFDuF&PQS^ZjR39HMj z9G1lchL`Wg&(2e1uCtjibZonSH?nr_op{#E6Q!EuxgjVS#f&F|eDS6hJjVz@zPdxv z;*sy>f&cgW|FCr!#{MMkOD85Krj|XJQI*Rf^kVIbmzP(;)4A5Ayw!vCv7^18s{I*? zmrER{=*(K*N*Wk^>R7PI z9&Q9`d~N>q^`SB+*Ao{qGBRDF(45K2XG@=TjND}vR8_eK?d^N!+9EQ_?8g+oUz}IN zaoAP{G8B8Yl8y5DJ$!cNG5t*(a$fc*O{3y z?Z?Vwlf}GADJd<=?4O0Ch1`8Il;Sv5XFXQN;c>7Q`sOkhI;yCssK9QNcW+~2Ozq(8 za7vm&7Nr`bur^wZGg0LteYi34^zWXH-ADnNVbya%gVN}>}SrfC<>oPm$;l1`oh z+B;fO;pfYKKN5u#Ttq!RJ=0&65@y^wiW;Cs$V-RCkgn&HeCe4X_MPXPfb0{cwa| ztkrkMvW5Mrc(H!<#m0E(i}l|#yS?IVx%%Y=uA4^BH^!-VmwLJ8Z>blEdLE?c@#h5ALRlYO@5z+L0_q})=8(&%lkEK=?}xmoeSb zr%&Z(Hr&gwP_YVuJqi4CZ@B6ty5cxY9UV!yxVV00sbk!}eH)jAq-`nLHylo*adMK5 zL$9R%J&##|_sNTN4{Q=T!poO0@2!nql9!ji!t)r@z`&rd*3)&o@)?dtZQUdBboeZT zIxlKbk3AIX`TB24YHDf$pHsKOKb}MUQ(hQ>QZLohQm=0G*Kv`FR;H;s)6>r`({eJ7ERg3GrksZmK3dZ3`d`;F~O+i*)u z%T_HG>i+%vD3%xX6SbcF%<@vO{4<-I5p<#+5HLU4FDGz3DnRx3_dlv|VvJF+i0tap zGlcXUXK8%IiYY>!$?%%V|JAFRxw%)p;uqI$sU`)Km-CO0k1LpqJ{+(0y!>P+rvc7u z1{Oani{&dyFqM@J|7-`gMKVESWwZ!d}O`SC4I zg9<&j-reVae&H2c^|r>c z>7)#=K0V&CfYnQd1;)k2t%n`?bpMam^1g>bg%cW^c1}HwV~zS}v)kcQz8AC+^x|@x zleOx_mdZ^!uuBaL4Pk?-=Xk(Q3vF+I;B|3!7?iY_7W1D7&cX6@Ql4L5CsL=FSzoso zkX>C}eFHh`@$V8FOJgoEF(e5#K*GYe*RS>1--6Yei&hnr9Q>xWjl6CSy>ydSM{-PT z0vp0-wAjk5KTY-uzr(eGpFh>}jYN@sh~hSDB@74(n*Dxp#-*X5p_Z-9@%9Q2ItDf& z_gC6pv)&|8Boo1zhQ*DIjxus{lR|cteDL5wa3yYc~;`M8Pjj!wlB7%aW z%bvH|ssuD&hJ@tAGAwi}L%7PwFk@UxlR#b+HTo(3|N8y0;|lh>^&>%};8dQgAyFCn zj|KB}irZ9`6HRYDs=5K69YcRG9VO41E&KN9i(RljgYbNBZAR&HKFAqaD8dT!sN?d| zb_$LO{8jGFdl;OM>hGG>--xzT?bEHo)4tA={npj&5>yaDYy;lVkG17+Nb~DgoI04J znvE2ZWJ*L>fm|Xp8yt-MV2T`$kZV$Z1OACg*A$LafP)|uJ1pebWMm!q7@QvhM3G-H zXS?DQ3jg##8#&x3?ElBx9H_Th&NyY{9ak(#;TK~Cu*Z2HcO@5d&)!mH7Z;=QZKDk^ z`bBBu8Lq|vpQUdKOQE(R-`{+FHkmTy$O_AVR%Y9<7W%arq3z<)AiHAup94~cGRS{Ib!vOn6m#S;f8reUF3bB#LtFDV{ct_6 zos>21PhIW@ifP-Gn{XlOVk57fNGk}Bqx&)7k}{y8P`DX&p2dZi{c5=c)AORyDc7+C zSc>9D(F!hMvS3QYplX>VF;n7;z7On?m)%(2ni*75q(?`tBLr<2d!5d3Pvy{}t<~hB zB{nc`$$HZt&h2Uti-55Cp{C`8*65yuf$Dv%DtC&VRn9BtJX=nr+$B{Q|L!yq+@A`T ziM~t~`jW#x5jwUmy}L|&`mVPg%9JPsJQ4f8owoORSo>o0St9d{XQr`$6!WUq`qyymG>OeJ0G*8YOD zEhglLf2!B7VYko(MZUkmGYwHWN*b6!BT?3-f@-`fnCf5achBL)QD@iRwoS2L3M27Z zO%qCZB&FnZt3{qxEnd!*m<)>O8x{&7N<;Cs`Vgnf#}LV{bIh=Wf4TbXj9Qy$dDgg& z`V;X=2I21gRgAX!P>T}4<^FgvRCSUL@6TdbYsg@FvS29^Uj9u(f-+-$5=-h9+Euc4 zieh@capa8d9ZYLPR*o$Z7w5e|r_*!4v$kj&LA5~Xq=`}pMBT?Kq97jDm7EbRXH>p8{NYS)jJ-}j8zQvVj-;VXyR6rzA1 z%<@h?X`|TtAT8T1H{iau*c+)Tx{3tdntO|s%W#(M9)&pZO0x{+5vo{z#jK*78E>pL zIZt-`Eot6Mtrn_Con5lYnrEn2XOy@-J;jJW`fOI{RQOB2O6Re$R2-&AcW06dB)*l8h3&zdq@iaaozDDq5XpkZ%tXpN9{X_ zHa`7|+)8f&?0BUeyMJk;NE=B@mJ7$(QPK?ss_?Ia@AcZ5&%Qa`Q6v_3t!~soa?2Ye zx70j*7)rI)68H+m?$+Ni&;yAe4ZT1=Txm87m8MrYt@$lvo*mCt-CvKyS_Y3CwenSe zeDSY%KJQA$v*;oI7{0*rn=J6W$pZ_^c-Vn5S1sQE^s=tSu|4DJ` z)Xg-a?^=%j{+0TwNmZ4ko!zoM*Y@=M)Ey~NCFLvkcZ|~9mfcs1>DVA_V(B1{zX6z< zP-5^^lZlsiVC!^irYUe}9RSqc=}4iO(~B2!3JUl?J_)|Mp%^#4+@F3l`-Uq;O+@fE z87*zt)|L|hbKzbO7a`h7_a)lrD+5>w{El*g_>@jdKWN9RUGw|y!3Ra|Kd!$~+-F-F zWTcLQ0$N}H5+kfJM7F}_vKv}kScKjJ|1w8gxFeTfc z`T8@Y01{dr%B4YgM5=V~W5A$pXZyoMt8cFfl23Ub(h5CaMv;(27`QaIwlZ;Y5)BJ^1~k!fl&r@kipDJj3cV{c?wq@4n*1^kRgidSQz_2?-_NFB^N2 z@jFfiu)Gek@MkaqWGVohkC;{{<_QfyKfl+>dO33We-GB*3})-_EklavK*6?}Xn2LQ z?R#FINHi?Mw#&iHRCy?^JpX)Ps%PJxdFz#D}!GBWC0-Otke z8r5+k=6f!3^{#D<)o5R;^vudisQd2Xg6_tjKd*VrT1|ktW$EP8Wh#AmyyJr;pTFGG zQKRetqH##*n!jmf_m$b}qa+}d5dH_SQs(~srQdSt{nKsqzRknNzF`pA)}?yLZvbSg zPm_J4^n0@@n558s*OHK$m$}y&pdZ)wQ*s>QYZ988nsxgXm6fLR?U9v1LQs1n?Pu8W z&F^h~zYqiZ^>29=I5{~V)p-eOvTEho$8i~l`}rXtZwAQo(MI*=^xE3nj_6x+>*Z5F zTnY1;fsCMlwx8MIW*i^k)hb&oBQMOM0Y_h09*4&bQxsk3t@rGu=xjIAweXFOK~#D4>PWz`r|I=k)m@S~v~eo<+G z$Rm=*6g!&6u)g!r;ADGu_Z0)K)3ayN#g;u=$4klS(!s=mWt(CAll3!A!9>!G5WY?a zYr1yh6=XIxHgHSGPft&`My--5SC5eO7Q&5UmXCA5{E+GR=-^X9^?m)Rh;HEd)^y|7 z(pVaSApj9`a&nRY`VKc5#c+b24|HNlH(4(I<*Qd1_FahgPJKMW*5uG2`iyr^Guu&do5S zSl#g4`)U_K7rQoX$ra<%luPjs0Y_qeUS?i%|J*d>M(d+IAjw8$fAX0fPh7OUQI(`< z`ZLNyr>yOrE%CDMD4mMM={3=RzomZLBPDigU716>*(OrGSuG)%knHG&WUw*$a{zQ( zKK3^zvlmb#!9}Cl`mMX7u#eSO+_GY~R!WuJ5+5`*3NI3r_bPNoai4kI{FC&c?emil zY72MkBo_gKKcM0?u6qlzQ^WA^Wr)@00K7{`DwnQgdHtxs1g+}%ia+Ezv+dcIcb|(6 z(^O?LRM87~ISGuue=*zO;J9;x!Y(;BGk$BGgX5!2hS`#|;mX=C1@39`$exGUgID-0 z2@&ER5`r5L9Gor{i2q*TX_{%G%j(d~(e~U(iOnVLe4{YfNoK=&hE#TU$zriWb;*r{ zzcjp8q<&DWruqkvS$@*blnZwvjpr6~uaG4ft*Lh1WV(Bo5`>Uf+1YHY+BqSUwVu~G zIe+viR=ccfBSiwTXmEV|Wh3u>t+k2j;YN;IOoGCna_+x*IP}xAN>nmf`^%s}ytaa2 zJ5}?W?vVDy!F*%-(kH{MQ8yHkUBf6UN}H{nD+RQW5f>eTe|x$CRSgt_KsFih+PuwC~e@vH2e;T#Irn6&2QwjEvxi_7Yntu+Cgxs$H-4}KA(#-SA$v)O2{ECP#;O@=)&PHr$eHG zY>q+&&UW-ZOjVO0-ANW(^2rRK=I2$cnXu?dB*ZhG`t^A7O?aFGzIx~*4}OA8!yg6D z`S5R`T{%|8=9Qf`RK-!`Z(BRsP;M!k52u2EerH&L<@fNbM~PT;INw(fMgBmg6pgRk zt{VnQ$zrq!uP>kSc>_6u60`P&sr^uL|#m0vmcd*XR&|mafa@q^P;2Wg{YG zc0+W7wnZvS2AX34gv|Nz620`X_{Hff%bt%5`b%+`K?PA7+Ih2?MGIUs=G{mOO8adrT08g z?$cMG^wR4HmpB)%_bORwF`d5~{W4NiKRQYc!e}eVXJix{i{JQ*hbnW@Z|vpeaz zX?n0Wx;&gu59n|kN>-Zp$-(hZho=N-u;z>CI+aTD=PCD zwzb9Xr9%Bmvst{)swX5f9ZGR(&_7vvSRmTNa3(9c`3++9{Z2Al&oor8>WQ8kv%cq&E} z?t4U@<*ZTW`ge3*gocq(Mq-+DXDE#vwFejgrOj;qeNGXIXbA4_Mm?#>K8%)OCazi1 zwHF@vP^pX_75*b{WR#L|n7Vt;RP)bh#Ko`;n9@WLU16tqMUpD-+wS+C@@v-=2Ig`) zUwA&Hb_#7@45Jgh`X6omW_)k5IQ`3)FRg!O5(N>{z6NDAUi(>Sdu!k%2Mxdf@lX&~ z3P>JI2?wkB-)8};$#OdOr(u;jPKCqXNChGC=x8otdoL&SlVoJbj`K||uCc=M-romm z!2&di>Mx=5VizZu-T-KX`*Cv!Q#=kfc4JQuQC?miE-^8JrKMp(Y~86>Ku}aizt=Lh zxV@udDPvx&ovRlJQd~NKq!!kAX1wj~Z6qCH6JNuH?Puj9XP8#RD9JYSQLfn~ETqI& z*m4~!B`F%6=o8kjryBJeQ~I76%gm>C=`ZsuhNgNO+gxJe9ONQl#UhofKnO;pWe)wa zX-JH+oZ>EjC|bj4a8O&jyG@Yd6>559%-zZT(mOotJhPe{Lxl`D=O6Rj!s@F`6ZOz4 zYl5AA)!kS>uB-A68P5vS$GGC$=<~8cJ*G5YkcCOE-w&6`P>6l4H3^{Yli)LKfV{FA z8sv3<|FESyA54>Xyiq}KoGt2GGZzxN_}UsJ{9CGt17#jke=bAYYW$=5!eMknR;PDv zSSB$eLCqXO!2B6>F`mMntvUa@+QQnKs>Xr{)2>tAmjNIKu*4PSQK#|FN<3!$;;(Zg)`gQPI4JnELU76OleNRb=ih0g&VK@cA#+1@Q_Ls)N!bnd7_9 znNXfVr9Y=(6@#E4HBuMU)YNR~(8*#8JU@_ooaMF}vmT6yafRVFe_oxLv$a~ueBT{Z zBtRDFE+8yy2Ca+bUtcdFvioHtH?(B$gAe^epLpK3zcBn6vL)K(^hCvXY~(Nsv@!GD zkNl3|kP1H<7^TY*0h}wm^HEMFW6dEm;D^=VJA%FsDIp?tsC^AH8eKRKJY z?k;)kHGuBT%`(Nq3E!goR=I-4qMZ6$Ie(auaFTVXSX$mhx-3*2dJGH<2#1vI`FNLO zf}QDaJP4QE>I}7uV3rRF50`uX{0?lR;7sKNCQwk((9pK__Sl`hS2wLppZKr$ z4BN6rJuln;)(-1!(b)3l^3&~ESx{xuBwt~JtSliRfx3Ff8YM}}J|U9E%XRZ6HV+Ta zqY`Vfn%dg_(kD8Yn3yTR@&X+aQewH!6)bO;6s;*dCgF@N5yZiMRE38^UEWsAS|`}V zyIyp!u&^*BEKK&%qbq-YeKm8tZa-eZ)5IA`S>biei{PGxuK08~SWqz+5zW2sUB~Bt z_mw{XengUOKjKha0_*o*{IQr!~)jaUxC-Y>&Cvx;2z3Cfm_d#X}f zN>)`p&^kx)w$9t9{5jGFG~u>MkLHyt@ z3pc0@9aS6SO`s_XA|G{<)$961C9>}~|3v2Xp55E+{F116Qt$v9SOgg!_dVNR%N+?zrm^dZ(R|U^KNeRj9ke+&h3U69 zzu74&Daio6SJE5q>gw9!{|mywRjhII!~ho(H&b1k0xL+fMGjmwTmCald|X`lyi;Z; zIjdL664^d3#eLe=QyG|m{imQH~tfl^}i(2tXnWme-$j~Y?fWBBf<&SqHAv^$* zJ|4JZYB72`_FHO+JQfu~o)R1*ip_7uo`<>2j0gA;q`Qh#(1?ECc``%@*?;+WX?DlY zfOW6-$*}+Q6Q$- zPhXFf52}}LTzdL-5Kj)rUFDe4qGo{C8vCA6#!lZi>fiD`h%srEzEl)^>+{M>iXZX{SZb`X zy_61U(|cyMAM~U%tu=m>Wh$G)!m*+UqDoluC6qpTc}CP_A+%JWr z#Z0PHSHba@-CROpXytbn_^-b%Ey@*8kujH4!Jl4c;|y_lNZh7hIWjJLIdjLQ+%wOK z07Z=|uZs)zY`i1*IuECnKZpJ}oMHHfu57Lr9j`YnyX(zEeC^Efnv`d_Xnz)lXWZ2Nh$dZ?~V< z$9ND{#Pl#GZ=eJZ$oIb4MX0W_sw>}4iF)OSe}KOBP89oa^g$#aG5-=(t;cQ8*9AZ$ zUHNY0R3`_P9u~7ZQ82sSrNt6i^y<=H&<+Y2{$b;k=^{9yd$bE$Ev3FzhF{hY=g6^I za18s(SG;L7yaB%GMvSPv`SX2pXYsmJvcg5dNn9Mw()-o;FG$~=-DE#KX}ckEb_0cu z*_5ysZJ}D{x3;48TdN>f@FL4RV}Y;xps<^h8^iC=Rxl;H+UEPkYhNyrt|XrI4q-hD z95f=sB55uR&e(vD;pm*1`c;&BIo7cuE+j?VMM1A*$DsW7wn(C|KiVX9x#jK|pdF?Q zDFkV-^2GmgPk!;H$4ZbaHYbP^|C&P7619vz|AO94Z0%D1ajbswdAAo~cA8olC<`9n z>(4egWz9JcdTTi875k-kmEvMir<_e=ft+>Gf$ipq!VSzgg)(_OT!%|ka5pU;m5ZCT zPp;{K*CB)#QN%3Lr%>_k=40bD;?<33L3pgxb!&YEdL$*}_e2WTro=p*I&5*<>)w44 zaQ(9FtWn=GfZ_8%`x++N2UWu_iFxr`ic(ZTI=;@o*B=xa#YOkrXZ}Ol@U^$S)8)?7 zD@3}j8^J;~t2ULU_-i{%*-TV?Y`EQJ=9cagfv&AC9KSBYE0u>8kB)3DZ}Sa*n6Ww4%-Vs079%M7hs=wLw?XC^{3Ntw}%utM`OXXcyb-EwR_d8s(uELn++ zXoWkxd~Lr8F5Y(JrUZNQ7+`Oyf?->NuTx(V7zL-S+YbSbK_<{!BNQT z)v!OrTQ5vMF`Au*F)(#Dq4*~11DsdYL#On4=`+wDjr8?Hx@T+Bp=RX?$A7f&<&VdY zq2ZfFVu`H3Q>C$lbtl^!25t_{1xlJ5iL#ES>sWZMc{d*?+)iMSC(lnq4oMUi(rZ>K z5c}jWvQnJsT+odhK9U;%_|}fg5y1h z@etd=Z-{2=UUm^o@56oVJGdl*iD`j7V4*79cW**(q%T*WU-O%0M?nv`Y_?7|zhC}a z%g7o|@`q)cMX`~ezckK4;ltguAPiQ0w>GEK9{LFE( z?|G<2Wf&b9ON1b~xes6x3!KTA9JDzu99N^Ju{ zj-P=pQJ`LKE40hht6$h#-?=IvEMCc$!hq{`ybxdbxCI|oUt;sKz^sk9#C{BF@_r+b z`|b0SO*638wVWrBQOZ7W!Fldf-XwgP&xt$q;dv}`AS%akHef@Wx3|=##6P6Ha|CQB zyu9RRCx>nE#y+9IqiABmadZ_xvsBX9-=m`xP*8)Gz+BS`9+$A8(aLAHKq@0dfoxw7 zT0LU7L2zxkjkhv+Cs84HUx0Y$AR6i4h;V zVnLIWpLj~Z%h&REfA#6w2+t`P?<$;T(CI|n{O;NRP5~+8inx!6!*~Vg{4l!-g7$h}Nr@1=QUUyvZW!Q3f~|9>v9VFfR=j^V zYw2^nBUiP_el2@gq+{elA)$ciN*JiK*(b-2)JrctC^hc#Ai&JI#z@{>eR zl@b41J>0XI>1n@0v$m_9NOB4Y2rzi~tkk+6F;Z+D9wL@1zqO>_zy#6S&pq4S4Ms?@ zaBy&Hwp(eCmJj^?HYl=$v_cf&;o$}4<@aA<6Ky45oZ=8&y(1wfhYdO6HHr8s>Ez_( z*1C>maEcNS%#|hLd&h2{-M?q2y!lvMZj8KPP&MG!TRX z6Ii}0VqOA}%Vt(r2^c)FLt#}Pj}}>gYmN8k%5!MhCiEY`S5S)g%D{~r!)huJw`C!f z`1|`8z%J3u)uV)7Zc{j|FgJMqU>|Ab8BkNQYSu%86fGml^=|rr85im-`**spmgjiK zJJS6}5mWT5Pg%7hG!rrL+Rvlj9nKl_W{7mPFbzkaxyBh+Zf#yh;a-Sw>jkYbO1Zrl zeK;mkd)jdii>vF{SrgYu!+a|Bmpo&AlcYq~&M3dbcF{ofE8giSS!kg)4NglOw6NI! zF5%HzoSQQ%Q8r3_6vf>1Rfj>CB3a<6fAr6Xg^;hBoEl=Iqc2%@zkdl!WO{yjj7vZu zoh}zfD4OAp@$3xJ`i;kbMQb|K5S#Nsp5vmDZR%kS|ZdZ>=oM_fNk@^}rcH1KKx9v`P?M#q|k$ z>Q80w-%q#snHjVMRTOD5@;J{wxMSUiLae4vCsx5M6k1@W>o2i+h}59gj=KFJ{rCKi zbkJMDWvJG<2NpcITwc5Q$Vk)o9|=$p%fV0@C@S(9rGrem-}6}i$|8e(8kc=K+wx|l z$bw)f&+ti!$nD!5+!vhh&LkN?4MH0CKIg|)DAi27YM-;J)Gz#2$3LfxnV6n|2>XUy zwm<6&+kK<15QSGp`E4NEaf6>0HQGdpgf8syxzb^yCMbT}i1K~3HDf)TmuPza`^$pe ziHKbIwa}HHU(z9sp^Ac0;6)$-Q=%UjDF8HV{SzbZ8yg#^FQK-ek)!!l;3WBW92)5wx@X=j;Ia%3MxOKhWpJZfifZ)rnkg9fN|M>!; zx|_#nu{1@9!T{uQwN&}O{(PDz5bEzyYRXPIGBy?qRq2rs(%k|e z1Pt7P#l?KzG{5>oqwK0O&f@ZN2v~M6K_3peEkqCP76zje%r?d=1pt}>P$DHKk9y*J zY<~i0lmcS9fa}IJa193mZcX>t{}bf=aB=z!iex`vy>8HzLF>CB;Kbm!X49vVEQWOa zkr-Y7UD^Z!dx2>aHezJ}ueD!!dAYhFV2oERuSsQjrtUd{X@lG67(u>8j!P$d?6+Ju zCpk(cWMq1!-wCKivNJ<73BXtoPoQ8_RMhm$jOm_;6;&XB52RfK>(5rO0heF~{gj&a zx8UF+1W{2}2{IQ5kA?8bO+`>F!CJu0?&%RC1%7;W5^bLyAr}R(f>>Zwm$yHjot-7T z)SU3AF_6G|;IlklDbn1Y_k)O8%>RDQx}z!i{roQ*Sp6SKaUedTw;-o7iHS{ad7b^; zm$TygYAqo1l3nV{Pg4MJxI{!f#RgH#aEF<1-I7L{ahNi4atO9x2Cx3@@;H`qD0A2E z+@Y}g@ktV@5E2rw!>NBLQ2g_{M`foeJ*j-Lt_Oy6rs0|a6t_hug%@mn#7UB8R13Cv zZ3cGsZnX_?{|vCR>*Z3lMbPtCDm%Bl>27C&eqb=Tb4*)aQ_6LLSph8bq}0@*khtLt z5l9Noc$xncc}!nlUxLz)%b~DEoBI37z}f8wE_Hw~ctAGjeNX#8Ba;M?pTJmc3YZU! zE?M%cC*WY-7&+dXgRZ0Lbp1;NIfE_6tfHUb9j%ixUS4M2El={uz%0#g=x_otC3^O^ zOM|kk_dII*ccX8qDnKR+S_u5-fuCOk{67F@CnclrfYvJMT>t!%2hBHnuahnR%D=zq zRXC+G%SZhxG`nvD8ny;_rs1z8;N@5WlTj4QYj8(sn45oo4twJ(l*vUQ_JmeOd~+TU ziMa|4NCTL9qZKvvk`nznZ{Nm)wY$U4m4kTHnXRC+SmAT#k<>&9_J`zT<2>PO2dl#* zs1)sNwEDg`W2w?O*F@a7dk??o}J*3CiTF7ujR6<3C zK#9YIP;b$%Uz(a(YN_?>z9;MaklZmO4uWN2j39mI6Hh6AAo^zQAsu)``~YG#L4y)1 zL*r-p=&k)>QTB+?OKxUdV;jV{bXq${P<4PmP5lBsBEpmoK+!YHGlQt`2#7m-ZbYv?ItU zA6i?pgwuv(`%*~nWF3wLxUKyJH8^aL< z+GJQ~0SxC=LV|wYg74`zj>A+P5j0@HOfgg3E7k-Xh{t`$oZYaB0>(iQ-$VaB2&MZV zVFMU)`t$3y7my-w+_lbkL~kFAInDtmwiLgo-{KBD=XFC{+pXROb#-;ngt6kleP#yt zVWiBSh5|p3eRWn1E6omq;b=KMg3y->$?oT8C*YkAgY-HJG`Jo%Dool0!j?1xD^7*S zKI^2%DhC2$q2>OP0T(7HS`cIl`M(jy0Z>S(MJ!}VNlD0xLr)WMEL4O4__SZ&0J{bB z);I?cf1h0M+1VM7=YbtE34>e{tOFYAGN(a3a8wiwim>YzVS>2c!(U+wOi4h1frp0- ze!DgD`u*1~BF>GB_=tn91)ymhTLG08>__!VXA5?Niat^Yv=rdHBULVJ&}`FOrjA4+ z9i~l!q*B`tAD0&e{qv85<-BHs5KiTvsr>)2?|I!Y-1PP>A=I3ijfrYl&W6xSw+fyu zsUTwyArTS1r9BWGPa)|^t@`XOlbxSTU7(|2$OLu*=u2Q6d<#3UnEp*Jw66jqA|f&p z)Ng(g^`wF|lp8CvCnG2Khh?XU1;Gc2Ud4FMB)t{=KAxW-zw~%dfsz?nBf1rpY660f5jsHyWnH;B3(TO`QQI|2LK53`zj- zl0C@a2rv1-XNB~kK{!YOND(yMU+wB>_qzlS)t7H9-ZTgG`0}R2n+H+Mxc+LaC!LCV zdRGDenC|p7{@urq z_^PU^5Opy3h4h<&{3FF9Ej=Bv?bdo8E>?aA5yupM2zFd<5zYq&2G;Ry&n;4B~Y?%g{-=-T8KAxf^jN#}6VhV<7_wo>sQ0(*VN?<7vQ=+V3n)Vbh7O&Dl$j}2%DRq|HSPkQ z2keO51m>)Zl9D)y~C_N77qrvW@cU@gNr~e z5Je6)J`oIp5X(YvINQ5|mJ5cciLH!&Yv5cH=t-~ zum5xvadb1wH-?a1NA?it9U^X9C=|dLw0diS*9^F7Ecm`3gxzjJiy&UMZmk==NaciJEZ-nMu9Q%e}d|c^b(Merr!Vfq)`LcVaq`5LnLoo*s^= zgZaH{Lc+q*Uw+0e7cPLn!9&Oe1hxU#d(xBd5(j}+pfFkkt_v;g`6ZN;*>8>byS>27 z4|ll%6obA3Q)#6n5o#2ncmnJOn$lE_3Y3bc{7mgf}V5w9INH;uiYXC?W z^vy9s1HS1^w>cBw3P{o@6jF*H$O1uVN9;4ye3mJy$zsRYagF}~?LvOd4!#d;OsR6; z15GE>F69i3uWv1_vW#l0)ZJBj4T0r=%M%$0g)D;_z09tat2bES0A?1Dkkw!_16)CT zngzwh(x9Ot3jq9wLg0L89ACKX8pIS9u_R0QCmkuf_y6q$m`VpjQkug=6~uy_on7P~ zH$8C9^YZZ_BaV-#u3fXMC>UsJ!T?f;@~j2Otg75ToLw9)_*?6WTc}id&#{!)g9aTR#NQsDh0LTf=EC~L1 zdw87Y78e(HoaE@2bAC!pBw}0y2mGUQhs0EaFF$AC^?P0md}MclM$U+ADmQS=9}gvh zi1{7n!|TtufU`ih1#}_>UK*HDNLcc4aBx5%BuqAMA8nh7cpZ_7pKf75xyM0)7LQbk z)(H9tWabza>Q476Kndfusqgg2bwPJV>3mWo7upVx&6j$U`9E?Ad|!|iEKA$?8M6=V zC2*YtgL!KjKy7<>cQXh(Jws))Lx{lb=H_Msn)(A}K+QuW7v`Hc zXF+-&sd486FWn=PTELIUs4!@jWWZm+|A|7uYjxnWQeAVivjs3o59OYjnb`z_02d#h zb>}bu9jDgD3WAe0IFIVi4)g^C1<~5+^iTF`GLG?Jj2hG$5N~EK z95;V7rF}cc3IC2Jiz(B9rQGwYyR-BGMW7PaWz9RhF?*}p>Kltul>sy1m1d9U9pYw) zS#G91jtljatDK3#X9`JazsJ3M^2%x>;wknO%FA239!c&=m50wZDwifd^ph+lc{;qb zm~@*X0xM4tG%_HyPjlmQ0H?G3@hP;rS{Q~v(xH41UK1eoOG#mXB$_rJa0tV-mqHDAgl0G5cC9L@|YfZXhB(-cI!S>$+>C7y6J(9T_uPR$fPuwuQM|< zVMIt)o`SyIp5)Z~6o^XC!#s`6xuSvs6TpWED~0?8a!NZ?Z2D>=#sJcYA2Mc2RM(T9 z0L-Qbc>vJk2ap2_!4eM2braAXpz4ea41O3m#9PpPoSmPSfh@N&URm5Q55rc+FgX>O zkkIA1S=$EMc+&JoUEHGXe84jp!gCBy_G`GSBR2MmG_xS2&o185w`#{(^%0d9tMvm6EBm1FCx zC05V+%F&us7czA)_{)kxnuU{p7G*wAKP|09h+y|xhGKE$YfWT1L<9xp7 zr&bdb>Vz<2lH_s;jv{h{S4I*Su0bk>mG_N5sT90s(PD0(L|%|)yk@TW_aaR*5>w~D z-WD~DkGwxbLzdhCd4a3`?SVfV@`6!;Qr;;Lc_H(a2`}&egM-P2y1?@u7QCIEc>y!3 z#X(oC6PDklv#qsNth5XdK7V!;PE7=Pu)}o$fr>|~pG4e?cEoPq4h2B~4VkO~ULgaz zpUN?4HG7eVgV0L8KRI*-Xh?~5|7_u^F63vZb#n2W{prCwV*LE^kpG?MT8TiT_yH(= z1WKY=`R(t}JmUt858Zrfq30~Xnpq0g03z%S-C$;>(DQxq;vPhRwh=sNK>?m~VG6TN zpZkVk1k10cx2d6FhAd*I1KLuV@Z=10b8`y44cMFr4{sNy*8_#tJ8k`vRLGM22dfE` zx)*|SzUk!Wu}{^wKeVDo+`tgyqU)K+a2wn_Cl{BNA3yRQtsd;}FTjljt&tDT4J29d z?w%f9Pzs^*j7;H2)JEWVzM`LBUVZ>50iWX%J$)}AeEMn*tWgjp;AsYkY6(sA?FrW@ zgf75Et3b_0tAL#R(%yRT8zoYMVOv8VJ{0s{*q4cq?aiQfBo7w@V3Cks>{Etfd=bz9 zC1quKW#t8UDuzy{QH>iJs8<&503pB(8wm_^2PXJQ{D1&su(S;qTd9I(n{EeOt{DRS zWew&ws0G+5#@~HTfgYmVJU!V~cQ-fXf%uq!_^6OT2i-DSxGcnj0P*~_-Wg=w6mU`e z<1x_qt8(2uZ?~tC3W<6^KCaQ++4&k2CW}f~T%oStE~<*aIG`_6R%B3Srz>_mf63w) z6vS(wdKMl&gE#?N(b3)g8R!NKp2S1ldkFXe~N?d^;gAF}K(r+UhP*F_1S_*sUxrADfz@`N^dt!qv>o_DGxFJ1}9B zUcIW|<|csX%AgcMPDT(l;P?iFFYD!Md)U?1)cDl|fFtwG@$S+?P|x5KHQCn%pD&Yw z^q4#85hjO}Di;sru?^lY)R`%*jKg~)|nC8MKL zfgBFcEWtrkzV{zLYJhbGE;MyG2~hlP(FyQp!b4YdN2y@eLO0*g*n6K6B(fRc6u_=VCg87O00P^0+wCK5!pg#d>BR0pU)AQTIMuR{<(&liA((D8|a;Q5)O%Ljc12>J|2=_pV^AmKIv zf_v?~9CC1aMd*HIWhHWwf=jIe&hxlHwoM!Sv5@Dn_yI&p*UpZm8;AZi*n^O^8Geox zJg!8j3$#4hgU><1!EDJ5(5I0G5`Y31K-<(*GI$Kq`>hccdg-Q8{rLDb?o;Z9iwo0K z+H(Lp+vr_SE(8)q+<5`ncGfX+t?X}YF#u0|`169^<^{By0LFsVh}idR|1vmZ-qMR3 z*%ZIHfPqTvU_VIPHjH=x0>s!5x9j$Rhq2&qk$M?*WioTx{Xtz%;eu)G?Dk7Z#aAu6avXAB4$ zL_i!q0IkhfgK!yHHmfyb2g@H>+NlAc`78CGn6zY3+SKy&9=CFx{#>RgCo}HeSfClSVjf~g4 zV%krDv?82yetv!nBrMh@f)e@hSN86Guoa2&j0;R&?wrFx0s;dO-VI)t80Ke?gkhWs z^04q5UQ zG?;fmn`T`o<##fy^@s%-$jreZJR*Wbzs$}QP@7Pb7AywRXe=u$8?SMXMoecg^=u&y z)CB0-6&D};DBn}I(+ejweSQ6n$=WVh1p#2&2#mEK(>`{vw6G|&AG=rPFwqJ214$@g zE@Hy%CuXxq3wvzr5+vnkb6P4Y;YG&lVz2!DQBW>3fFz;r*o~+yMSKEggFLcn;S>eq z)Dp*pu&mhg`*ru_vV))eQ63 z&pT4#QoC7TTV0q>X{;7jHZw*WnZK^Q=Tg43BK z>IrS@=CLsvNNY$p7f3fqjMyf7%l#w2e{0lv9S4hvN6g0R7|lST2jn7WW%&js;>bx! zC4fVM?1^J(X}LCD84bB>ZhbwMzpEJ*%niCqh>R^AL^uOb#N(uMDjn}#WMrxre~R_w(Hkl!3KveVFxUSJNV2(!@@Xrc0DM9&O>YlKI1_mL#h;_ zwHLrhKNv2u3FSL{i$7??_Hc;tn;ygt)(->HZX2P}`%S)xhiWM-v{e_Kp7&#!k>zD8lIO{7$yfhPkI^ zPH14f%;Ikb$ge;k@1z(CrM17vLeYZrkz1KG1rQ!t=n2ugpCAwz%!Q!yWM1xSZ=VLY z7z1ExcuWEU^c*H@zB0S$9Y?oZgGw8%Xsd>8o z`!1-73IJVW1Ra60+l&;X@?yI}WP9!P-$!sIxH(xxEA(LJ3<}~v=Kc&cR<2Z%je{d> zeB1zRFxK3YApK{=UzrBY1G*oRUc1VWnGvDk=FOW(FAt#R+{Om61Z2X=1=scS9Rhs( z0eNkr{b`4>vM_jV)@_$5@Dfc|=-YQ+fg}Vv|@ z5O2=if#oFrO8nmUHKoEs^(t|{pWofu_x!i}8cr^`=^zBU@K)-?>Sw<&JhpgtP4$@L zJNemPUI=L!EDbN7+3Kz>s1r;x!X`A!qPHdcyIHc4!`0cL?~Rg0vau`6D|f8Ublh=v z)BWgkgt;#(YgFn>%j0D+-%X=<=ub{r>xs=K9C0e;PP2?mDs9 zYU@SRs)eSe(J#nLYPC>gy&kOw>#e`y)~RnM6BtT;)O6uFoMNQVZOP4&*fun434AR8lumOUaQPf^Hu&bUaQjsTA2BM~D z{m0H4kK)6;#z4OrAHR3Qx?4)g1ANQnDjnMe z2j`>rOZAHvHWIqUl z1q!fBu^zqk`pM(;k@NrH=7~m5&9s-%jNpEOiBgcSGMb8}!A$-{!iJhhf9g2d;T`TY=`utJjJuTieQIKhbeN6aQWK6X%ikb7B!jF) ze2on|fg~}Gd`jKBZR^&>XSH>8Er?zj?Qh9-=f_d@KrVI)N)bKGg@r!2HzbD*OWNc_ zH-u=a79u_*Eg4_$Md$L((gO$Xp$~)NEKF^O7w=5W0=UId^&L7j`py6}U%nBN_4RAl z+&Q*G%Cy|xyxD|Jah{N9nF|GC#t$u0(5>*63T_#D?H*ooSw19Rj7Pwx1Lb%_{{_w^V1@-R0lB*n*VWXm zPb6UY%QsdatwHxgk9n{IY@c53%KqMw&)mt~I0Tslbp?nP{GREc&jD>ab`NG`FFE$d!n7GHY%w$MF9UkXR{wnMIlo;6Ty3^t#!P;~1EPDhU( zJ%vF`-jyC@_z8vx_@n??9B7pY!vTiBCbqYO$_wYgP764t5ks}NYVJz~iB&h8fCwam zlrUPC_Ut}03Tr^Z5XOu*8p*t+0kAqtU&X#6`iquu1+w>NW^}N(h0JVO1s%sB-L(>j%rvwhA=aeHH!nR zU=>+790 zKz?<;B}WTj}s6U2I7{UWNSEwT@!TgC9EgoK2YO>5UiHU0L8 z9U&TVFeP{g2M0Ii%+s@m3z7o>oE)V7WpMzx|J8L+0-Cv_-?xvJ0Ewz>serp94$&cjb44I4{3;jvmsW_;>BKt!^hVXUPQRXqTcva2t(aEa* zJFCh7FGCRFz{#dee^oPxn_YRvlI6sR=mtUo7Z}41LenZX+T%^$@ZgLR5l=X31>eBi z>z^JzoKppbLzKuS=^3Mu-m*S(`g9Z_j*JjUh+DKsj6hJ{e&H-%i0+B20Cm<+L0K7J z$g3Q+Z%cqxK8r&$gn?NpV-yvCbBGKN$3o!jo}NAxK!YLPUo?>ChFZ>Q&li&p0M}yP zm&ut8S%lf+NgH<7%E4Z_svf2brbHugYBNFG8QL|D%)84cYav9A{t2)i4H zkHld{#?^5VbH^&STwZ$6Kwm#MKR+5Wf5D&QIvE)mA+hEXF2KBEv>Ol52@|2#+_@gB zq$f)C85!(VP_RG{;2^ljcg7B`bx`o+l2q^sf)(*4Hcak(^!3J;mi5`*Lj*aE_3`T(m`zw4Cz*Z`8s=SuYZu&$kf_nAA?}x8i67a-JQk15A%`W5k zX+;t0%G>tM&-nA1C)B=w)?W8nm4Q#FX0Cbm8q{pc#B8nls4Zlj;VECQT)+ad&RS2 z+YU`Rzt;og54Lnq(ocYhDsy`GyZC^4ejDjDQ8sutdzJ2iw6uv*KG{#^j6uA0fZQ23 zd2&u=zc(m!Qja=w98-<{uoD#p`?~d8qZ*Y(Rk7 zxNZ(+8^goGdb&uiK+-4t#i6n|j$cp7boO4()BMZb`F~?RMa)11F?+@SJr; zTpKP)aI=;zgnuD^Os|KJui5|m@FT^;A0>a1@fIMg5)d3Gr~bxg?`l3joSbokLdjy_ zcWz>b13tocuvj{})|g-@^vxg;HB4nB7(eDuA|{KWHkRzls_h|e!=||&+^gWB82eQc*ZUdc z09x{@lm1#UqFL3});4o~^vK}jxe@p2+$Lgx{&suu-)i@5v03Rnh%^VNoS4VG)+uK)|#1_2?BC|^_Y(@^{^#TEdhc% z5;hqH3}_zauncRIJe4S0l9nM)=pcda;CHlctUhzWL$PpO?_|XRm}sUrw{G3Njmi1~ z0B;z^PU~9UASbzk_e&G5nEu&p3O?xa1fVD(y1(6Eo5f`pu`3{Isq?y0)P)i@jqiVE zF6f=*kzYQ=w6YIGE9^w8J0-@ErcvGjXK;#)XQ^m# z4y1hI-L7BKp?PBdAGa4cf_a*s?vz;I<=BzTzeUwY;0yR~nG?p|%7w$+VD5E>6`hFn*T& zySt79Sn;cDsJ&g!cHilVWRco}SL4=Pk*L17Wx?OyNEGPotNq+g@blR8I=(rjvCcy@ z(k-E)=;llaO^kvAPoe_KAJ#m7?$dY`BrX!59)89T7m@;|%fYgbm&y&s;G2AoO4t{z&<8 zAk&7mLdH%aGLT+v*e?`TV!6ZdoxZ6%V`F2p?80^>;vQhT4}Nll!}-)jnD~SR4rUe# zF$Ce@#fumDJ1dHd{a|&$yhUgwLZA|{^Xk>kn6qf=GJcZoy3EsMo=mxWX|TM4LhXvf zV@H0DE|Z^YIRaLw0Y>5dm=Nh_b#;Y6YEeUQI2mH>Lq#ZB=0@m7ETFGR-%jx8adDE2 zeb9>0lNo>bG0#q$nebr=9E3$0@6~nS>@r&7am`J%zps87B>cLdiwP8>Yg_doLM(O0 zORioRuAs0Nx>Z;+B#>mAUvrdiE#C?E-{r?_i(eS3 z1sz3ki$E;gys!{Mn0K5~_ZQ2sma2pPL|?d@6i9$e?Qf^y5pQ00ZAGKb0k|yQ z*mwC0^cn^wjTx>ZEefSUl9E?3F5rC_)vUjQV{^yzn`o}Y5%2A_6F-60IZ6yJcA824;K=Z|H@bvzSA2)(`y5&6SyaFc; z6gO>(xw*NE%U?c!uBaVl_Xm^g`U%&U=(Kc0&U@WkTlD@DO1iE=no=`!*xx9Me0KS9 z!z3@fc@!AWPMo}CN)Q=3&CZ(uL2;JDBDCTgLW6-V|9l1ZLA#au;vVLA@d`y*LBk8J zB}(lj5g$6v%;^s-6cyI0i)>+GFO(pJE&lwy{kTrl=AvaF%|yJ%hIjAQFdY`HTnv{W ziFhRyxYL+4ZjD=`x`0bXr_IgH#q}hAlMr6!({iPMPvt%uo-5w@97gvdw%k5%?{!Gh z`Gxx;T|X)bo?d(FH+n-{_VX{jG4||<&a$Y7reJbYM(G^S_}*Bf2h(VXgh(d~`a)=5T(CbgxVIWsp_ zztduT48lZ0r>HSw46x$GA@uoe-^Am^KuHQ0*1Ft&$*V6X8?C3iM(rlXYj9gU{)L1tloOl0G{O+2xI1cq?UO5=(EjqtBkx})Fu0q~5`-I3 z78Iln1b|~W7x3pmqmb^FmlZ%mVB2}dm%*yY+H=`?@TRxh5@jrS7e!I}2|>?SE7Lfc zW|Cxk%^a46Lb_ZjexWNiu763C(t|lmhd%ys4vkFt@D{Daazz8JFPGD!pM70?t;VE5 z^5saykh@}tAzU+SpT$7Suj*DKd-evD4uzP|uo-l*fF!_cf6l4anQHC#8v4+oCiYO! zhin^^d}=*HjS3DY4Gj&MnpVgE6ikh3!VBuNDjy?Pjm%8%t2*IK)-qY=ce^WF_{`Gf zlK~GV=Qb}SATZitU0#4Z&E(7X`EmXc5h%RO?zj#cHjGjt8n-akogq~Bq`hm57gjV% z9!i;e?eXK)ZNHj>#=4eQ|3!_qg78Bql`#*AlLeEk;@tYqbhJSb?=982%?2S4&$=2k z&YdB1KA*2xN~eMwQKhycYW=^0?wv&ApK_&(R{=X>9;^)Ws-4z&gb9KIU@czkZzCf! z;;-}}FGK~*O|)DeQQZ=kke~#GDhz1#Hg?b)xEz1N;UK!F*n5(&;Y12J$x>~NI_MJ| z&Cn;Nz<8m~;_{1mfNnjSgrUW5OjvqiAk~$hvoq?fTHf&}nTOP9dMIwyMNQDFgNN=s z;iK-m+O^G7{0o;N=xaKVbg|ci^W>*Z3q^iZ&OC>)O~_33>*ea?Uv&>W)L~}bB)mPe zfeXhr9>_K8*K4uiAX?<$DdB`7e*9HSAaj(aL^cL@ zIM6=6yIgO}v`jPg#xvdW_R&pp$eMSlivJ{NUDcoD4Z+ykM|k3tA%B^{6;2*zh&m{& zYLHGCpht`umBr0m6R6aPxBmDL4L(!2iY^BH8kjz1h5ZgdnoW#=2hQQ? z5tA6XDyGoaz=(($1SQRt>|n@rUjlF!2eY86swztW|LI^rZ4~s1W`r)82r5c=x8Yq* zW<~Ysd-0Kbd&FO*(?7&gw;#YjyfboJJbz7063QRdPd88nNdBIJ%N8nK%)Mv)4YUQ> zcJP2*?>d6BFL>A=$aR;QyYJ3f>*eN$$0f0`#Zd3HR#skIf5y35YT8x3X4Hrgdk`c# zmq1n$NE+roxCq4vSlhV04OF5%R{Ju2vqB-j3Hg$MFJ7RPRX#6Xl*ST_GYBi{zqHNL z44M&jvo-3$W!z*KZj0#05nao8wMF|9O~6Z(0s{lDvFtUprR(V`v?H&H8>&R`=VY*@ z8ezO@3UoJt1DS|@bK}X(IPXs@v7`Y(e#TAAJ7_2~TCKDLM@K+dSmN}-bC3r2;7NdliIO`v`b$;&P|H*e9 zy==e)qqpu=FFgA_(-xY~GL8cRsjM3gO);B((F4L76_IeKb6hr@oV#>69^mDzX1y?v&5CgH}mAz7v zy_-5?H5^XU&|N>~i5D3AY4(XFBT+|GSb?u~|F$G@P`J_u;VV6RmJ1>VJI|Vn3q^7*rH5kFjP^I<3rQ;z zF$EolL0SY`Wv`OmXDVAG4iN2mkQ8tmmSQ8pKW2~<#n}6Ddfyis8XmlL^7rZA7&#$( z7{ZNEHFG&+#NAa+3;+HsG8m{qTn8C`m(q3)TD%0`)R}^uW?ykD3u9t~4*}&q-xU{S zu+jeKj~0q&5!<(oBlEtT{)?N`2jbIP&93(vkFdh2H^v`Q^bsRu15}i<-ywGN2r7Eg zaAybI2vUqJlFZezva}MH>E9Mp!zo)mS7Ukrs*9!QZ-HaFMOU#EFHC;Swta&P9{RHb zHD z80v&(;^oC;JzoxV7ysbk8;B30paZAj*Y_Y2iQmN4FqKZT8gn-ez)a1&dF>~SApNhc6^i~ku{`3UlSaRx&O*!JA+D6y3)We>u6Pq|${;$* z;r>pcuw4JdgyXrtt%G9vyB9+z1J@od-p`4l4H~qfyZmKG@RAdCC+%IuVE&%ov{2w~ zeb+3t&I8em?y&h~aG(K`XKqHFjHH8oYP%h6BjinQW@vqgVEw0;724qe5y zEp~iXcUi@oRLwV1X3l|V@Pn|U$`b}fbWkw>c{`j)tpf-P2SZ0=Vkk@0Gd*uX&<60) zbFN?!&hoAYvF8aNIMm{x+DkBI5RZNF&yG&ngul)$V_FVAOB^wpNruWhBjf*z2cEpD zMaMorLltZ)k3hsm6!+pV!m#(-UoWHia1~KhYBJb`9`t<_HV~Mcy=Y9BbWgc*uT+xFs`2jq;}O#>pVKxgNcGi7P*Z!+Bek7XHrp4D(pKcCDD0 zUd`(W~9$VZ?M2j0zMJ60}nKg4&{(@FL9qEJc}gwh&cBym;dVfMctE+Jcb4r7x^ar6UN z0#!k@lmZ3ECTUd4I)mI|*cmuRxbu`_w&(U9;2T|x8ZbO53jwnK)x$B)SI9jy#>(KW zy`?SLv$jnQ2La11%5i3+{WpU}CtBo9X`3sEoEeF|cch{V9-6FRPFWeB2y`4qROnSX*c zOQ%@o#3!!49KqhJE!zdIq$Xr4#(F{%Fj^2g!f1m1GRTGviDk-@{uv*Pt}2Y$iKzez zuE27(8e42GZxNikmL^plF$@hKh+^OKZW+4*MI#tvmq;DY7|*5p@h=BJ6c3_@)k3cKv=RR z6#wgJ8N6-9z4rD?P49^gbVh#=JNVwgA_o266QvftNWngIVzgtRb%R9fV>KIr4l3j!Z=9p87 z#Klg52ojgBfgILYTGF$6v8YeHs9pfY)nrP%9t|+VL&~y|7kPW4J-M<{2HzTEeU6ka zuVp$86%bTK_r~lr+ULTlYz_;LEuL{smd^~?Y?;7*SEqaxRqU5RRC6v((uZE6e^ma( zn>UH6y&Vp{9bTV3CRf`1aA07U=kL!SFS8=$#0H&YqyPqT9_ znm5A*nbGzlRO~-IX#7JOdqxBiGu~ie2z)`d%fkK_0wwb3R|3TZ1>qXqY$Pq3tQKOA zCu3Dp`x#1>J{uVriOUG%EB*drNN?uXS99C-5DWj!0J9_F@OrqcbAD{TP7asNiKFKC zuU^$?v+lxedcU*B#3hCBaH2WkxDUt&>frUGxre``I&9tK1iZz~_tGopT3BB*Kke_= zezie}JJ3 zIB;M#&Frh48@dAf*Am(@$cPeR!U*OxX}%coGFfg4RP1v)8T4K&`L-K$>C#2G(x5b; zBpRXwCD`}&4+j4`+9qZPge7&#F@2ZI7xE<}x-Qk8J9|lNPqGoixt1zyF4%JX=fC|n z)}7mqmBu}~FXM7!X}88?NzCBkg37VFqey-^>ii+i>*K-!*+oUZjIu~!oC@eWhr$5% z@5Y?SLAe*7&X2hz@TF+>0Ze&sj=_M&>*bRANW^G#84;(W#(U6&+C%6v1tD{sou>=4 z-n@A;%iCy(Qq$ggqgOFAQt1+45J3$WbBV5uugcKoju1wBQW=T3VBxF(%`gr#IQu|# zX=&+wFc81!BnCM^W5wc~5Vk3y7{Vb8nt$?`N~6LFP?#`7`)FZ)ismSRdOVnnCYnjv zE=)XF$KezgJ-J~wP*yhtQy64VpIPuzHB-o(#7#7zp`ofjdA4kBi+=XtzKo9T%IvM% zw+o&#zuzH9d`VeZU1)l*qy?YNdeSw(&)EK|WVZmj=!3NnPbDn9x8=AZJQ&>=Jus7t zVGZ%|@kRHxnBA|a7*F#B33DO6S?~9l^e@ZC%MgWb+1z{`@`+#6)Kz5_JAvik#<;>< z6XD@LUCU;XeTQF`(}ajB?o82fOUJDPQU-Ux!TC;2-dunjjE9;r5Z)UBVMe z&WMCdqOh+HHWK}#vHB|9cPcEmrCMec@*bxJDMxbPy)>n6_Uae?-B)G<=ov#iDZVg7 zYbt|O$*g0y`<61EPYA*b^1gE@b+>fd+eCdTO?-4c&<^~w+O3=I_@{9{Q0T1<6Ua77 znFXrpnf8*D47wD@4ASdHTc-uHA1Q&Q)W4he57|yq!#7UrM-2mSB=SDWeo|N5{FS(iNuOU%@RO(gh7TXWIeWQ{ey{|~Y@4G#bS literal 0 HcmV?d00001 diff --git a/docs/images/http-1.png b/docs/images/http-1.png new file mode 100644 index 0000000000000000000000000000000000000000..225c80dd1182bce4ed5176e6bd3eb1f2f1d0efee GIT binary patch literal 18703 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 literal 0 HcmV?d00001 diff --git a/docs/images/http-2.png b/docs/images/http-2.png new file mode 100644 index 0000000000000000000000000000000000000000..5c70292cda4857fa0340f75ed9e455f4f1b2c5b6 GIT binary patch literal 22114 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 literal 0 HcmV?d00001 diff --git a/docs/images/http-kcp.png b/docs/images/http-kcp.png new file mode 100644 index 0000000000000000000000000000000000000000..ebd85242ec7d7063f6ece9da2899a31510bbe3e1 GIT binary patch literal 26026 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 JNjC1G{{%9e-|GMX literal 0 HcmV?d00001 diff --git a/docs/images/http-ssh-1.png b/docs/images/http-ssh-1.png new file mode 100644 index 0000000000000000000000000000000000000000..76ad55db0accf3726c2df36afb6c999f2cec9f4a GIT binary patch literal 29754 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 F{{U=7LeBsI literal 0 HcmV?d00001 diff --git a/docs/images/http-tls-3.png b/docs/images/http-tls-3.png new file mode 100644 index 0000000000000000000000000000000000000000..ee891aacbb0716daf0946c5f30718068dd7c59f8 GIT binary patch literal 30369 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
0V*S_6dtLHCvvgcIcu0dqM8t`Ok?fj%==la# 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 QjXtNGw31ZD-AAwf7hV|H+yDRo literal 0 HcmV?d00001 diff --git a/docs/images/tcp-2.png b/docs/images/tcp-2.png new file mode 100644 index 0000000000000000000000000000000000000000..2fac3abfc53745d309f5a2999724cfe7cf1fddbf GIT binary patch literal 17857 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 literal 0 HcmV?d00001 diff --git a/docs/images/tcp-3.png b/docs/images/tcp-3.png new file mode 100644 index 0000000000000000000000000000000000000000..89508a397fdf5611562a1c1fc5ab8e74635369e5 GIT binary patch literal 22383 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 literal 0 HcmV?d00001 diff --git a/docs/images/tcp-tls-2.png b/docs/images/tcp-tls-2.png new file mode 100644 index 0000000000000000000000000000000000000000..c4644fff12879100ce635c5372a86cf8393fd9e4 GIT binary patch literal 19670 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 K(vI literal 0 HcmV?d00001 diff --git a/docs/images/udp-1.png b/docs/images/udp-1.png new file mode 100644 index 0000000000000000000000000000000000000000..c236f0909cf13ac16ca84c3937649a5ba8e42b70 GIT binary patch literal 13117 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 Vz+JA7187RiN+~|bm3-p;e*i!z4psmF literal 0 HcmV?d00001 diff --git a/docs/images/udp-2.png b/docs/images/udp-2.png new file mode 100644 index 0000000000000000000000000000000000000000..d89eb6ec81c1dcf23c6a280e3efef50fd546b51f GIT binary patch literal 20625 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
M8yM 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ȶWhJ%?px5>Azm)H34dRI!^tta)(` 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 literal 0 HcmV?d00001 diff --git a/docs/images/udp-3.png b/docs/images/udp-3.png new file mode 100644 index 0000000000000000000000000000000000000000..a1f039e8935fe7cff6eb55072020b264078988ce GIT binary patch literal 23409 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 PCNa8m$9LO}NkZ9MVXHu* literal 0 HcmV?d00001 diff --git a/docs/images/udp-tls-3.png b/docs/images/udp-tls-3.png new file mode 100644 index 0000000000000000000000000000000000000000..4ceb456b94d06a0aaba423ad507b5f1bd5b47ebc GIT binary patch literal 26224 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 Q3K?pRUyyGtdvn&m06&;qZ2$lO literal 0 HcmV?d00001 diff --git a/sdk/windows-linux/release_linux.sh b/sdk/windows-linux/release_linux.sh index 80db6bc..50e9f2f 100755 --- a/sdk/windows-linux/release_linux.sh +++ b/sdk/windows-linux/release_linux.sh @@ -4,11 +4,19 @@ VER="v4.7" rm -rf sdk-linux-*.tar.gz rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a -#linux +#linux 32bit +CGO_ENABLED=1 GOARCH=386 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o proxy-sdk.a sdk.go +CGO_ENABLED=1 GOARCH=386 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.so sdk.go +cp ../README.md . +tar zcf sdk-linux-32bit-${VER}.tar.gz README.md proxy-sdk.so proxy-sdk.a proxy-sdk.h +rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a + +#linux 64bit CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-archive -ldflags "-s -w" -o proxy-sdk.a sdk.go CGO_ENABLED=1 GOARCH=amd64 GOOS=linux go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.so sdk.go cp ../README.md . -tar zcf sdk-linux-${VER}.tar.gz README.md proxy-sdk.so proxy-sdk.a proxy-sdk.h +tar zcf sdk-linux-64bit-${VER}.tar.gz README.md proxy-sdk.so proxy-sdk.a proxy-sdk.h rm -rf README.md proxy-sdk.so proxy-sdk.h proxy-sdk.a + echo "done." diff --git a/sdk/windows-linux/release_windows.sh b/sdk/windows-linux/release_windows.sh index dc8f27f..8603fff 100755 --- a/sdk/windows-linux/release_windows.sh +++ b/sdk/windows-linux/release_windows.sh @@ -6,14 +6,20 @@ sudo ln -s /usr/local/go1.10.1 /usr/local/go rm -rf sdk-windows-*.tar.gz rm -rf README.md proxy-sdk.h proxy-sdk.dll -#windows +#windows 64bit #apt-get install gcc-multilib #apt-get install gcc-mingw-w64 -#32bit CC=i686-w64-mingw32-gcc-win32 GOARCH=386 -#64bit CC=x86_64-w64-mingw32-gcc GOARCH=amd64 + +#windows 64bit +CC=x86_64-w64-mingw32-gcc GOARCH=amd64 CGO_ENABLED=1 GOOS=windows go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.dll sdk.go +cp ../README.md . +tar zcf sdk-windows-64bit-${VER}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll +rm -rf README.md proxy-sdk.h proxy-sdk.dll + +#windows 32bit CC=i686-w64-mingw32-gcc-win32 GOARCH=386 CGO_ENABLED=1 GOOS=windows go build -buildmode=c-shared -ldflags "-s -w" -o proxy-sdk.dll sdk.go cp ../README.md . -tar zcf sdk-windows-${VER}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll +tar zcf sdk-windows-32bit-${VER}.tar.gz README.md proxy-sdk.dll proxy-sdk.h ieshims.dll rm -rf README.md proxy-sdk.h proxy-sdk.dll sudo rm /usr/local/go diff --git a/services/tunnel_bridge.go b/services/tunnel_bridge.go index ba4d0a8..16967cc 100644 --- a/services/tunnel_bridge.go +++ b/services/tunnel_bridge.go @@ -1,13 +1,15 @@ package services import ( - "bufio" + "bytes" "fmt" "log" "net" "snail007/proxy/utils" "strconv" "time" + + "github.com/xtaci/smux" ) type ServerConn struct { @@ -81,10 +83,24 @@ func (s *TunnelBridge) Clean() { s.StopService() } func (s *TunnelBridge) callback(inConn net.Conn) { - //log.Printf("connection from %s ", inConn.RemoteAddr()) - - reader := bufio.NewReader(inConn) var err error + //log.Printf("connection from %s ", inConn.RemoteAddr()) + sess, err := smux.Server(inConn, nil) + if err != nil { + log.Printf("new mux server conn error,ERR:%s", err) + return + } + inConn, err = sess.AcceptStream() + if err != nil { + log.Printf("mux server conn accept error,ERR:%s", err) + return + } + + var buf = make([]byte, 1024) + n, _ := inConn.Read(buf) + reader := bytes.NewReader(buf[:n]) + //reader := bufio.NewReader(inConn) + var connType uint8 err = utils.ReadPacket(reader, &connType) if err != nil { diff --git a/services/tunnel_client.go b/services/tunnel_client.go index d7f9ed8..6c54859 100644 --- a/services/tunnel_client.go +++ b/services/tunnel_client.go @@ -8,6 +8,8 @@ import ( "net" "snail007/proxy/utils" "time" + + "github.com/xtaci/smux" ) type TunnelClient struct { @@ -133,6 +135,18 @@ func (s *TunnelClient) GetConn() (conn net.Conn, err error) { _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) if err == nil { conn = net.Conn(&_conn) + c, e := smux.Client(conn, nil) + if e != nil { + log.Printf("new mux client conn error,ERR:%s", e) + err = e + return + } + conn, e = c.OpenStream() + if e != nil { + log.Printf("mux client conn open stream error,ERR:%s", e) + err = e + return + } } return } diff --git a/services/tunnel_server.go b/services/tunnel_server.go index c82170f..09a6867 100644 --- a/services/tunnel_server.go +++ b/services/tunnel_server.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" "time" + + "github.com/xtaci/smux" ) type TunnelServer struct { @@ -280,6 +282,18 @@ func (s *TunnelServer) GetConn() (conn net.Conn, err error) { _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) if err == nil { conn = net.Conn(&_conn) + c, e := smux.Client(conn, nil) + if e != nil { + log.Printf("new mux client conn error,ERR:%s", e) + err = e + return + } + conn, e = c.OpenStream() + if e != nil { + log.Printf("mux client conn open stream error,ERR:%s", e) + err = e + return + } } return } From 6b2b75bc50f061bb515a15dd6bf1786f9f9e41f4 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 24 Apr 2018 18:18:49 +0800 Subject: [PATCH 75/76] Signed-off-by: arraykeys@gmail.com --- README_ZH.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_ZH.md b/README_ZH.md index b294c8e..21b7f82 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -220,7 +220,7 @@ proxy会fork子进程,然后监控子进程,如果子进程异常退出,5秒后 `./proxy http -g "23.23.23.23"` ### **1.HTTP代理** -#### **1.1.普通HTTP代理** +#### **1.1.普通一级HTTP代理** ![1.1](/docs/images/http-1.png) `./proxy http -t tcp -p "0.0.0.0:38080"` From 52a277138238bf949f3b8b22a8ed7093e72dcb39 Mon Sep 17 00:00:00 2001 From: "arraykeys@gmail.com" Date: Tue, 24 Apr 2018 18:25:04 +0800 Subject: [PATCH 76/76] Signed-off-by: arraykeys@gmail.com --- services/tunnel_bridge.go | 7 ++++++- services/tunnel_client.go | 7 ++++++- services/tunnel_server.go | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/services/tunnel_bridge.go b/services/tunnel_bridge.go index 16967cc..8125cf0 100644 --- a/services/tunnel_bridge.go +++ b/services/tunnel_bridge.go @@ -85,7 +85,12 @@ func (s *TunnelBridge) Clean() { func (s *TunnelBridge) callback(inConn net.Conn) { var err error //log.Printf("connection from %s ", inConn.RemoteAddr()) - sess, err := smux.Server(inConn, nil) + sess, err := smux.Server(inConn, &smux.Config{ + KeepAliveInterval: 10 * time.Second, + KeepAliveTimeout: time.Duration(*s.cfg.Timeout) * time.Second, + MaxFrameSize: 4096, + MaxReceiveBuffer: 4194304, + }) if err != nil { log.Printf("new mux server conn error,ERR:%s", err) return diff --git a/services/tunnel_client.go b/services/tunnel_client.go index 6c54859..fa3c4e4 100644 --- a/services/tunnel_client.go +++ b/services/tunnel_client.go @@ -135,7 +135,12 @@ func (s *TunnelClient) GetConn() (conn net.Conn, err error) { _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) if err == nil { conn = net.Conn(&_conn) - c, e := smux.Client(conn, nil) + c, e := smux.Client(conn, &smux.Config{ + KeepAliveInterval: 10 * time.Second, + KeepAliveTimeout: time.Duration(*s.cfg.Timeout) * time.Second, + MaxFrameSize: 4096, + MaxReceiveBuffer: 4194304, + }) if e != nil { log.Printf("new mux client conn error,ERR:%s", e) err = e diff --git a/services/tunnel_server.go b/services/tunnel_server.go index 09a6867..a977332 100644 --- a/services/tunnel_server.go +++ b/services/tunnel_server.go @@ -282,7 +282,12 @@ func (s *TunnelServer) GetConn() (conn net.Conn, err error) { _conn, err = utils.TlsConnectHost(*s.cfg.Parent, *s.cfg.Timeout, s.cfg.CertBytes, s.cfg.KeyBytes, nil) if err == nil { conn = net.Conn(&_conn) - c, e := smux.Client(conn, nil) + c, e := smux.Client(conn, &smux.Config{ + KeepAliveInterval: 10 * time.Second, + KeepAliveTimeout: time.Duration(*s.cfg.Timeout) * time.Second, + MaxFrameSize: 4096, + MaxReceiveBuffer: 4194304, + }) if e != nil { log.Printf("new mux client conn error,ERR:%s", e) err = e

5Ep$&kvwW>UimmWZWJlbr+xrU8hnO&-urlt(efVFCvdEk?*)IF!Zp-CqT26q|V=i!M#vNQM&H@j}* zWkMG1pP>CY&qb=RM{^I@itV|iq+~i9EetVf!0o%AA1ypG z>%R*44vN(wh@+ySqIX$YKLRM?fa*-InE6eDrEd1{JXf`d${X{>r775iDWo(^H~XV% zkC$_(X=$TQ(E=j$37@CL1~%`njjENI5x_RH?B*(wa4QKwrHv{XhSJx@$dw`zYP7#P zGBQ!OFLZGfs&Cc>AGA=ztdg&(3$})z3A{V$HQj+QjikY70Dp@vVCU9e+JNFql%74B$yxy0U-g!p0Gwfj)#Yb^nZ|%c2|a2CJ(}J!`{j1!NyY= zw!-}_PBsWZ-9~u)*uj?OW(1ak{1^y@xZJ<}WTT-cj?=tQJ#Tk?eAMQ-@NGKn|8h+7 z%4|?0??WYj=|2owett{;Kln$)M*uI@P;dmSC^2vp+91i@qC2x#y{xep!KYO>fZ9 zd0cl*(u;j{|81ee@{6_*arC>K7OYusgD{k_Qu18<6E8MD9I&=m*`Ce}ACFSmyX( zLk7U4>1cW0j19ESE}mUnr>*IiP+H>`=30&tXw*JT|4J3ZP89;LWj?~oDLLf7e+_%Nr*g<(mMmShn7+& z&Z|~flEZ7!U%|fTYS)ocQc||fe9|Kp_J6FR473U_w9uqzq6NDs1M=&QdPP&~>(V~g z35qo*VPps8^cf&C(A_`WcL@m!qGo0e5ux1T>rBD!T_I z#yzd$<8`9TG*ncdh=AxHfn_!Y5f;Rk$6vmD>CaZesu^GBY1e={dS4{ur=n4l?+s*b zgWdsDOITPKbSlIu2Yuruj2M6a{)PFMu5-bCf5g}Ye*J@j>;<~Pd-b9`?>EUfP5s%v z28G)y+v({kF^3U0sIjqwR%4Z6Q1wc{u!f6^8v_m-9HCRzwb2R$K{XqdpcUHAi6e^+ zpNIlrWekITo6x^6Mz;-4%SgVtC3=#Z{MDP{iCto4&k9eGgg%%do%j`EF~kr+)ZF3L%u9K}oazOi zamxn~nG4BY6oDM0*P4WPwa$&;sQ&mySS88i&4qk zQHEh>RDXd^G7xpFum3y(I}c1lMu4549JfyaD7$v`DryR>f6$9Ay#X}STjOK}VEzL% zQ2_z6z?Nq)QWNu7zbYS>5j@*fKz_}J^4FXb!qKnX+yp6ep;8K98MIQsDEDBYSiR0q zC09Zzg}Pw_O^5QyVKhO;*Z1$=7s4`wjMxhG2Sv>K3guXnn5R);yWCG#e|~I^@IBzk z`3Wy0KN$rZ4~!`t<$@w1WcQ%3zycu^t!*1BpKh5tjOzCp85xJ>TzJdJW=l6tEr_6& z!eYe$bppWgp)7-^Zb+KhKz6QH&EVDn49MbOa#)(E3YPKotr`sKEAB zh+D}7p5Joo(CARH`IjJiBV%G>sOWTL;ZG3Xy}%Z@N80%gC&RKMHW!`x1h6r&zWVlt zF$>U@(PH} zh?xxxi?EB|fEPkT$Gmmx))QFqt^Iu?;B+*r?fro|0kDcWMXX{A1-5M_0pRQhKt}VG zT`(wQY1i?=tT+u8sM&a{`u5U#uZweMXJ;@tj6gYn%l`t42xVn+I*_FpFe(Z{h=kvf z5mrUxdJ;o+|1ZFuL0CC@wz0PCx$L+#Liz4y4 zxw(Ps0obINtI9U{;FoM^n=RN8OS~>T>kp@~pn!s*B1(Upd(z!oml&Hu00Gs)A!JL7 zA(2s0m>-w2-?!*}H!CAlJ9;N7Esfc$&nomYRgzq4K9u%hg^KGrTHbk+>m9Qq!upkNXdmtrVLcRfB^3A6Xsxj8`> zt%^^o0Dj>V0m@7lQ{Ff*nGhoF1H52Qwu0EI>NP~58Tf~W_LQDSX;`B5YMiY(@>fi_ z=ICp!{VC(Pv3{_#lOmrcj`&jADBQ5$e4udkgZo6{2-3)T@ymzJ{r&E8L6W>BFqMJF zlW?WT(9hrB#y)IoZ?Ego{FjG{S@J$497ct&zF)$`CjJI|&9+u(EroYrbaHr;Q#GI9Z@}WX>1iUISwRG7DzGK7u;{t1Xl=11PrLwb5wscIy*k zf`f(Y^&gZ0|CuS+GY%J1z=v1AQZqek=%U@y(jr%7Ylwo0^Ci?q_PidGu6(fO!fSRG zdK92X-L1otg5@P2^#zX+C8bPP94G7u7Gi~EW@ZMCBoUZb>&>a}xN8TAqeX_90O{XW zx6O(GbqepO2TzuaVSW4Y<41vknPz{yCljGG4eXfl&yP=H6A~n6T7$_~YMeH@0i{^o zJ)2tGGP^#ra? zo=0_`A{0)Y-P}09V4>~&7Xxuw{=?*+KRvbBC6fgc5iT!XD!vy?lx;h3PYj)@O}WTQ zwkLwjTLK_J0kFVLGM;bH!w~`-8fY7s%Yg^j`hJCy)qe4pz*<{hU!TphEkNr0`IS$O zhohm%CF{M%hGQLGWOr2AUR7;AiN4A=~g_^JfQ>3sBfkfl!8i5S5YSnl0Kv z)C#=QaGe`B@QgPXdXgV7F?HGGjsPNp1}_c>5pi}Qj|1jJ2%Jv7Sx!PbTkvqQ+0Kfl zOU8cE=N3a^`}LpFdoXb#vlGB4fLc;Dj%&^*YZX=CJB0>M_SiK7yvPeK$y8K&H)&{F zEI>Y}J=-6JwD}En28uXBe)2eP-i73tYk!bD3pZ$-q&|&!o@!lofTae$g9Ruu%38<{ zAdUL~@Wb@m4lMmz4Bv2DTiX_oKK)kxb>4i1f};h(kOa^|KEp&XX4msLJA5c`7qQO%;S-;d5LSZSXv+ zoC)DaX74w>(+=!7H`55d>W{aangceo< zY7(Yzq%1+kYy&sWlTU2W5;WXk;|mN~x?G*B!@GCy79sp$)jPJ2c1Q!OT;N!N$#^v< zBASSaivF;@W9+QOzPmA*e3P6H;pg(T>pI}otk~XAyQeocdJqv}l(1zX2uy>jHZ zG8l{i-O=;ims!-Id6kiA`Zk1yy!%)wr47t!OJ#(&>8u{LroIP60sdNP$RS{T4WW&M ze2)VX9+@s;jqY{im)U_O%VNAb5>yKC{c)5X8je{P-F4t_Ljk+5qE=-~1Hg2k{apk@ zz*#Oq+sfA=^%u<`jMF_OK_4rL(DFu1^M%}AfX-bF|tXq{7(5JO1J6c z{`>2)7^0B_R(>(k_b(tl|F@ln`!8zYzx_iT8Fzu#H%qDr4G5UF0)83gtNROBFzlvR z0MG#15vp(|PC&`q+}q={p5tr{>xUv! zyR7UjU=*nM`1tzlplbAH%H2l+lLrL?fCRXG+d!!0b=d|}0UgZwNSSJ@N6yT^Si1zA z50nZAfpsvk`_~H_RXJ}_!4%T9vpwzJ{VDgk4N_GB+S9_Y192wzw5zQxJCcts zK-PdP{?5hhj94TxsE8?g?#t6`A6SiQ%Nq|$~ zz;0qaKbgEBh4Ro<>J{H*gvcwgOVrWhSnI&Dm- zF|$ZJ*3_vUc%2^FqyR$V&4)SF5T<{n#O>mZ-15#f=YT#4#~|2oQfTX)wjS#0>gf$$ z=SRjfUp`QiYHn&f=v?HxE* zQc)??v$K7Qakg52?qv>}ApGjpXB(J2sB%{zpc&;`gAq8~wQehzKJ>8tCUB073KK+TAsPVI2{XHaD#s zo0>*SO%2SeLL!MKXtZ>~-QMa_6OO#ae94UmmOW>~}A3*+Ft z>LA=SM5s3PTuHW<4PE!anlM-V4IG?M#vwz8+J6D=RaabDa7UlCw?^@$u<(B#<@&ta zTJry76n0&b;^I00Gc3YM2v1MXpgY{moeMoH3+DfwQYa2)Gj`Qt2g}M9;0mDRiJ>Dw z?*bYMaaV%^(E|+)(ps;+f&XPfB9LN(#fB0vG4#N#$8fzS&CkzAx*`xXch3TmZUYW| zl=WN(0i-b6H^>;In*$hbGAg-EK@rlT_XX&Xf2@qeUb5#g9r9adKIACS-40F*O*BjVRI!8Kl@KF z05K~HRv8gaVT9K4I^#r?sJ|a5jo>Xp&R#=9guO7-NCuwh7AdJX09DmW>o58)j^lQ= z%jGbi&e0ZVy|W!5QtZ ztSmJ7O$AxrbU#$!8Sq2E+}c}sffXHEZ+tK_JlRWRLDl zrkv)p1fEp0rGp<{1v#|<)4xA(r*Nq9VcixPU|+!9pntck@ERWq{I8jZ0zXgtnvAQgm2EB3q% z=qBJ;fr@|()&R+2;-ZHG0kwzIffl3XH&7}nDhM}JY}g4QAqWN`#T?~e*hgMLO?`d+ zYdAOyh`?Mf2)PI)#GB%=>n7ycyz0T&tfK`@OIO#8oB?sr!U!0Ye;%}B;!hU+jODn} z#LBMaaROHW81TFImGF-rzx@ciUuFIG(#MHER}9Qr%2XL+)hnIzU`#?TYp;eMcBu*0y{R^3b715T4C#|vhjF=sAGKqmvG z77Oks810Y&>cXx+z-w5#hDjb-ZoJ^$Z?mnuY>x{XWoJeeYskFX@et{Ba^!0m>z(fF6K9!BOzxoQe%YXG! zQ^F+W>HH}%$t%oXw@+$ySafbK@GkK7xOD%F-AUYGgn8rz(2(6pj{|`tAO_{(Aq&fg zoE!%5@nC(R65eC3s_AJxAGri@e`u6VwmGbi zk(dwWJ_R%(@c409Y;2Yzui|4u2aAbX665ZKrzR$u&ghD2YW_cdh(e-Z0-b_D7?_|R zI5I{i*BsyfO+ZAH0=fUXxQ;$)_vn*(;7(LhQV1N`#sFQe4U47rvm;4F;(dqS3vm}y zbMvOg#!Fx^LG;Tv`zM}U930-|<-kx?ez6sNGheV!RaKRlmGw3)EvZD;Xy>d6}Lf1&(nB1O)V-uT#4YW*u-~ zWp#CQTnD(}>*Ip~WZPR%0-AbzaiKtOZf?H0d-Ydu@4LJ_Ce$N7#nj;78*o-Req2Xi z%7#=UKfAb?9Z56Pn|N-kuV5a!zO=1#4s`h?K#;Suv){(X$ew7~p~F7dj0Xi_M?^y#f9xKn72}23EfnR)5XKJh-(xEPKhTtacVu`Oi5ySf4!_lEEVK4&dEiU?)^~ zS)_D&<`xz~7Ah<(G?5i_NT%l88)0W>e+J+eIcE$UBOau)cX0T-wiXQ3Il`XN z&msGx?G9(v=ml-imicg@P!T}0%i^)OwzSMYJcdlSdbH8oE02&7QtLvDjMxMO1eG2q zT(DN8uV-Ex7+mJ(=eHOxM5Cdh0X`WG%HlN`C{YfHZa{GaLv_~!;&r&vCWVJ3u%v_o zz@Hz#(>jtCU4#DYKvKPvq-_FGonYS0)S`;7Eqz^ZKnuhvoTa6uX%?GpBhqD)wW5At z%zYq#Uc7usR~md}neQYnCgv`ahf@eRqp}nOz^5?KzTu?qSfSTroW)`=WurM>@4YgMf&^uzv1f=H! z%?_WC5WbIrTv=K9>dl*kB70E8o`UjUGAAi6-UuwLHJpMK9x5s>eit5&XTVO9keC?B z#T~x0v-2DPC9QOVACa~*8XPkA1vL1kI{vDHf)_yEE5O{};9~&Q?HV*oYkT|mZ{MP=uCC(qogN<_Qwa%Cc6WEPva`P{ zC@46juv*)f>FeXMVjAe@HDlIF+&L4NP0Psm0EE^3yLVBTn3%-emSpE=8(!!pXD4#u z8w-$6`XJf*1qHp&$jCTkI0Jp_&bTn%&`w)V4sby*K7bJ#^Y!c3qSUx)Z{xCuhUS5; zBnR2ns|b+N>Ua$iylsy`-47RM=P7W!#&BCvKwWA%wsmkIi2vR`+3V@7w+Y! zzwGawUx$T-e7$z%$`wT=CD57BV1_9yD{FyC2V}D=5ZhagM#jb&_QOBms~)Hr7_bHg z1`zit)F@DBP%&|FU(?euK`A5}sz#p03A%_*pCNZ1Aa`IG_jvh z5m>=xv-+nj=XZJA>m^f)(eQl%a;)2rNqTnnYB~S7)s<#c)0*8(juD{w^XCt;0c^me z0h@x(SZy z;z&qHz+|D__wpXqSLLby`;~=sM4+(TzHLh8Cid?iVXbVe_x_nJg7sfL`^V9cwtaj5 zkF|Le^7h*Qcs4rTzvuMwVAB43PO1&IL&Jf4kLZ0;h#T%J?133l0Jz;2;4_L6n z905)BDdJA$)HzF#kJQEwL2l$X*xVpLqmV?8V z>T5&D%{b;KZk$l4VE)!RJpqZ}x)=HrH8m1Q&cRn2utGrG=t4VUWn(k_m;`SQ#5|8>X8#H^~nj?;|Tc=bSP8%-7dfR9qbQ!qM7V_E4i4SAGrc$4KncsuqNR0LUf+l`FTC4bBJpJl*_kp zw?3bN(!qqvHC*peQ~C4TH&kR~q%{;WD0MbKOG4ee0)Mr(wZ%<7wG|V&Mo^T&CZ))SiCp>ditelJ2x4O5DKH2Jrw`%ifwR5YND1{hFV8iw1{&n| zKYvJJ)_o=~j|-CFz3_HfRn@DX^Zkc_i6JNlR`!XhDc#SmF8IsGt57P`q*H8d(rPC)YbQdoEk*dvhagg@LQmsL{oDJ^{j`XVvZ zP?%+&i-}!>TaZ;$d=IoaDig~OXn9>Tvw-DgGob50=0#hCNu}CpBY(3wqrRa2`1%tt zM;)0T;o+emds_hz51Yio;R^4?MU(%?A1_^ON%beR;hfHwrl+QUP5Jws()mf!nsZra zWoDvehZ-(F@veUWLb^qT&WK(71u@1x^T3ck?PB!hzp z@J$}rC=gaZ)YL$D48LX5Z+-PMh7I)wvBB;CF$aiT+~|qSegM|L!SK`C+KNH}Uxs>u z;E!rSFm!EIM!X#t@q=7jp6sv5AziqHK=^3uYm!rq6%A&~5`2#FCttx&{Pf}Hf$Na;FrU}ZIZ3qax9;21?{Oj>mw~heu=VTf3>bkRxzLkiGU>RviMTDtp2*66Y_vs3 zM@M}FP0ePy`C9+rpdM6M9?w%A>>D=*NDVuin$Tf$4#Ap$d^DqWt?JzCIlC`E4A{Ys zhzEUG1b7CJ5vMjba5OYDkQok4h;O5#iD1kn?UJAho2r=fYywp>6!H&j6Iei0JHz+y zyn>93iz9_ntM$clS%?ko4IF#H25c(Tt;-Gu7au`7WHon(QfM;y>?r*y1l_Mbf8HeA zhw&7dg<$6wZiUH2WoF035GM$EQNVXgFflKHH6R39^nE(IYfv^+h?f}PRw1rfIXKW! zz>d>75l&X=RVv2qT8zHrl!7F zvVD3B;V0;^GjhB4d$MzL8G#m)?Q3ziY+tD;s`|g$JJYBf_jiv!Xp;&>ks+n*Qi<3p zLunFrN~Ot^2E&#qA#;>8wWa?oPCKHh%qL(pY=vB-00x%=#sCd4t-nu6crX` z-?}9j#a;tlA2waFdEcpBw|P4dVVP7ceM?Zn&n&B%wDw2+WBcke{x8O^MgMd-y0X`f zn3$8xcHX(U+dMCD^qrxmotX3{vz`bsZ6oZQu&7Qx<0H)($+j<{hQa)$kQ0PU(fsw- zc>6TJ@GoDMamuuKOaWV!72&ITz0xx?Q)3Ff|L~!;g~j^((kq{D_QqwK%?A=@|AEue z?BdV88Wsu9=f$Vz2wkT<3n*@R`S*qQJ{B{jU<0dUz!zL1?S2E^SjLeHBR~FOIW6#J zQL<+jNz=6uZGYIUAsFeTIXOATx_gf0`hz5M z*}~yEqosf2SEF0^-Tod-!}zsFET_(x(KkOopO)7Dbxn;oB|AN+H8V#bAao}i$Ph1Y z?=~w|te^{C$ZNP0oory1v~>1@1=~<^yY%eY)AnJs+DFdjeWV!GUcE>NZ;?AxcI@X3 zg>eaviRry<+cqyBpSD7cXSkG1@#5Lff| zoQCYe+MeHPjvv}iu!OE{>F$2PKHp}Hf?dKdoKHhS7;rDY`({v&Y`==%`@QM&=Ez9* z{eyyJelW)PV4J~`J)rxb>6@Vwq+bZCbZ%&9pifxa*!+TwCRNfJ3ux&JM=>@aV3fEb-k+w5xhfTGOkrrr~|J#31g_d0F8|zL@0p`1Ak$6u@sj9XTS~AQbS};JS*dG zf27|FA46}0do)2Bjm)@lM7pL&vJ` z`mS3y8bx2Y1;MRpP44eN0(A9j1qDu33(0Flg61qWcN{)-N&$+^N%#G#yttM%wf+pq zVud0jcpEPM0uG$*gb7pzzl8H;sf+*Jl}{zlgw-oSxzJR;UQ@U~P1iOlGF)$fqr)B? z2L9-$t=-j*KJZMt=-Glw^(~sJ+RrtuYAFhgP^r**RRV6 z^8%43DMn@eKi8~WY2o0|h4)0@8UDQAw(#(Rq-iFim*J%OO(Kj1lR-PsJ}K-Q6r41& zz*QU_1~Wn#TLA9`FYW5f%gZTq*Ql@lkfv^GK}&NR`m=sZR_qcJVvD@IJh-Him3C`l zb|)D`eq3L*|}U?_2J*el;H+*ldJ>NU%Dnz$bP!T&{NVq?pK9W@C=kA2dJm zV{u}lo+RM$gn+rtBg>xF+hqm?2L9;&XhL9hb@lLoH@lj?R8BeF$x3Kzb+Z~*zw>--?KF=@g*BTGfcDN{POYZqdickl0^#Xc=LsWss# z*Se~zO0I^>AaQJ*a2C4j*5Isx$6$zc&vj9!8j-yUy?xsbUu;i_Aquc57YuE}3fL;l zkXl-CV{d7-N+)@Gs!uxM5+3K;`s5{dT4n6A#EOW^@k^F2b#iuoICpz-_u0%spyyG8LQ+x;>EJM! z&hKkE8!rU50x&)L5X6ksiQ6V_IrbpE<7UaLB-y}0|LWU4K^t35zPztDn?Glc;85_} zTUV`>-0?gfb1C+3rr7+yWr|x?|5r@$#QyUpDA8!S9>`Enol7yGo?rz-|O z=hwoJUb4#g-XjM_UCg%daXzA5-M)SMde(14hdP4tp{@MC^x_!9>2NAV;Cgjpr|&J&cry=>DFP#ngBmH)Gejxf6Mz#Kgo;D8yTC_3zpd=Z)Oq5As>y zaygS4yo1Vzw9hSH<7&EPY2jYbisq0ZO0t8- z*XDhr*~#SONP$uqRrOxAo7?j9(JRyW-9B&~50j#@a;RVC*Y*q+i-!J_+(mE}U{v}; zZN!o>&xZ%K{#X&dPNyrx$a!&;nKBmcba!*>-poAwawMaoI6qkClvGrHO?$rX03;nO zS_;j7*REYu#ty1wWP!-^4RlbabZh zhS2n=V1uhm3zq~$*p~$+o{c?oW(Z>~o{Uh&1^BJ!K$W?vXV1I#weT3{#kdBZJ$&w* zGSq^w9ud#!fBMC{ciS*f965Yga>1{H!D}F)jS}rrbNf37A6~v}%JUYMbvi$$+|;+Y zxVQ-kl%uqTe-Kyj-Ju6AC3X8dNT7cE4I8Ec4=)#Jw!Yypv&y;_nOw@Si-O zQxKJ%9a?CNCqet0oB*#?P7Z>ldH19Veiiy5n0=RA;%8M=k6vd0HSZS`T&3^{ILu&2 z#g-&o+5)|D1C`Kl)?bR3Tbkecyb7RLs`l*Z4KOiS6jw%#wTm(CM4nPk>$_3f^s<@G ze;i6Y{fbG0YnguWqKL4_Wwo47qd~>B$d1RZa~(9gn2i8{4Ii{@xus(z>*4VS8W=q1 zLI?eL>j_3vsaQkh^qu$NK>nWC%%pO%$<76HzR)|M5i zzV-KB)a~jZ5&SurAUG~AZe>!G!<;9v6{+yJH{sX?_u=5+pwM%u458ZvwHYTfGJHv}s zW-R=CaD1U=+a3GmaiSlIC1o@zB&b;Ji(7Y}E^`qiJ=fp2w%fU3olVP2&93nT875=lAX1>qT)R&*E{i zcFLvY27;+(WH#2Da*N;KMUR3ES(I9EbMxke_n$^4J&j7}Q8dhV=js=2y8bYSc~&)B zz3gP|*b01js4$91MK(z`Kzq`wueZ!VxUuo z(>$*~be~<#;_W&Pv4s}CEkw!Z#pbVj*)3PhM4s~Px>c?m=p{kL5#JFo?@V*IHIG(& zYgowmsmdfEAmV2%lv*Yx?n$OEsquo1c5!!am(Y7g6V8~0Ddm{=`ep7O-@kG`*G*zU zyTB${_@&K>@rp9^knvUZ=FP9QZpWHk$_t_!I6cvZPDjz-k)DOW%2H9L&*-)3H?B>~ zcXe~4v0EiwoXhq8e0S8xbE_-zMHgVC?cmsB7sONgoyIdbROeL8uQ$tU_S-hHYW{+z zsUwiPqic2b^d^m59jW&}8wMXApIF8fNDT|LOR&K~zauvG^s8*vrB7fr%3|W1GV89$C1Ln4iLyp5k=16FUhc&-gKs01~s*(xpo|TY9%+ z4>D~={L-bctnii&D;mmHd?roZCZTtRQ|42&&Lh+RL9(vT=vdCUq!DfFFsmyRB7 z%Y|#l+nWkGR?)z!P;sEsf(&39%08n7=+_ zioZEaD`x%ihrWyo3l3n4I@H$H#i(ssM{gF|EZnQO;nID(s)Qcm&3u%Qlyn1JCod5{ zO{lb;MD805%5WZ^h`wfWj9}yACyCL(ZUwTEop!gk&mi0@KfkK3-kOZgapN&GO=ZKd zVZ%rOUK8W;Y>-C6sZ*h0Ve;a_(Fo$$6v4lSL`6ldw0`ySW&c=cI;iIFc}fyEd^OB} zPQ#Bin}j0atbBEF#bG)<5R;i+fjhmCJJUgZtptfwVliiqHcvGSsDkwHn6hRgLqiJ` z{?ndW5!6L#accAySn@=V7g$0`O&PVIcU1` zq2s~9ZL!_!4~#+^8Z=032|0`;x?Q`=)+I}pJbV7U-{@a=-Rs(^@E5(L=skNjG4u}E z-iPEGF+PD*vO+rFQSI1 zbnm{^q(|g$Mn>0x?n>(FUD%A&z6O#y7`d5=bQ7dP9Mi)6M~|fFYY0CThdb^X$|H** ziHUN!lBKDssgsK!TaD7&+|J7ra1|T&W{`!{2w6%tcs(YdBYqW`a=sjt z%1(~~uKr|prOT*0eTFYSTu_TQO~4J{`}Vw9D8ZXBRq!#KrcDb2Kp-z1<@aC!m=yB9 zYJ@MRk&$a372qOhs@4bmIx;MQ)5=E8q#dcD`jM4%(HJePi_+GUhHE*90|r^0&=31-k2GS13Aao z;ZjRXvuObK$g5GiaZCs#6cG_oc|EB9pxK)4^uh3!#g3l9Imp-Tv9W_PJ9p`l%ZFu% z4&$BXum_pN4w``e&!8hsW8?0Szo8c|j^H*9fk+e*5sX{cI!;_Rp{oLTCI5Zr&ktic zP!~y3d5ixYk8$+4K(D`t@$~#3!+49C-)koT{7f?L5)Ts5(0CJNk=heDXx(_6A5p^rwcX=~oq$tW=`2JKMvNS6 z9Gowj0lG%J`+PQW^spjLTK&EDm=8%9#Fnfl9MA01(o0u0+SaxViX7Z-z3?rNb4*|U zz8ByK;J5)I-3uM*b?Jq{qM#giP#36d-lJDgl_DqM9|XgdjzO5X!U@L+@=fw;BNKuy zvS&b%`-#VQBYKG-rXU6(jjjQiouF*Y&CS6RSeZN7*w{E+Zc#se_m%GG(S1RR>BK5* zZEd&j+t(4ZhBScv7(94Cv3>6~uCN^5__{?Y3u>uT7cw}^(k5t#C+{YV8`lA=%T4x| zZR1eAp!e9|{nyFji;IJKUEb+51e>l5FmP%6?na@78PB zdaq|y{TL_74wOQ}wu>%*J+>7Cl9 zHp*@7*;bn`XN35g2dS5w*|)Ff(BFQ$VUa{( z5L;{@{!T9sELwqckQE$V0c6+3!g>!64;qAIt3xHe+Jtj=s|cN~=Q)L~5lkNnYp#Cs z<+g`E{O&MzYII389PFW=_?`i8{2<6) zJ-($YD=R-^j^+k)?*@Q^A0NKxzLanH7@XNf&@ZCCc>Eg+BDP2gBPUl$>_@8nYhHb0 z5(Ry6=V_Oy${C_6kNRBrYX9-&_S#V$jg5`17`s#7k`I%o(5X-%Vx}jjrMZEZl@PBU zd_e;^l9*_#C}Rl`+Jo%42Ai)s25ZQIZuxHx9YZ;djO3}NlB-uOk6dOu#@v)hu*P#H z{v3!rDc;hKct=&(v=WMvXgfepZ(d5KsSv{Ckf2@1$BbBdOypgqv&md^<5R_dsbKQF zUN3UNM|d-;;LMzc4xRfb(gCB39DdA9Zk;;ABy#YfkpYQ0t^7(w^r&A2zgRg02kGxr z9V=Vgtzui>nHHowDGf#UT^28ec9r<)({pS8eHrz-vcbr-1H?Q)@9WOUEXB_4$vmrq zL$7z?rbG^#aC6JSL3AAr7N+o8z)$v|+3Toe6KsysthWZ(txkty*sgAGQ{!bWP;a&Q1gsd z8i?)A-lSSPRxIKM@u)zs5VEWUdiR>{!NaPF5qWo$9W`Xjz1AasnT$RxCU zjtok0DgXX`?XKwPeujpM1kC1cUx(wFB)ujy8ts%3HV57FNK%sUgK2V_1lDkJQnItR zr_dcU%-Mkz2Yw+$uwj^j#@g8_Ff?x>o;=mtoQ_-hFe>*^(2~zA)z|gNMR+eLD7e$t zZ>HA3N1mro={`1Z`P@Y``;3fU=pT$yMraI%Ynv9MCsyJCP)}N?dm4Q8W_aKk0B#%Fz zh&+J(jXEK+gd%yAzNT>yMShF;+89Iz@l1cIYlP3#4SX}%&Fz@s*~!?Od!LE|Gy&+O zEjEbv?}BFe_yqs$^mFbr@Hsh{l`q0gN@lH@{Sz=1q(%zV3ugQ)KR<_Fwd>fpzgY`V zu)A;Q0rWF})hV-KAhgOaZwd{~#`}nYx$@2|=d?v-Tis`@vhi*^_N(C;{!%!r#2ED9 zg8+y^tKx2Tz&AkzMCS8YLo9Ms2v=EK>>#=gJqS^c1!D3vOU2Zb3q4vkx3rPEJ||N6 zJDDiPJ2>Qu4Qcq&4~z_u17}Nw8;z!jlg{eht<)x(`U1@QBhreaQXMVai4a+0jsi$R!P?rI@ypuXy$6shyz|VYfrru(f@be;HvQRmaP6m%uYtqe za%+OE?e)vAZtrWyR3*GIG)uWVGNAvH%NA3bf3&V_9UzgFmuK_1Jj6pgVGw+;UPLP+ z5YNYIOJ2YNA&2=O1SWpVTkww9$&^AjT(xQh20@mT-`6hHfA};2hf!>xg5CIqd=W!7 z0r}pY>|UCjuOH|3ps2`gZ)sd6iosP3ExuEEL18OKy6z)fZWs?zh_RevtKOQ1cS<_d zQH|D;H`;TiWv(4TJO>hfkJ?jS#(|chD{4%liL|m4Z>RjV`D{!0i_5AN?%`} zlNV;)n30}tl-`q^{JOZnrBrYs>4`ccrolb8W({R0h!?UOvd*L?o8|}c6XdcIz$qs^ z4uO-d@qnLU=JWrOMzPPie_t_J;~9|0(8R>g>7c=svoZ*66!+m6^22egYam02M}tg* zK-OZp5If}Ord8NZE6f^d9~es}ORW6l)(m)A_4@S_4L|&v!p7lU4DRTykgOk-C#Gl@ zm;SLK%`idNM~|aV`FwH>ayc!AcxFbdjT__Qv=kH+yfD!jemw#A#f5_VQsA;7lx_`p zTJk>H_svM)&ijzv8VSSuNeWt>G4!A{Vs%T-tDc@nlaS(A&uo69A8&FSE?!uu*`X3j zr}4&-B-C0^Dj=CN_|tbpH2^}22CdYqmmJa>Ptr9kD<~3-WZ6)xH?%FO$wL2PQB+%r zs_}xoPexohd$z0aD=l8E81y^~D7udHge02;#P+>=Hxua81Sdl03d1N6HQzdKdI#2ws8kzCHZA8!Naj-^v_(ft|IKzFR#i~I5d=c0n_+Lu zf!IOcN-LDp=(T!Wr=JB7^xj0)pJ7js&n)s2t{mW*4ZPqGw!4TJp12^~0YkrX)4ee< zFW{plUs|5DvfFno(njh1T+WYnz=sAW^qg>sU_NiW&)wEj>uyRpPcv;Mhhhz#;M}Yq zH1p-v6l4zo#q(#+)>BUT!meyIK76v`iNIs*=A_Q|@h z?^5rD(z;G}vUNG0o<8}V#;FK=y~Zn(7EB3bjursJ5#2=NJ@`!gp`>biX^MBqgDQAC zqH5hKwJyEZKl7)n<$%AXf>}kI-c0KjgEi8kJSn&18nOXuQ53jP47mMtbjH1#{3HEX zL-M>pz`q=Ot(^96a?x|0dcgoAvaThdjS6XHX}Q6w1X4@-^y&1^A9Y+?!<`!IL_%2m zAyk7DzgRzg{Rvg3(E;oH2A4F8M|uBM9qmFN9&ibW_G}n0%Ql_=5<|jE}v;>DAFHc zIf6o==#`aj>7Y=1`A{et%%KDDlY`;x-{C)pot2E-P$=3H$iFnG_{7sF)LE4BEjc}} z#9srxrh1ss-Gwz~!9C|krAt4RT+Jj7h^PnE7^t4>wGdS|d0A2-8dz#+l295SjVFny zs`C%1aq|n;jAs&E%jb^vzr;ixNH zCwj}ZWI^pwdBUD3+@pvz^?C-v%QTrm$7Y45%j#Xr9-QRIdvxP>($PXYe;az7)AUwn z>HK6UTuArBf+hq$g}SJSmk;YnNH5@h!bmu};!pR*^QhkM+fSm&x3zFIO&eJuzuG$* zt!Qfpq9pU4rKClV&{UNk<~c_5Ay-YA!7nn8q&er$>j+&dc7p0qqVLtQqf6=_?Ui+~dbESDC_!97@q2(qn3ukjo?}oa0l^eH)Mf&y8pbSPYXI9EC zaUZv>kMz*Z({xGHMjztKaVx4YJ%qYsVP%^Z&^BRzMDVCnN=8c{=3EYJbD?oloTKS2 z85vbAoZt&0*+NEb)7(=nHuNe?^L#=JCWM zC~@RRAM#GG&_0yfcM`q~6(g@jgF=~oIeZc|N7zVpogbEac}860*s)_(3t!KyeB+Fm zo=OSyDDb?-8ZYIE-&~$aAFTDY+u7c@`aCIJ#%=P&1&zdq#-}*Z)tMbdc1l{JUnsht=)y&zG z>n$VkUS24aS4M71sQv&usvp-S_ocmE^Es1H_iIi?_pRA-ez-|PG$uOPhs>+2xDEig&mvFCYE-^|{c7v@(ajT#J?%pNVM0sMoL4y0SGXFU1F64LBO!yO(CS^?3L; zewLOj9;K;~MHiZWfP)u>%FH8A-P13h>Qw)8cjuLNslNj;(?8A)U`Xh&r%$!~{AzaA z>UWi~M`&remBsp;G$UIr-IC-M;0$ZLm)w`9wO4)*nKQ_&r&hVooDU2P+*n_)C^LL7 zEO^x(d+*z&vO_48Q(iN26`pLAb0evbem?8P3%b@s30>W64gK96Dz~!RR2y-rUA7Ru z=Ek{m=dc+iB_hN^b8~D|e0=VYA6g{YKK{ZQ9jBUwki53Fp+NI{_wHfGdk1-VjnN<6b_>6 zR{eNvz-^lB0KKl==cJi4zn8n^51@vx|8sfB{JGOvM&&~H`sxGgE!z@tK?fQ5E?!g; z?{^MD()ogVypn79ua)hkcDt?BS=%3}kJ!D2X$Jh)2Jo{}?J0$xgWif*WLu~!g@Hti zSoEav?_vp~*m^0Gv@Kcfdw1Vyte#2hl0Kk<{RhuXgCff2Dkh6Bv3>WDy({O>As<)k zJxG&Uf$X=%)?|t$xujNuBYgP$gH^lX*$bwOWIwISA1`K^Gx3}`rq^J}Inwd|SlqbZ zQAKA!WW5N0Gm!CwS zw9at^UUKN=niy9SAK;>A|7B{Xb=KkMS?bWPG7hJs^px?~Imkvmk+>uq6_YZh6uT}q3;CyfPsAi&!K!Fjb^kxnr>m}r{6U6`@ z6|k+%BCcWEqq8}~W7BlJY2?-Y`-v8?bG_AuRl5OGtIEpQ`ueDtRTF6`k845SKR;`G zrPD3*==mXu{ST$wse4eG>zgVe=Q1;tXlUAo&nm~oby}q;H-xIjn(=L{D>?K@?rr8t zxz_TxmFn(Jx%-g6PPIqdRRI%~`Zl3+$|`D6kPklN$8h?2MQevqH`s0LF#uky|!i>Afa1XX6{HcV4fTQUEa3qjKDy`?%NJa>bV&DK7_t4PQm zmD`WHPOPn7-d@rPJ=%sxV;Nb<+x}#|rlM?3si2U1O7sCq>-@a9<*LRPE@opz4-_}H z_MuXbp3Ol$<@=K*d z`fZ(T4eplh?cyah)z|MXPxUnK*@|vB;||PT^R7mBjk~s`WnBg{I2C`O_*Z+NWrRR9Av*(88PoXRVvsL zD^;j$L(E7ffyLVdIZAtSb zk54~jd?@uj9ZET$fRK*BGw#V=f9PUy!%I5P~VR|C^()4I%mZcW=C`g55ssF*KDOeSQh zk#u={iBfb^IXDY7jp^kMl=U?}=#q^O?RfhZbhWD89Mg8&tvd zPaZTS%x%j1sC2kx>oHSyv!R}tD+N@7n`W7eCVx$;L&F9_p;C=8zkX^{H&=91Wc*Jj zpJuPT`orU+yov=gHl$>pJI9L#QU#@FG}V?wEO#1GQC~k~%6@lN*n#bFKJ!~a4}^PZ z*{qO>UBG0BZxv{r#P^rp!EZg16i82Q?Uf_nSGc|pg^HdW8IH@B)D+6qElx=W>H0jvmF$_hl`Ow;N~X{43g@41q5WV8d<=)5)sB~R}s`27P>;as-$ozYL`p?qx^C8OYadd+P& zynLvenu9v;kL*)PV-xok|FBg3o`#MK0U>z0k{PXVd0sVl?L5)>D<$cwsgH%UuJN|L z8h>ytFu>%z=p~Q7CPl2s#*sj2kBgda5xqiQeOkv!NO~B5A-$SJqc!Vq9iS{xf|gZp zy|Xme*R1=_YGj9I{+pnrgv-yl0NUqf(q~|8D6PEwDlsij@!i?Kz7AcG_Nc7y-S^X- zw8JV?Mi)KdbVocx37hhz&+|P(xluWyw@pV4&u1`tTDh64SA4GzF?mA1yOQkd(KSk6 zqXeZ2b)#U|N=dgbn(bDy1I1}Z@RsxMP}Wdawn0aQnWGjb^lBdA&CQ~mRLm{!tQQuY zDqfV8Lfx3yuFOp7-4{h=v*R?E=QQ3V6CZ4d68Q8&343%qe2Z7g-}zL@e9j6vRWbZ6 z%8l_|gGAOTwyB#wPG@txv{mYGvVN~8W-Z}%`1|!$Vhh~{x;{mu>d-o9@)A~~?3LTJ zKNuf8o$DmbIL*1gmu{mk@qud(7xUM`@<6Kpj)VNsF1?S14Sz8)o(ZoQCXGC8H(9gW z$Ttt=(GU`ebNkZrM^`M8w@#A>z7;pbu+#f9jz74VkpA76Km7-x+^8l1!-i}>s)aJ} zO>ts*({({OLw29r)4c?b?UU(kHs8E&q24hz1f`h=ntvSbNRtV@cEh33d|`jL^pnGn z^Cx!<0tOxTpi&>>59R+n*J6>J96)o|X#1r3-QAj8P<9XR!?7&iI{o0+-Wv{GpX%?Y z-w<6{+R4#I<%VcnX8Ng~CgZcNBP;Ot^0C6Da^)7u1DES$P=uQ+{Ok2nua44}#?-4< zl_aYdkeQFHUSYJ3+~ZHT!QAHb6P@l$^{F3sXb?UB2DhZR?mFlpOpZ-V512^MSuIQt z$UON*(&TP)Thu`_d62qp_#?p3gg741IXYFI|B+Q;x0eu&_d;p$Tz>5}>t^U?ETU}z8Mk*}&%JXQ<^!8>AH-YpjWwzXR0$BSJ*Q13qEs{SNU$MOu*8`0`j$p{1cZe{kkwd(gQO zZR+Wjyzt7hfL3&RN5MH#5DL{i)pTx{>EthuV%g`8lBLSYUxfIY-}0v!{-sBL6SMN- zNJOHJ5&F#h>K;^7dt^i=n-y&|Wn@~_idM}(sYy${wCUreP%Qt?+QJL|Nnf^_01ovV zPh(j&R74jPbbT%NA3%A1h}3)zJDSkW=;dB8rD%1;VnE)kB694N&sKjy_X|+ ze=nhPX#V1710LPBVjkRn9osq_WLc;iYy7S-XSvT_xy++SRz)*vXm^FSkNoq!T|phB z@F`zLJ%jB}{T8JiY4)M)8*}9}f%YMc*8K5`3X9?|u$)0vNs5`%W#>ZGPHszTsyu3t zFjRSD6Z1xPqP(wOAQS_4ni^kmxAhev|8eo5y(sbp&7kJ5!uxwuW`6vBth#&NJ}^r@ zp^Z^bw`ExUXqUGKXP%Z$9amc?31)6uL$0}?On!R6-1RIFVaG_8WxNn$4uo>1J@0Y4QCK5U;f+~BxV>;>{KHrb7 zC%+k(2^PJ2*dSScle=9n&nMB41{KxXcvHlO%BPQ?LNxRb7?lEhX zAvFB{0oa zT7Map>b}GJ5-bqBITOeN-IT0TMg zOiuzh9;d9w+-N>P9X}Q0q#_ZV^(*UdPbH5oz2pW7DJSFi%(~A5+Qt($W%>x7Jx8f; z?2>4Y(5lZ+yY~C+xBDe%6n{Ib?Jkae?r<>4qRPC+9rPJ_A@++-aqMt z3=K+9=G>pf1=61q8&j;lKyvmhpe^{|9)7({s`mcCM+5Yk9AD#kkNppyYbT%*5|={u zesEe3X8{$jUmgPGXOETx{>bQjRn@#2@WMhe>1pcY=ZfZo?ZlAO`}evloA;p}(oW@` zO~BJnzS_qWS{>h|Ut2YA{_zv*1-Dly6gi}$;LX|7r9&qliLE%?0@n#49mAM(;|*M# zYy)GuM*`!dkmbD`nSXvx??8ZsEteiye zsullqh%W7S7mzKqSBQ;$-@Zh-Ec8vLrLZCD-Zi1r%K=oE0WrO~#jq$$egDP!xwn0{ zS7wX*tJ;kmg)?p>Fk1hqebh~%{(ep6686=rRHwR) z+YO-%YhGu%L`1z8|3(}(#6+;P%wiARb`dbG(5l~Fe9j_jn~|EDO8e9HgYdqSdQS%& zNt2+;q#MplG|=;qQKmtU4&k0yf;vaw_IpZzHmPv5rEL~5Omhvjv|b%Q zew?qQ6`adbk6B~s-R-4#8UHGX*-Kp_7V%jWpDqcvN$XEf4&#L{W8J|6o$W1E1%C-6 zP~2mGLM{ z@nQ79G27ne6YKU2sB^3MHcCnd;u#+s8=H-6!pFz^o12?E$D1N8%KPBz9>0F3efy>a z`e*vvYffZggZ^vi;3rS4+Y%+Ns_S}GqhZU+G*V;=#>fAjn401j6ElF2J97B2_VZH$ z1U$N0CrHKAG?8nHIr?p1qP83c_CgyLRn#}@D6eQE$HmXDDkmq$-BwYtG#;s0b^9c! zRbrt@xntF0GylmmXYw|drdq%-ec zDr2{|x1(ZWvJ480ap1g3`Yh+%zklDokdLWNZH7HB2{<{7iqqm?f zd3}XwXpVz4V#-l~xYpgje?M#nwy?0!)z#H~V^s+Ski#z(u_b9}Xz+btJqDIa%iI`!p^~%$a+~k^ z^_rV~e#;$-_&^W1qVMC!kCzu)gjBItuU5589LeH`CeQE#fRm7Te32MBamR2hL8L^6;k4Y^e>L*W~BLC_s zF6yzrN+&PnIY)SQf`^c;nZn{IfI?j(c@Ft!wrKd{}}t+?D5^<6uqYr-p`;92~z{z94~EY}u-le)}btM7HQP3|a4)Fb`CN zu&^-K0S+h$a6ry9>{rO=VO5AU(qMdU>b1l_1*a%22v^J0CToIMVVQeDfk!x6Y~2;_AWNhe#kLD>FxOj2=lP# z&$}{|BbR^3Gt3~7kQr-cY*`EI>MU-Tp~PoT^f}cN;l4|(JI({X3;`G^$QOPrkPHzz2hLi16&CP1@N=aHMOZ?4F{ zfB&A`l^p>_X$FK9FXR{8l&*?O*eL;Hu7=BqHR``PlkHgTg#?O}$1g1ieSJ8V>|`Gm zXf_x&hO?c7SjOpQlb;LKIY-NSFOGZfo&DU{XgJS|>i?t239bs}Hvlp8z$f1Drp$U& z+l@9JK5@Z%@_QWjg~ylXz!N5bWh3IZ?i?H(jQ-)V*W!=u2?{-g2j2jox7=;wq;Cyn zXJxrdlk)l532frf@C!C61K4<2h7}zKG?^Z)U{+zK%+C6Q+KtHs@Nta5banY(Ru&7e zqyKLz%ln~!RTe&p*cRk0eAgV4B;0Nzr<8A0><;N?xb6bTG(7r&IokN5sHi?{YS+;2 z&YFt(uN}A(*ewlme`T@9TtALMc3TVJ9x9k?@Jk4aT=~$W+-1Jd>JWwP*w~a5cD!-p zAzcGBFi1Jt>5L~%q${#}--7$5Zmp5B)Ds|WW^)a(eadgc1gGsfGoB$09ck)v7X+KW zR}AcnR>Cno+pF^}qyL@-NQ>q-B!k(n4~NNLO8r+k83@>#MNk_6THsGPxhgCSO^M0x z9ZwFQP%*Xk{8v4>a^(u%0+tL&3|JV;g}D2g*B73w&JCE5cMdYJ=YI@3$S)++j;Z#F zeGlkqVP$345+e}pgA8lqrDhzr)L`%6~##Mn#4vWC*ubBzjmsUAIy*bL zlz|%{_mXE&K&W)HVuKWc&8->>mr{8_sdE~VkzV+G0&1*|o?a9n3fbjMLqkIgofHa1 zIIuS8>-4PstMBhxLkOXFc4g~}-K5g0hogu1O4W39uv|E!VoPjBPqCGu*Zs_PnJu*w zJQ{lCjssCZDF+L1J%e{_V7PVfHxw+IWkC@840_W<21pjJqu^1E=m_2;L@7MGT~ ze!hQzUYQ-NHBnYpo}w1d5O9%V4!s$`SSs@3z@){h_^?#1qeL9cc5Ief+*@AVEWMm?NG#a2 zCKAa|LP7#@Z^Kz7-tyxsZURs2{2nK~GWuM|7W%6IlaXB1rIg)ED=(BFAWGqwB3Av| zrPnHMY5)SutPk&1?#wH!Gk$B@G4>#y1NGs{+tkz>{Dyb}+H*i3w}L4Ue3+`!!WbMZ zY}J+{7jz&xmlHZiLiO84T)do|@-{X$a1mYDF9ZY89z7N;SMERg`hqzfoX*&vg;FH* zz*@+@$s;!`cmpnE5+aIuwdA8YPz*W7C}<7ccIF zhgMq-Bz$!4SoQC^E}+JxR_*Lal6-hbv-zvIeYw1zst!O1We&DM5}J34RK*1N;QYMv zm&QiB`QeY)42bO3cu`egnAq{e@(BV?mwV^R#N;F&A73*ju)o5Ethi>*GzDY2T>2BYql3#z|@b8xnwj- z#%aAq4kz{LJ=bG53IKhWktQrGBo5wUm|2BjW-u+2EgS`8n?Cr~B7i2uJVQ+*BO^$X zcpV)bq&hN(qzR&{rn@v{F@0a`03dqbKL~|(Lm@~8=@o)5{#f&#y?aHW)cY@dVZtvW z;*2}iROfB@`nngQDS(o8#*5k^W^@kml#p3IC<~i~(Jzh+KUz%8N#s2UQ`Z6ZM{JJf zhdR>97ZDa67l#`jzKbOCf7Ch(<1FB(2;7N|W>3KS2(pNj5C#tXyLa!7lutmSKwdP6{-t?%=-{7ZWV{G?!KoNdK#G5lMeGc;u=Qaw0fLcF z8K|fMfS2itnah`x*bMjpWU%2-eMiRYa#jA5`z?=rAs|jpni${_qIj2d{`5j8V-Z>dYb9oLfaj-@mI- zq0>$@XoDe;SM~Mvetv$YE!}u@9tfFS$R}*ZIr$Llv9B*38NSz|#dvv@q4!@!UG1Cr z{kssFgf^g7*-84uSykVoG^iWO?$q1^hYuf~*ilQorj3ORQVR|ZrND%QAb8 z4=A(BZG!iJK?rH@0-WY8FS~=nGX{PH#R{q$0hzG@t;T)f0^gM@+GoVn3S)MH)s#>jf8N2=R z^p#Hz3CMPsIMwPSv<8?0x}~MJkPBkSda%a33Ro)sew^PbXMng_Wd z61d~1o+PPXS6A2E($WR@&n>}_gVbv{E@f;R;L=1(tSYw5X$b%3&1qP;GN^nAE+}2F zf=&>!jLgh49w~^CG6+3#QK!LvO!Xz-)n6T>U!J#II>EtF2$d8K%EZvtHrEVf)p1Jw z?vAg+K$X7upjUd9Mp7<}!4#O+n*k*wrpbpclk*Byu#nNn@q^$))GUJShyyj?-lg7K zg~`jSLzEzt)2UKU2?-;Z0U`m;mO_bxF`c1Od_GkLq3Q?~%2bWDh4vX1ziShfoBlF0%^V^eDBjfQQ*D+FvkO4lw-ZwI`u;ha7vj+FAx5(l-7sA6LWOpi{F29X6^HGM()di6w0`8Ha zmNM8N2yt9*luafbm6er+IUmGj0S^Nah1|+ejxFZI0fs2u1zkYLLB3yr!6+&WQh5@q z!GuA)DRB67Z``q8Wo}0#!}|JWa%(ehX!$(xw9Bsf03J80c@ozn1U<4|K*5s8?DRpPs#wK zbC94*DMrYoObxa~7XS`!@w_quZr}!G!X8vO8tQ8g`8I8|x>#CTK3fqm zDT^AvJ~rZf&3z^_wyGP6V~r2?RZmQlGIFZmzD&&HRPa!Axd9-rZ|ho=k8e zLz;9)YRb%L|CiX4be~S0O7_(N*$+fC8GI#h-J}M*r;>Wh91_gDCr$!wnj5HA#p3IC ze9DJDn|TgkHUZRI2F$fRnKWa4bJs=b6vJ6N5UO>+9Xx0ICKy!b4C5-i{nkKmSIh05DI%-Cfx);r8HG3y**ycnao&j%oj}0DW zy*9j>9|;AYo0}ID<7{8Kr@-^DuugDQ)F~IFRq+Q?gGnD6mk>5 zs$En_4L&61G?<^R7?BHc<4Dm23mk+a<(IOVS+e35Jw3fX%#$Fh7eZYDe9=viL#8Vp zzX#EfnqBD;GPyt~qN_MG_a8(m1wv*l+I+|G5IQT%r59E8DNmn0Lx2|pv>F@&O$4-d zma}DzitG^$TH*2l@$g}+R7FFhdvT(L2$Va543z;~Jz+XcQ-zap?;JYbDj3)Llp=Q8 z*h}$T(9ZGKz5hU7Vcn(*LYoIXhYA9>$2foi8CRZQ01<~+`CUa8EhQjosX+db8C)Rh zB7cAT3s|q5u;q6xa5=c9N_oCa`CL?o`W_>Q&PBk7a22LUwcOoHz^w;=w%80+=4fJfSk0+SoGlx?NgH?{tdjBg@wZnVe61L)$Kh~3;PIoa7HZJl z;7kkEE@Lgfzexk%!U*UV#K%P!IX|O+)3^o^^{&2`InUu{kx50EtWkw?_E{V7M3f2! zP8u;jqj+^;N^Y(4Xg=rp^ZiFHHW4K@KY|#DEYdzDz`GDT0ZePXjg5^ea0<{C5$w{r zkfb%ASLYr-Zy3>nmQA1CO@vGXtB93w)yFgeY!#Fb<|4wDKoKSvk-Y?L7gvBQ=NxLw z@p=ZfKoEn>A1fR9FNBLLx?%uY7|;O9{hT8QKoQ&LgHQvAW`1E|U9hKwTqZxZx3{MY zU!3cm6wDwX1FZ#j-rpP<8F|78tpv%ZsiO)COy8@kwQANcs$rMGgQ+TLxJ*}G>`wA1 zAN0usMw|^628`5$rkUUKXFLsD!P_uYi29>-Xnz)4X;zZ3`RHl`FrjW`t}OuY#k_fl zzYc=j2jmvFa+VD_#>DaUKeDrh)vWvy!mY&YPcu2jj=;9PII181b}DE6sqg=RkboW$ z`58|#;`f4Ek_|M%z}nZdJ&h+!^R)4HBVQQ1VXPn;p^?-Hp+Id2_Be~@vIh~b9q9-F zYE+lW0wZsvJkO)W=&$&j5@-3hWDaQg{-u|zOv0m2va!u>N~{Gy=p&B5M<0aojQQ@S z;j^CJ=cD4=#Fb|k?9vs&YpSRrJ_9UU2JTwo8FwX;d8CnNa+jx(l# zUfg4V@d{NXpQ4 zK$LxB9~IKs06dwJ0IH9}&v$Z=AN*Gsy}c%pPWEHc2;|(1FH@JsM&`jUA=BnGTyHCHeiO<@o z-=(&{c==M`{mTJI@$|QEzXM=Y#=z47BsEo4Vf8K%WJWw8&#=&=pc*91z+m{ZXD-!# zAmJy$7UEm+;%gppCVUIXZ2<=Egw#={jKR_0xZIefEa;&oAT!F|fS*+{h`%ujQ|qQ{ zu>vNRI}j}{)wSga-7-C9vc6A}WR|(dmzyWxn9|D|(0JcapFx$_cKOY|&J|zxrHa&-oDX21h z-xn}tV84NIA7yZMqGAa+nHw^E94Lp%B~(+256A}GDKQdt#q3p z@(Nn4I^ zB^rtkvfHZHZyBTa|$pVf7q#gVou_k=l!0jsHo$d zmt)H7DxSZ`ll?dhF}}FT!A4X~XsV8SQE@Q_2oR67Zy?m#u0fzLxV>8eWt%gdtFGwXlP>&^V)bxB!GpIuGB#MR)_)7}t2Y#I>tJ)iw*HVi2u{ zlszEID*3x$|CM4W1_NGWFXJUKxnO%E$y)$$4SjROqUBQtD1HM455WT{6naD#A@+ay*3G5vEoIjo zAzw&n_oY^GLt9@Er%1;y?AbFMVe>x@4~5ygl%uydv|&V2UMB-uxZHD00{ndDjDM>{ zCM5wtpS?s~ZTA73MCfUwgQ}9!4`_{w07(Rk;x;x43`THpEi5c}!L3I)HP{>6%Cx60 z{eW>jvPsYkYU+QS7VI6R~U zJ;A@vd9?Rn%ti%ROv64Jwrqf zEJ7B~ENqEb+Q{?_xS1I=dk-KK8&PWj4S)jEWrfu#DJjqyMcN>Cg|!B3QXWwmu4@Si z33))nkUWMiEtvWs4@qFpa4RFS5#SrqGRO)ba2?llPc+-vc^3xrIs0!rh+JgTpxh$nI^v@svJ~*L1Z3$; zXkOZN=XdO7xPDd{`pi1O7TV4zOe3-lj-rj04_aorQ2gx(Mbp#Pz_L`Z!p@8X;o;$e z3wOe*jP(r`@JHbM4E|G~()ob>1WFxehlXpb(y{*lxC1Rfp%m74gACzP2JNEN`{I65AL+tDIyQ8eWDezp6jnPuJfzhfn(r@wthb~8Ei^mk;vRaUt>A6jgg)+ zes5Xlsv92kr(s1@2BIio*58D;{jJrX^HDHzu!8}LV6t=eG)5U3T;Lj*ZrMU_lDlPc ze0&-ac%VX7s)wi=aN#bnVwU0X$nc!jd=B{Hm;cz2ThQ|GU|TlK5Xh|j>v#Hj(S?9q z^0&u`zq0zPC!J*Gt6o<(6{rU2C`iyJuDM{g*--x+5F{u3QAs?=igw3Z~qJ&A^N(; z+s&{CNm)zFToAX&0Ec5e)dQXn`?o4aqL948i$x+6>53u`F@SK#K_KW2){CaTE7>?#L<>!5Clpo*LQ7h3QZR{w|`NgZhKJ`?K@k_PGke=118E06jhvF z-~WN+zB>=H5y1ic3iJRs|0TLBL-=HXB~w_nzGakZq=Bm-bP8#dN7O;FKiVK)y5s{F@S^b#M1Bo6Va{t|B>hp`hP0ADadRV zf}H^PNTefthH`!?tZeJ%V)qvAPwjz1Xw!t{=ElFAclSr<=H>n#Bd<%LoEOGksEDr5 zH!!G5zY>5U2hN1n3tb|mVA&o~AN*PFWG>1;utuTwe?$IW0A##_4AMBbZxzG@&|*cz zc7?gPJjfe6AlP7+6(S8aBVCjN7t&qo^+a|`KrZ-i)dVWchS)*5wg{)|PLn^Vpr{B|#l;I3ZiC1* z$5~{9f`lh^&5(zO$i%#CXwBM;e|wEhO3yVPU1BX&UK_PnmFLp`y9C+d=0~w?M>j5Eo z)*Wwy=PIYzk-iIb=(!IZna?maawzy_ryZZ$eZ}(oX>J}Kepn@#IxNDLFDTSRFw|iz z>IcT4z0ZQWy}b;rP#fqT)oi(f60L7Zb00>%brh(p;J%A2HB|x&Y&E zA9kI7&*>Ytj9#PLjZ8H>Yi;qs=H8Ue6Oo(mPH~;J=EgakeDc)8hxU}%$D8(->}y|% z3_g1Gi9tk{cP#GO<+hJYK}**$MQe|f&I{QU)aJrO%GlTgw3%7=$jCkF`a-&+lT+T% zZ&G)6_ZG)YbaeEyLci!biCXOG=_x8J zyF0Q4e@e^C4dIWdv@~_*j$(SpXX$edBk#n-#Ofy8w?ach$?lRE50BLupH11GLa$Rp z8`qmA%TXWNzvpag76Tb}aalO0lS`(uzDiF|XNhPOWsk_~vG1l(D4*4T>{>$M^QkpU zN=zicbGxx$Asq5Z6yoBdE85(gboa7XO1D#apXg~inHsc(zrX(|W|JB)(BDt67Re5qbW^w>y*JH3r02k=bW8_mLq}?UPS+3API?72yz9vD=V$xnz%gX;!57%fV=J@{F?;_)8F5p z#}0=PUq%X(8T(C-68Y0zHn9#4Zz9v=Ke_w?y z|Mwf&|GNlK3;*vd!c(nbiYri&0<0UNzV!-p%1wXmulD+ku;XU@#mFV?A&}xN{A!%1 zo?<4Xv~8a+yJtz6U!)P^(HvrOkJ118T>4$Kj1MTjTpYTT-MBk1dQ6$?*P^;_t;Oy; zem;(BIzvVUPw8=i0f}0HF&kR@92tD!^=Uyg9t>&He+jWu6NkQ#@25o2TzvM$Fz?9{ znVP-PfBszxez?z>F>#z^@0Cod2Gyq&+~lrxr3A_Sn(1$F8p+hu{&T%{Rt&@E!IVE& zGZ^`+-us1Wtq>4!J+lfBrghV~siyYi-^JR4*mCQKbh3x&QaM*Xrx-j}%CUYDz(M=k zH%oz!oD9BZ8QJ6`$%NHO6COD$zo*gWEcj9(l%V-Xfk>7&G!@;yY?t0Q6jQlfrw9~q zZ86Ek_Oh}J_f=k2zO&6l6&kGIyp2iad=;lojQ50xZ!YC?Cgz!Ai8P^p?zQX1*EC-3 z*<_}1C3CSOInOKuqJ%Uu^(fLDUZs8~!bc{=`=^WCErNf>4|<#F+#<}edD8q9LnC8O zqGxjJ$RbmZxJbQK(U&uC#NoT404L`MOMd>FCB1a-$FC>u?11RyZ75X$$J{kxy1bfq!iI5sl$C=G1a1O zQs%a1jLfdBMIe03F9>*=mXxYEWM|Lcj-poJ2$*M%DZbS z=94=+ZuN6+oY@_fXQ3DiTbcwPp0zKYCyl*OnwP*hs7PsUty#OZnu(3Yv#EHA!tYMD znA(-JWq21p&)9?6=P}MITK2^%5i z&zA;dKShu?(b4=~$F73b!{m4*^sXQ5JGW)N?Z(bd0D-F^bRdy*RB$#UFI#wLtCOi0W%gLH3avJXD zvM0wMoRU%C1g6Q*+_0j;Jx_~&>(;HIkrCsee4}#b6&fO3tPc{Nq5J!;L<|zNu^bO& z`aeJHt#Yx2iK&n)JyK@dnx&RLxx5^POH4EQLwKU;^yCO5rGDv?XpR5((i*ju?#4`K zv0z0Ro8+qFn2Thlr7ZVeL7AM;*>h<}DxI_9W@;zCt;Wb6`!R?|RFvl7mq!8W*|H~x zJ8GYl>9}-CiD<%c`T6-D7d<6@vAcXOK*l#Ki_JeEV5(o!Aa=lZxR`x)v;qqW(3Y%l zHhwHwr73VrMMKfyXms`yt)UVbS-keC27Nb=j>%I$1yTy3z%-_mpNsi!TTi#wCK!+u z_3V7hehx9$b)@FSj?CJ4&BkU2@61mXNgPC0ULF%!URlYs*2q@JId|?H9~Du6wt<-r zH6?q9>U!wSHrcsuJ?V~+VAu&icJC8Kdn|aGh?K4_t(CPk22xN^kfokU5Tj_CI#8w%;n(%Cke`+;zG7Lz z1&5uLX^Z?TS^4`X^(W+57tSn@LJfphi@mo;+qd$O;^Sk-4ZcUL`468m8vVS^{jq=6 zg-OksDpbFIbGF0&aN8nBzh*dtQ^(w#t*@_d^6y`cAC4|At#d!#8}U)87CpVM@mb@X zu@AIdz7m!ovk60t&ygZEb+L^hK}j#Rr|A3&*k9~ zLO)z$j$en5x)~a#BBf-V7Bp#sUS>7@*{$eM5h07mOF|w6nfEU1UJyNr4>MH}bi-vr z6oinVG^cmuiGg|aZH|+^6KPJFdN^qUcNvGi)v5}VPMbLGE^B4=Tk2Lh6Q7=*;zP>_ z4<{ZS9p$qg7S#Cih!}Yt7Z>QZH8(X>V4S6qeW4|S?iB_WuEGfW_rr64L-DuheS@8A z5uf;-{{2GffF}}|&Cgb`h$ZUI)*LCUxXBfkH44a|$(l;*Cs$RsW7PlkKBo}d|CEiXo%*)g#uK)* z={J^$L}hf~tjg@~h6frsu~NlmERPHFNTbeqF$XI=a-|5)IzD&*qPYuyh;J5t29?j{ zyPH&jiCI@*7zPJtbymKA;&YT~qI=iyqOID&tJXJcgb@;VWD6V4H_Fbo{uK08e*5O2 zrkB!J;tLlp^yKOYSMLr# z>znUuYg-H`WkN(7f!)vb9!p_!*ICnbd>2wE^D1^0i^%q#vP z&#WYauUqRu{g&6DWxDy*)buna0|SGryL&oR56<}bM>B0JlKCyCP^IDs^dVzU z(~99|WFT~~xAAD^mz?TPPwE-GVaxd>(M_(N-(Eh)cD7i;hHbacXSc~rdeK<-X@h^x zT7kQ;-5k++?;WX~zgiPT+L3QY4AKPV26A=EpD$6y@ft(~5?^i@8M!<@K5jmg&qz)n z7dPw1A*FF_Gg|I@S=eTB#I{aurX?I!0LRwW_E(;PWcTw%AA|ShIASvC6)X$I;jB z7ZNMTY8=1x^CP3B4U6T}O!GQ9RPV4!mk%#I(e0{E4sX)95%`4&LZ`)>BUv=)59u`aY59c+I;$hFz*Lu*KB!7vJf7`cNJN z+}&jiiG4(&&X%cauiETz{gLDB-@n?!4^tfez;xcWYydGD<$)?fJ>ln$Wr5za@WX20hamL;Y5Dy zPxpq3o4XO;bJ)lUewR@`EYSc=hC|y=a5IZMUmcT@pk5v0F$a0yGaRG_al-7u>LFyj z|B2d%)dKgJGV{)x4}#Trs(tb1MwF4@Z?3gzs=l;3irBBy#W1$~9BQJY^ue4@=>3tz zzDR5`3|yy`VIhN#8|FPyhq|@Ln;q9u+2mRC`NRA;W3ZJ1nbNr6fy!#c*!%b&?u6$` zY3CMDWI33oJmj4GlpK~TD)fwIjeWR`MB%Jat%Reey23s_>S}n0zdtfCaAs4dFFvys zT6@%Wv(9?NYT^L~28LH7*~z@*Sro3ec~6S(vQD7DGjlzsG2HjR3$f16szAYK9tAsG zHa8k^q663dzX{n_~az9*XZ(5#=ESXbZL; z3jKoqbTz_g6n)5@z_+E_INz$;l_2yC7i|r0ZnN$Rt7BwVEFA3p&g2VsRG7=CzB>F?f0NWW=lc1=yrZ z_qfW3IHrt~k4pG3>aQT;{u>|Cw%uQ0r(DET)AsEV^u+_FW)l6%-z9T8TP#)!+T_AO z?q%&#D|WpUwuMHWi`$s}%FHjX%DoK}rsD=u^L(7}LL0tLXmES_(Vnl|E1ygyUxL|N z$R*hT0t&mydSA?ByQto9W-J?K3&koLlcyXHKl@L39T5ZEcNG;+Bi60zVz|K3-_~e zTE-`$eRt%6l)H4_eB4@O*78kk+-qb%#gKz96VqH@E#z{@+(`KRt3%7q90QE@OA8|D z6jJ{3hTm=jdoLlNua)Pa)yJbG)4+6LY;HG3;|3!LI`Kw5ae4dMn(M+KbI~48iT!1DURqeZ{u(Sg6pdo;Z`yG zec^uH=7re)O&FSGpxJd0%p$= zbtr+kgLO&UwyGJ$$5G|mFXNN;Rcez$9ql=8W>e&AvKyLZ`&?)l&Badf+2AO&SFn*X z+n+aJ%y55|R*b3g0O#eC{d?3LqPP-t8NbGqZx}A;YFN2`Y<_y_*{^~p0!(g3Y-tYC z+(Bz|rPdgln#PEB4bA#qx2FU}$Q(s%R+NRSHYZC@Z77b{D}lU=XRO(nujmUpF2{<8 z$(puiKNc6&S8O@c&E0rI!J8lDv0gStv@e?AU7Dn>nY1S}tEEA1xA%49jpxo^pGVKE zcG@f?DB>T*7BU26hY;7%hbV?p96gI=CwV};;KYtY^1yT;Hp%42arZ}4!yV2?t_cY* zmCnr`Nu(u)6QJ@5zwG;6q5c~NmH04~ze9qGTnl^9Q-wQPf+`8>_4`XK z*myZ@xAnPUn?{VG1)lM<%JEqMeZ zYLS}x?87?M>4EUp671vS{hakZX5uWHb7k$6Ef=|t-OuNJ-%m%#Rj-`5?@qX#ogytp zx5qfP+z2$Bxcn}>-guNsz0?2hXNpYRETzOR!^4{53prXFOS6U{kxSWA8IMvD6n)#{ z2p&&PbnDA6?omJ;?vY#IkmgdjCS1$kh^M?#s`Wa~2NVl%-n*JF(pin0#MpjbCzOp4Cu~oPeD2%~ zaZXJBR6jFEBNqkVqJ#^_qeyZr!z9KILZq%We&XR$rb==CBaVBO-R#$$l9bk3EJ>*= ze|)=1?sE8gG>XQz6~=34G&6?`-RAF?cQcJ`{m;)>77)_K3kNc_$ECg&54wqq#6IT; zz_g$aBva@kT*e=K;=fhry&l=RK=$UT*Vz1d`9-^)&OmC`E!9EAkYNgPSIfZUTaI>N z-7HtB4JBFIr)4~*RPe6~V$yz#ZWnlXscRH5ISc4TKr69Yg^d*{qbp{>81E}$6uqilQ(hK;UbOVgxA_2C_XQ^B88 zD^McHphk53S7_VZC+_6=3g|SWv(Ws(8O1A-f z4?adNhQbZxoTyq3iJ8Yc%kP_Emr5?=KjbhpMv;rT<(DSk!JJnGl(Jgc(bD&$aevI}xE(?v+;xE#&0!^!q0;0&cS_EK7x& ztODLxpLL!bJOH_>k?VP0izNH@G%y=kUOc7c$Ul5NvKkIIdoan`LXk-?{@+{-UE0H4 z#ii2jrA=yNu8EdDO9v*h4s1UKzG>ywUyls^SMbeBI~vUD4<1a-&jr@|ErQ( zSy^V139*66+YozI^%8Ci)wP zoe{gzX_4tZD_piS{<A`r9wIFMp8OMSA=il4*2%M$0Q@T9p9GYOF#HUfIb`sQZ4fMk^$ z=w~bzmU=>ozX+=LTxi~GSNA-+5JNE8`tgySMp10=A79S|e4m0NT~*cB+m3&}lOcTZ zcVy*^r<+3dK~T9rcW`jqvY*ld_^mC@Fw5u~|aioyD+Cpsnu`|{=YVmhxsswAHeczJ(Kmijpe zfLrt3t-p)RdJBy29Pf_Uy6*IA_U0SiMg;+c&!_<##5OEMK7uwx#BmNmG&m^832+)3 z8rBb&@*10)Gk@*+WeKt&VcFRYw}do?E~Ym2vC<@l%{sQ~#{^(f{mp2Ye#@C6TGbgv z1OR7xW~R5?-q>oqS}WnL#G&pBVbOZ0?TXj0NpIe|HD9rULr6((Z5q<)qn#@c>utBc zZsfE)LAwikc)7Mgav z8OKReH_Jv2HOpkr{>Hh!;jp`*962$wy?<3d(lyD)XT#`+%$1`-t&ei0vw@hltv4?` zQ=Sd=_Cl_lB&4>9Nmafx7vOCarJg^lQeNHa^X6cq0A@rH;}ffn;U0_0&B3vu&plH# z68r~5c+v|OUTT+EQkW+#4d$g={QTIgag}Lnu*}vVj6ssV%yyzBnpLH+xc%C-YYiIt6s8+Iz7qK$cQ3Vqe z-YbHFbhMuIJ0#Udt5ryto(_l| z+$HN>Nw?nat$=e;b0v@|zRGRun&sfv@VELe+DvL?QhiEFgg~(cMLPB8;^gY;d3ZTI zJiL3!5;gT#2gstb$*#&9HU*RYj@w!OeGvi;t0{+)65|fe9NkFzV`^X}HZkaXC~?uXa@-o23axjK7d!p;ii6JSUI? z^eZZ-AX5QXK*E5H^}1PCr}OF2T3zyts0@DRJASOx)KnyFZ2j83cZlCNA1F*_R@O^j z8{mr2VS+*2$Hu{#f_e*l^o4u-Mg)T-Pt7S@AaiiOJGt9FiSQE7Bxo{yx39%2vUDX& zlE5lA29X&5`5uLT?i>a}EB=C9{KYok!N&Hse_kH1ho@(6<@0BY&)?s$Mi7)Ijg;}) zOw{TvoS?3h1;`jJutPH!@4vsX;r2wG+@ zJo-}9X;9_N-4e-QE1;4$I0LG#-Sg)Gv9T1$BuKg~>r;(J78b`sg@uJL;ep7fe*=9S zRAxT6O%tn$S~|EMl%(&cPpry`HyQgax<7~`h>MHM;`0}JUmku5w4Z4)Y6+trs(k)s zdr~oN8Z{rd3~FhilSx@xasW~E?Up>#eNk_(KkG0;86~m-9eS_u@(ru{`0Hl*MvZ}7 zrpaE1?4UmTuqee%Lc#3A`k{?48|p%Qq^>UBLXaj=!c%nOIa6B0jJVwFXeb3IDJV9X zN^hUC|6JOk;(vk-A`ZXv^5c8&_!wJPOo%wGhl{@j5MIg{#KJq@G}RbHMn@M8`>tVN zfb!e7Z&Y9&ynOl6(9De0`jc^M1iXDt%YmGb^75P5czCxn)l*G7;Nu+4BfU zI_9|}%NxNB_i0;qa!vdfe-VK-#)bdR0>Fb$)hc=l%52NePby^8)aZ_hHhrW`JjdMStI-9TJ6#KvC8MSLDNoAWwYD-b5|lJKFgngGG%Rva z%*nxgG6>IjUZ)3U%cB(+(M1J3vH?cUJ1&>?Nojbi>)bk}hKJjW2tqyKWZI=3!u9 zfV{?~Qncl^oTwF#pchYAG3GAT=8ogh3x%n`$j3)oV%gsSwaE`-^?3WI$~!>|b+t7b zve}oZS+s;8XCgO6MH4!`H~*Y_+WF2GhM|Jq2&&=e=;&CDRZ_t4?{ap3_*vr{jD+5g z_b)pw^hhVz@==jeQlkInAB5OwzGQ&*P}OXWYh#-{y5&CW>+4*sDnH%}w|?`(n_677 zxX{#h_B!L^$EOGBZ@OTZf_>Gq=+t+jB+DZG>8H`w+>eEcI`7o>*-~yM$Mx;S2SjwD zZ+0Aa?lpacttfIj_IEIk!FhG`9H0~sno^$)ex(v~<)aa{2?JD9 zefal_T)wbujDc7qS8JawUdK=qJq=ALsGyDA-6Y)F#e7;`e}83Di8`^NMOfiD?@`y? z%HSRL;FI!HlFK*-;sJ<6q;wng6_57Tx(A(Mb8_K)4#VPx{?}(xv3)YLVyn^jzjoH8 z&$>1vOagd}jEsG3S@F%St?3T4?G<0W0Znv%5WnoRk@hkuDCn-+p|}hfTQKaJ{OqQ&}-}MhBcmxUo_L?h^w~M>GyX#w9jd%hH9ve8dN2|A-R!6Cwot=Y2LuH_k z!~FB%t=(s8-E)U(q!)92{VeITX5RTrmo8!B;woNU38TJ_`|1*}Jm_y2R8=$L-lwB~ z+8r$|KWp4U;Dv5Ok3&!&&HvRaC5#B7-d9?+9wPAO!$FX593QW(|Ic(#VLBo#zqPiqDhXG{#E|D0)X}3*72sV@_5)UIY;41NAB=rK ztf-PsIi^ge_JJxCp0Zxm^g?g?!tWwhuY=i3fL_fkSR9@|k6oR?@Bt&>TSo^Wm^fGO zM96}nn*XGkpuDnDCRJ4`_t6X#WtL9aS3$v2o^+=$>Vh_cnA1N%B(*w%A}~J%LAF=heyj}y$9eazo4M4 zP!F~;t)v$%)Kws?1R!gU@E9{|>u}%+GthqfHjRvo?3|r%{FVoo|2%_4*jn8geckaU zVO6(U2*8aW&ZqECQy+bCzb({JCCW-w* z52K*XC{;L(Py^%_HsJQAmwy)x6jf=w$r8K8YQr5gz!p=b8*ojEtWB7ejB@%*1 z!_-c9TTNsdc28d2sc6SB-7RMnaP+lh0gXq~FtCrA#KdS3J8;pf_r~4L2fxyZIxYBs zA0O+q!-DQWz&s9mPg^7C_WwkygqF}le?W;Az|Byr_do~$NV!IR7@c?KLLJ2$Fo=G; z&(6w4{)TClZ`^tw#o_2fEw`UZ={5h?_L?r+R!5m8t*_fz4dtgwtv$F#|! z0Uo{4afn8QpjRS@g@pyGh5)<7P5Bcc=T)=7@4mf2hGJhpja<6rq+MNIeK%0d0dMrF zmRVyUaeirO+{Y@X#g~e)9PwNiFJFF@l5zzxU3*6%t9Gn>fs9P;+Xkh!1#i#Owzje^AiTu-W1@a;E0G8c$UC3gW#3Z0hQRfxCFg@dr9B>D-DG#t_5W~IIG4qkq+`GR)1)5q~ zdL{sY9`~Ut7hcO>S-yax=7%1NRDQ0p_2X9p^AvqCh)IXO8HGC?>wIjtaiB@-+*!3vZF z1y1ds8(UiBMGv}HS&d0=Ixka+Ixpj2c~+LuIo9mFJOut@Wh$z0ff*}kklt$uSbhP_ zMKLk4bpV!ESXktS$6*&tO-=a%kNVcwc-zvHEeqI_(azs~bg@u5>pY$4BoNR2iL=un zz`HX5rpvw0B%mm8k(-WlL$y3+R$ni*zrT2`A#8u$7oLEx0P;@5GMP`*))ePF`qB&-24ynFBc;|urPl9%)hMU; zzB@M2=&O^AU)*^C+!EvLWX&5ALWN@k06wPX<^t=^PIa``;D&&NHWAVppwodiFwLQ5 zWMqVy90vyng)AKoXdkAuligWU3)xA5=E`?FF^ynRSShO+oAeWv-CAiyRty4ER8o zqt1PgCxTYwB`jHeb2APgE-nqv5^Zj@BtAnrfDc!ZkdP2XPYj;hWGHrpsg90U#K*w< zgaY>3n}NuP2zkJb8H=#McYpq-dj)wPMi@#*yDN=Ab?r`%-J%cwluVE?NHjscPOYv+ zpo9i88+hkweV~QKacMDe3%l=H0nsvol13>Tq~81h$SKYSh(875_swO(6Au&>ara$A z&=kyn7qal^R|mu8r(41=_nipBz=U?u2){B3%glH?P+^Q8w5@QLako2%cQt9?nRuS; z45B3h5F}NDqiAdZeiPL#X+<1lwby!5Z!i4(MB>xH@b958OV290x|W-dRh9r?qhM!e zN0r-Y&>Lj%8CS<^IH3Sp&_AF$G=qpJ3)r=I6&9xn2(B#LdNVW&0zJGMeO}lipa*U_ zwYa^g(J!_c)2=?4doOZ|euvN41oDpDmq)o7FFvh3s08??*wNC`5(-ly7=}oEwXM4P z8}pwZ!(I2+Qw7&R8%l%z)3*z{E=Mf9q5H+qI!8Qz9G}Kp|1OjR+4X+nEf?kTii!}B zT0$ng&-CyaWfc^HsRT@dd&~u=QMB6{$#4nU$^{^Vl;|JZnw_|to`=Q2^j=5YhC$Z+ z-nN##p|P=u_Ekm(GpaD5A+p7jJlzT=y2#^YmwWfZio2;l{~&X`qf;piamjhDdz*#$-{qgx{-Igs@vd=qxIJ zWLj-e1HW=@?cuxLX|z<~p%_7^({-g}um|MEV-Ozj@$sb~d-dkcoAuSIHBju0deh|K zhd$BK7fY>vVH?XCcnc%4Z&3dMK-Z-N&WynrDeRfaS*lJcZEv~LNP8bYZE6`q$W zM{HklY;3Gxj4Mz-P(eW$<<8XvdM114PTe{`@c&lOi>4M9f;IKsa6lm8f#-n^otc`w z@!POJ`Mgg(p#9eaV+S1s0}=B+r9}l15b)qNQ#9trzw5mLn34Oq025^4xJs$H(p(t& zS7Dt%OmE25DeI~AbcfL4-JSXMKX56QAP^oD*JS4Z{E4w1tQZA^PDFc?6w)i4+edfg z<(b&o+y2P%)*R_N7haC%DT44{+08zKIx0ePH#Bq!g0b+p#TcR1Rnt2rR-@S!){DH=KH+T1$zx`is zy6@(+sgA;y`E$867X(N?yj}$j z#~fHm?e34RuJe%iYJ%s3Q009GA!i2YC7xk5wYZ+E>rEcLO6C$v$P?Zj0d#;x$*t`V z5+1ljpj;}YkC-}d&E2owZ09`qY#cpx z(s-<;Whz1mY5=@8HT3P504Mze1D}8o*jGwWIU!O|ROAN{;M#+^%h1iyCJ%~^5HymiL{XmA|f@?1rAZZ0=4>Ygu} zyiQ94!~k?rw+XiAgOt^=KFYFj1OS?onEyjB@JFDbABsS_@eXk%D>yDXaHw z_UwxjccBse=IpAOr$+Autc355}roF3I3;{5DHIr=p?)1Dyv# zb4qC6g$VyXX3ZHGN3x%klVqKooTi*pkgqT|Al=Rmo=+in_p=^^(H~~o{W$5X0?MBl zE;IVOF6EXLdY(*v!P&1$&#E{wo)!otD@=Dkc7V}-7r4$z31U=*{V!}~|a z(mqNadLQ6NMdA&Tn!W~mQ%7fnwj+R)R0f3U21C9pqN0`cBz>cRm{5BNKuwMLbH|Lq zrH!szYbc}AqGwKki@E=60e+f)TH*0$8kM6)FCc#`zEWIQSJ!u3oP};w=67jd9dmp( z-Uah68sq9F=hmA3;h?6L#8Z)wSFaiYh}bU;aC_{HxgeV^9zf8u7QpVDO=FXZ8eM-k z8~OhIWjMePjB?a&Uj?A=S+4N+|8b-2Q*hvEK^{#A5(@v{_$I!>b%P1^ZQPQ;E$8KW z)?|-2#%>@#Ip}%!q)6jvvjU|A%|`;5xG$`&48AY0x~A6F`9pn?UNC4t&4sq5k+p+L z6$%nuN>!K|cF^Afi;9W_=_o0m3J@c3VBx0yv~1jS@P>lH;Q-)_2YpGa{p!`LsQd+P zR87y0lA_`peed1hW~dpy{lYV%0}mHB4b&L~0izO56+k0oW@a`7gAEn3KuY!l`pXFK z@BI1mh!056XtEz!hss3z((oQ|iF@yF5W}W#04!)^(Pr=*UGsMOZt@OB=)I`C`SZhv z57&M!ZuON|{KN&5JnG{^4hq1|ocAT&HnG9#LJ(As2kGC=&Q3~0gM8d=o-P_ajndT8 zg6?QlHu7tx@k#!9`%`!pBES$bGBWB12QQ(h z1z;zvF_zUxDKXk#wJTrjt%k$ALJ%+uGY8>kxJa>OKS60}>CG40Y?Ur+A7(a~(R*++ zW_yDH7XuQ?*}55q@fUEox@M_m|7X*4qQEZY;hh2_c?oB7-o^Zgzm5lKhud)Q0FKoJ zTaT1Vf9aq8l2`ji+ZG^UzW13IoC`qeVUfrf7$N~Fe}gRr>pCznaMNxQj)sWCUfno8 zaMZ|oOa}TuyZRnw*X`W0_2f}lF7WeE*{5;zoug&0#20!H)m!xE$+<}s)ZgMpa>dmq&+Ef(7sK+VK7 zv|?BEP8wIoMxc2PLDP;n^4OS`gJJOD-=R8%7Lbyu{l2}OEfrL(j zi5A-qZwc@c>eNa5;i5-B(1D8vw;rTJ(%|G4W6akO;=^>um6fX{Fi1~L5pni25< zb6F|xdSO}F+g;9EwY9bDJ3BuL5@BxzcQSzd6`hbk5UUuwHX9us4O=5zx2pT#=;-}D zvs%pscz*Brjd4*IihzOxK|uBZtr<^JQZff{A|UQ-OJIALS=cKoDcyeZBu#J(Km#al zPvG5M>kzX1g%6yv^FrvrS3R8+_t3t*W<$O|fB z0p(r*mVLe}1EWF>RoyZi1cYU!4_n5@#%eG|#mZ23Uv{%uRig_4P&(A@<3f`VLEf@e zN6oLgZP#-$pvT;XH;*>tn<65N5M)512atgRyD1rq3MeZSDGm zn>Q~(@_M9wZ44xMe$PWEm>q<$aatguQIvZJirf%XaL~iP_4iYt$qoRF>tI1(Ax@%n zs}psRV0TUe3W5Z|yg;qNi)VcR7dF~C^Zt}~gP;Yf9R?WWQF+GbEy&IR`cC{^X3H>K zW}Cbu01Op40;!B45a^0yu68`&j~=pl7ZXV=-{oZA0Hq8D0D~456tE z&E=C2<{S%Z=ebUTC+hU3`|$9z{!SQ zRGx;TB`@V(QGRQ0CqVO2&Lo-QfO~L>s9w(;K`;DQ20K^(wHG5cjyoSDVYBS5y)Pa{ zS{TbP3_&IY<|zo>0OUs0#|15I?q-6nb087*OJMPakg&>v=27a$V{!*5RC^42U!%s) zoyDJcXm?wc6QCl317cdM#_c)KA^=Tu0Sv$aZv_N{O91;(vma^}^tDVISE+$GruzNK zp{%Sd-DEXE*LA~a_t)q)Q1Bv`!B-oOO*w{v@iI5pI$~A^ADeM7Mc3aOFK`@r%(DkmkF){O z)m|7bwbn6t&Vnw*R^xxm@o{HL|Je(&PlM6k5qi(gE`K|r8~-X(+UCpD;P#rPCRvN} zRTaq-a#ZB+(CXUsh?}2-0z+;2%^EQTJ-PUAK%sddC=(oQN#0D)>c?FUahFz)WM%Os z{yPicpOs?_8Raz~Ht1m+Wb*Xn2qilIbD!a1th0dzxvq-Lg53d{TW@#Fl&S+4HX zYEVLc$6@#=f4W`eykZAMOZ&F%?!9|N@Tdb&Eb2Noy1oVX`*v(|v=%N#EUnQ6Qj9X3 zqYMG7E*SO|dL>>dH)jZb$N}BhDzSR1o%;w*J(=B--%~HFH-X9KIcNfUPP)^|8kwhm0$G!?u7qSjVDpOHQ*Nclgyr%HE zXj0n^$+Kg}Vi0xzAJ+z(t3n6@GawsX>o^8Id>T|>Q;`m>4@zz}$H&Lu0^~paK@)vo zV`GEroxlX{&6PbHJdeVGk8o_k8q$z>K-M~TJPFeNYZV(jlSyMQXO3yeZ8M=R@A1hA zAYoAERp9CT=h1X4qhb=^qQ@nc591C1fBgPBGdK5C08}+Apj4@16JJskk6a7TwHn$x zr{>nM>ZtK`tlS<8I>>D&CqXza3euStEcJYm*eyU_J~F9NbzUd;=31MYQb3~@`D*qr zPY?)4XVW2jvJZwQ27(?ni=ex;=`nH4_P?vVeYP^@t{Ss*inqSUQ|?2?q_1!<=}C*C zcsMaN&!yO_v^b{5i84mjNs>+5=9JRc)f27>^r;Z3Is9?G`xl}b@5J$$AHU3^v)<|? z*H>q1SCukS(>3xn^Y*?eHq)U0A%NoX-bsRy{DPiQCswBE+XMl9HP;FV0LyATo(z**B!@SvVR zAFSzOqM)EK0(a#LR3WNwzfessrd&E@i1;u z3kt}9k(HW#hOyz?ivDBx5)m3_fpeqqr5yeW?Mh5c4wky)WW;Ly{r#X*<-1pb-FeM> zIkis2Wsn3w5#)5yXkbr|VZkzp-%ZvfB_*h!3b)>LxU;i!ZRwVb)-(GDN0WEsK75D( zOb_V#{ojY0nzw#P(A*nnNiW&Rf!cQ6cmkt=36u@MxRR(W%IqguxqQP46{_9f2y?(mZcp)se73?zp+R8H|)WAiDl}0V1ckzWa(0v>?=W zhE0RU%NpZKGILpzuHdtAa{9qBI8d~Oh8BnJdwP22t@-~|sYvbYbG5OxZ3R(12t2YB zc)F+y2afYCY&yXH05P+Mr&w)aI!nR?Jz=hKzsC;gdKA!Z8VzNh1v*TSTOfE zS}B9b#EZ_~#9;#ik5JjrnN#>(PWTFH5T6mz=( znKM+(2YECAeu5GOa4eD6@87=?9(Tohc?CfrD2{>|NZd_>%=DyAPEHPmrEQ6uTU-3L z<6HST@pneJRwS&8;9C~g^2?4lE~^K*xVF}LmQ z`2pKQ;rYTcZ&%pX=%WfsbOFQ)=|TCsZEBiXo;!av{e410ldzOHvAkC9qgTzZE-Bdz zqDz5_qE8o*@}var1bO=V4R{6wsQBq zufGfT)+Sof;L7Brp{$%-YFb(Yn+wbeuS%UXNG^6>5bc11@s4`{Mef0xqIqK0K|ewXQmVF=3#nmBkHes{;jI0pa!HT4?jr$OZXB^^I&qsT7Hk&+1+h{*?08oEBDEHY$fB# zXcdWXQ0`H0-v)u32;uZ6fEb<}_Zq8*Mns(VI6K+H2W{H_+{MYYHCub>63-)N^Ahc2 zIR5zUl7WXWtoY^T+}S-y(caE|1fM*CefRENb1N%ZH8nC+)#~iLSCCjTqfL=3D009u9UL6;Ap@ABS4D}Y zew>_&%h@WyHAC$uzz2Kn=x0g1?wH+&+{yZ{2P!B;Md-aOt+OXeOK*^qlN*B4h2lR5 zzC+}Or&I@aVtc!y$YV`{dHZJv+p}n{z4=QTClc`PWUvq16b?p(bm$W>&r#mp#^}Z(z3Jf9_xYV2Km{C z4%)WrKN>_>cPW@u^C=VOH05-Fd%dk zoyU9Y)@6Hp`)St1xx>Y~U;1gp^}c3heea|f?AA3Ha9HT-hw%y>>&84_P&@cs2M_o_ z1rUIXMAm>#OhY=V2?#!cre;T(vZ#!f&hU#E@fIJExfUs7)<8`xm;l*GP+0iY z^D1aK2VT&^AuQ4eo+v-SC{)*$JlT%kUpz~vxqO)sAM@J%1ZJD9?QOwOosMp(vHHdh z&bJ-S6Y%hxH@HkCqBe&Q8;3`YtZic6y#IB>%E(eo5jf~^itUWk*I)y(nydA zF{Bb5b+om^HtB^dWAy%BB-@ZC{IiZa74W`k5Y-0{{LDVMwXlt!+(n~HjL~g&pzov1 zpuX|FJG2PSxEj#r;7Rbm*fuA>bjcV}N~mE0JiNWVeH~chyP9NtRE0rf5{ZelXac~p zWZ1Iqs(cuNpo+>uxNaF(0n98cMxd8X@2r13TxBiC96du~?+)b(4X?nY?LzwInBzB)0)c+Bo>sk&InH=3ko?%)`oz>4?F`!O zx$XuE*0(tv|IN>pl@-^ifJ;r*QBhGSUqYnBmNXwe3;+QEsfU10)Ag8>Tou`->vDhA z;l<#iQ&e?yUcvh<+S7#%#R%O97>5kBWq6kM5E!mLJzPTQYd64%xr_!0z#P1C{rZFI zcpV6VXJlY|A8+76vED14=;=Yi<*$0Z7#$zy$u@w`Ew`EULfQTJ zF~>8DYB0dAii^{QhlejLFE@dcWe?OyVpd0AKLXt+U~+hQc}+)FC@r9S9X^p`V?P%v z6-H%qdCg{#v#!!CuzuKxpke5ExcX;1z!uMTOlR3OPk*1eu?#v79RD^kGn;_|EB8FQ zfhGks4Ll!mrNigSfWil&3?6hVR7V{5*t%c!i+fGF%L+>jF zO%_Yee}r=?SU$at)PEdAAY6pN?1i;@CG17AQ>Z@e36%)yzduwo?M*ubyc17YTOy~( zd5|?i-n!HPefMnNgA;1gtVfZDfE6N^m#u#F_lKDIS6ai72XGEsBu_U;#P&uVA@{*NkDiI za6geC>s2+3tgyc_ugR2_>SGofoMUcbA#G#B0|G@m91kZW>%ABZpqdjtx&{wgriqCO zkuwLpy6iU9`liT#u>g-lS~9ZVKVX%by$N6P1;>+;k=+H}AW{t=bt6Opv?er3gYp@E zo4cl_EMPOSREQy5FhlAa*NWZ1&f$`HTFHy+?#{^nUT zew5#*o~!iH(a|mZ{R<~i3={2JSX!!*B)!p~O4PplvuB*J=AhnK9m@3W?mBZl&aXFr zub~@&+JGCqEo@@}1dj@r39k&C>_%u* zM46eHflx6m)MlfB-gVh+l&G2p2VL56GJ`xz1Rj~oXzwyKrhF#Od#6-`5D)*C*MQY!q?r-k;T!;CW zs_yce7y)(z+Xk}PConW%(|%fKl|W+J2^FLTrw@q5VsPK4-pgk%*>PyCT&9)NP^ zP-HTjoQ%qz`qHpLat2GRkb`%!VjWy3jYbXj$|Fhc+2m9ld~NvDYx={MkY^uNlnHs6 z)KjePh|VOB8VnUFjxs5Zr-p{Yu-}&<$3b1dl#4N0)AEqKiHXUqzpATR{lLM4%RpGG ztE-d5E4Vso`2EOHyNg}1K51oLrxe55k;e~Sb74V4(mvlK!S@Gi7}rYTePc7y5F z7{UHvPD`+{##-MZHTWDb$d#ee@cI_Z%V3C-g-VY^jN-lf_j&D8x$oANEs4Jm^i*CE zXsm%hlIOs<-TsadXPZt3sDu?L{_^ED!d48b=i!J}0Cfq6Q!F%7xS;#)25^GFfXWfRr&;| zMT8q9#T`aGxTv+Jcr%G+G zw2V(lfqp9ovY=>YT4FnJ=-@%3S&__`w%n&_Ct!7Kd%GXO;V_DP^CGo3ybWB$;NajY z7QQnL;!`Z3(thukOj|P(leo~mks4?3dF84gI&!JyHhF-%oL)MJ{+yjH_LcF^%@G>H zpGCp~Ad`r4LwYkDZjM?BKM}zQH!c^8V+64oindRz&O5nreyp!=Ha?%caPi{#bmbi1 zLH6xbzC~%1=9(=#n|drlI2P-`$9{ox2|K#G8OqXR-(!9H?ZEy99xuXCLG1;6x6F0` zphNu7HOF)!jrO_NDBb}#h>m>1D;Gh}=a`;&?cf5KK|!m*Ka;R-dPc@vtW(6s$uWd* zS;7`f1Rq?holb+F@68X0TanCG2@J|XvVTUvZ7hPf8528T^NUcA<1aTJr%HgX!m zd=Qi)Y8AO3h<`TFUC3E!uxI`Cusn+L@bDmV7UpRZjKQ#N7}jA2!`zwBJ#cQE!7}oj zo*aKXMA9K>G#Ks;Xd`uQ$wmS5X4MfD2|*%sx)l7Ff3iCLIugn$+6k9 zXBEnicy!o2xxdOw5jOebFh8ZGr2$3+hl{|UB1cDpQ?MgVBua>50AwfZWV&fN`Wyx6 zaMq^GT6(TK3w|;5`xqF*5qsQy;8KPp`8T*R>pY-xSVKpk0TBZGKR>^5+I+>(z_)w7 z*baTPA#=|wtQ_%8*M@WjH%FcX6f$}+$rFI%gK}DiszT9d;j+a&M$8n)JqJelqLH|s zijwqH&DQkqKOcuHS|IpQ&pM>VoKo7jT18swrL`nSZL#H{wOBMIZAc+wigE#BN={az zHX&iq)YL?>W{F8j%k=c*e0_cK@F1}Psa{A*ih&z!+xG4DeT7e+Xkj*c4?E@f1S`>f^J{GmhYAOsC@G}(ub=@0`y}CeXMA-n#RVU@MsXb(<%wW*nT;&@WLAj0v8icVcJ}-;D*6j0PIh*ifa5(2CCT|K z+h9>3g$vsQ;qUqj<%QjOL41AwaQWMh8}FNzd0t^;bgU96cqeO&DQVG)iD?{+7#t{X ziJVK~e6WjB%40ze4rg3iA&K8}II0sF_AkNqAKLx_Y#;i!z_w?Rk~Un3s3|~uPXTd| zddeNO^ed60ltT5cBjI_+)D3Jb01C>?>3ZRnzD!+!$w##9FMM{ohTSdM77e<7>cwF= z+03E5g03n#+Fy>Qm#yn2c_692>U)PM0Ux^r5?+?@-lb9rl+pTrr(Z~U=t84idr--6tE@5&&*r_tO=Wx2f7XnSvyK61s*Ks;>CqHqIRj=ym>~A(F_L6 z8N3MhtjCYPb`?R?CXqVmON8pkFDxV(GrvceEQn&=dUWh-LRx&{*<_4ly*oNONa?h& zu&^DIHfMvCxX8v8Fh3WAHkT@HPmyy7(C3YTF20qSnGWp)BmaZQku|`MHJMQ}lf%wY ze&<2BEdl+a4I!6HiDd92b9MHtM~@m#-ggMZEJpQ6Dy-kpMnz))rL@0<)*$ zIUy)~BYRhpkjn|}O;ExymhY*8&t(y~UnZP6Fx@C4c!tJ2C*osl1e~WN07Sh(?4pF2 z1)s_;Eyf8Y*sVICyQ^SZWH{I@=`A^D-GZunK~`tnW`@_kZh%CPCEg!X9l1!R;+5Q!uY!)_3%XEJ2z*m7!u_sTo=kOnc#^w7xBbu=G{Z9$h}(D zQ4N0Hu3Zbl;ZPv#53q1?ZEbDOO$FD=9hX^qQyAi2=fwq@s>J(`wGt262_pxQ#4&l= zDmmEM*%hN@;v^ODhcO`TKG;VQ#>aVG%G0LxTjNe;_4Tpgx>77xJzpRY?8X#h4oh}p z|4crU21F`S4$V#%DptSZJ|(@42R-{h~*5MbFss63trWfZdnk&2qpZa zNVL(&$VkR=kxtlGsZ1{Sl&LR{2PL1aJm>4PHVBa2IgrlSso_Uz|Byp?M1hJ6$zwU_)xz<=?!|GyP^QUC014$Mfc ZKYRT}p_BDP{9prRzh$ej%#n}J= literal 0 HcmV?d00001 diff --git a/docs/images/socks-tls-2.png b/docs/images/socks-tls-2.png new file mode 100644 index 0000000000000000000000000000000000000000..2dd9c0fc747cb8772dd1cff2ee9c6a6cdac0900d GIT binary patch literal 19518 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 R4E6>kFMD66K+5Rpe*-TwttkKi literal 0 HcmV?d00001 diff --git a/docs/images/socks-tls-3.png b/docs/images/socks-tls-3.png new file mode 100644 index 0000000000000000000000000000000000000000..5fd78f96bbb72e352c56eec274555f98b0186aa6 GIT binary patch literal 25257 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