diff --git a/README.md b/README.md index b15462c..bf285bf 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ go-socks5 [![License](https://img.shields.io/github/license/thinkgos/go-socks5)](https://github.com/thinkgos/go-socks5/raw/master/LICENSE) [![Tag](https://img.shields.io/github/v/tag/thinkgos/go-socks5)](https://github.com/thinkgos/go-socks5/tags) -Provides the `socks5` package that implements a [SOCKS5 server](http://en.wikipedia.org/wiki/SOCKS). +Provides the `socks5` package that implements a [SOCKS5](http://en.wikipedia.org/wiki/SOCKS). SOCKS (Secure Sockets) is used to route traffic between a client and server through an intermediate proxy layer. This can be used to bypass firewalls or NATs. @@ -17,22 +17,24 @@ Feature ======= The package has the following features: -* Unit tests -* "No Auth" mode -* User/Password authentication optional user addr limit -* Support for the CONNECT command -* Support for the ASSOCIATE command -* Rules to do granular filtering of commands -* Custom DNS resolution -* Custom goroutine pool -* buffer pool design and optional custom buffer pool -* Custom logger +- Support client(**under ccsocks5 directory**) and server(**under root directory**) +- Support TCP/UDP and IPv4/IPv6 +- Unit tests +- "No Auth" mode +- User/Password authentication optional user addr limit +- Support for the CONNECT command +- Support for the ASSOCIATE command +- Rules to do granular filtering of commands +- Custom DNS resolution +- Custom goroutine pool +- buffer pool design and optional custom buffer pool +- Custom logger TODO ==== The package still needs the following: -* Support for the BIND command +- Support for the BIND command Example ======= diff --git a/_testing/associate/main.go b/_example/associate/main.go similarity index 100% rename from _testing/associate/main.go rename to _example/associate/main.go diff --git a/_testing/connect/main.go b/_example/connect/main.go similarity index 100% rename from _testing/connect/main.go rename to _example/connect/main.go diff --git a/ccsocks5/client.go b/ccsocks5/client.go index 8fe90bc..30edd8b 100644 --- a/ccsocks5/client.go +++ b/ccsocks5/client.go @@ -22,7 +22,8 @@ type Client struct { bufferPool bufferpool.BufPool } -// NewClient This is just create a client, you need to use Dial to create conn. +// NewClient This is just create a client. +// you need to use Dial to create conn. func NewClient(proxyAddr string, opts ...Option) (*Client, error) { c := &Client{ proxyAddr: proxyAddr, @@ -37,7 +38,9 @@ func NewClient(proxyAddr string, opts ...Option) (*Client, error) { // Close closes the connection. func (sf *Client) Close() (err error) { - err = sf.tcpConn.Close() + if sf.tcpConn != nil { + err = sf.tcpConn.Close() + } if sf.Conn != nil { err = sf.Conn.Close() } diff --git a/server.go b/server.go index e96feef..910cba3 100644 --- a/server.go +++ b/server.go @@ -106,7 +106,7 @@ func (sf *Server) Serve(l net.Listener) error { } sf.submit(func() { if err := sf.ServeConn(conn); err != nil { - sf.logger.Errorf("server conn %v", err) + sf.logger.Errorf("server: %v", err) } }) } diff --git a/testing/socks5_test.go b/testing/socks5_test.go new file mode 100644 index 0000000..b778679 --- /dev/null +++ b/testing/socks5_test.go @@ -0,0 +1,128 @@ +package testing + +import ( + "io" + "log" + "net" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/net/proxy" + + "github.com/thinkgos/go-socks5" + "github.com/thinkgos/go-socks5/bufferpool" + "github.com/thinkgos/go-socks5/ccsocks5" +) + +func Test_Socks5_Connect(t *testing.T) { + // Create a local listener + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + go func() { + conn, err := l.Accept() + require.NoError(t, err) + defer conn.Close() + + buf := make([]byte, 4) + _, err = io.ReadAtLeast(conn, buf, 4) + require.NoError(t, err) + assert.Equal(t, []byte("ping"), buf) + + conn.Write([]byte("pong")) // nolint: errcheck + }() + + // Create a socks server with UserPass auth. + cator := socks5.UserPassAuthenticator{Credentials: socks5.StaticCredentials{"user": "pass"}} + srv := socks5.NewServer( + socks5.WithAuthMethods([]socks5.Authenticator{cator}), + socks5.WithLogger(socks5.NewLogger(log.New(os.Stdout, "socks5: ", log.LstdFlags))), + ) + + // Start listening + go func() { + err := srv.ListenAndServe("tcp", "127.0.0.1:12389") + require.NoError(t, err) + }() + time.Sleep(10 * time.Millisecond) + + // Get a local conn + client, err := ccsocks5.NewClient("127.0.0.1:12389", + ccsocks5.WithAuth(&proxy.Auth{User: "user", Password: "pass"}), + ccsocks5.WithBufferPool(bufferpool.NewPool(32*1024)), + ) + require.NoError(t, err) + + conn, err := client.Dial("tcp", l.Addr().String()) + require.NoError(t, err) + defer conn.Close() + // Send all the bytes + conn.Write([]byte("ping")) // nolint: errcheck + + out := make([]byte, 4) + conn.SetDeadline(time.Now().Add(time.Second)) // nolint: errcheck + _, err = io.ReadFull(conn, out) + conn.SetDeadline(time.Time{}) // nolint: errcheck + require.NoError(t, err) + assert.Equal(t, []byte("pong"), out) +} + +func Test_socks5_Associate(t *testing.T) { + locIP := net.ParseIP("127.0.0.1") + // Create a local listener + lAddr := &net.UDPAddr{IP: locIP, Port: 12312} + l, err := net.ListenUDP("udp", lAddr) + require.NoError(t, err) + defer l.Close() + + go func() { + buf := make([]byte, 2048) + for { + n, remote, err := l.ReadFrom(buf) + if err != nil { + return + } + require.Equal(t, []byte("ping"), buf[:n]) + + l.WriteTo([]byte("pong"), remote) // nolint: errcheck + } + }() + + // Create a socks server + cator := socks5.UserPassAuthenticator{Credentials: socks5.StaticCredentials{"user": "pass"}} + proxySrv := socks5.NewServer( + socks5.WithAuthMethods([]socks5.Authenticator{cator}), + socks5.WithLogger(socks5.NewLogger(log.New(os.Stdout, "socks5: ", log.LstdFlags))), + ) + // Start listening + go func() { + err := proxySrv.ListenAndServe("tcp", "127.0.0.1:12385") + require.NoError(t, err) + }() + time.Sleep(10 * time.Millisecond) + + // Get a local conn + client, err := ccsocks5.NewClient( + "127.0.0.1:12385", + ccsocks5.WithAuth(&proxy.Auth{User: "user", Password: "pass"}), + ) + require.NoError(t, err) + + conn, err := client.Dial("udp", lAddr.String()) + require.NoError(t, err) + + // send ping + conn.Write([]byte("ping")) // nolint: errcheck + + // read response + out := make([]byte, 4) + conn.SetDeadline(time.Now().Add(time.Second)) // nolint: errcheck + _, err = io.ReadFull(conn, out) + conn.SetDeadline(time.Time{}) // nolint: errcheck + require.NoError(t, err) + require.Equal(t, []byte("pong"), out) + time.Sleep(time.Second * 1) +}