package socks5 import ( "bytes" "errors" "io" "log" "net" "os" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/net/proxy" "git.tcp.direct/kayos/go-socks5/statute" ) func TestSOCKS5_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 }() lAddr := l.Addr().(*net.TCPAddr) // Create a socks server with UserPass auth. cator := UserPassAuthenticator{StaticCredentials{"foo": "bar"}} srv := NewServer( WithAuthMethods([]Authenticator{cator}), WithLogger(NewLogger(log.New(os.Stdout, "socks5: ", log.LstdFlags))), ) // Start listening go func() { err := srv.ListenAndServe("tcp", "127.0.0.1:12365") require.NoError(t, err) }() time.Sleep(10 * time.Millisecond) // Get a local conn conn, err := net.Dial("tcp", "127.0.0.1:12365") require.NoError(t, err) // Connect, auth and connec to local req := bytes.NewBuffer( []byte{ statute.VersionSocks5, 2, statute.MethodNoAuth, statute.MethodUserPassAuth, // methods statute.UserPassAuthVersion, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r', // userpass auth }) reqHead := statute.Request{ Version: statute.VersionSocks5, Command: statute.CommandConnect, Reserved: 0, DstAddr: statute.AddrSpec{ FQDN: "", IP: net.ParseIP("127.0.0.1"), Port: lAddr.Port, AddrType: statute.ATYPIPv4, }, } req.Write(reqHead.Bytes()) // Send a ping req.Write([]byte("ping")) // Send all the bytes conn.Write(req.Bytes()) // nolint: errcheck // Verify response expected := []byte{ statute.VersionSocks5, statute.MethodUserPassAuth, // response use UserPass auth statute.UserPassAuthVersion, statute.AuthSuccess, // response auth success } rspHead := statute.Request{ Version: statute.VersionSocks5, Command: statute.RepSuccess, Reserved: 0, DstAddr: statute.AddrSpec{ FQDN: "", IP: net.ParseIP("127.0.0.1"), Port: 0, AddrType: statute.ATYPIPv4, }, } expected = append(expected, rspHead.Bytes()...) expected = append(expected, []byte("pong")...) out := make([]byte, len(expected)) conn.SetDeadline(time.Now().Add(time.Second)) // nolint: errcheck _, err = io.ReadFull(conn, out) conn.SetDeadline(time.Time{}) // nolint: errcheck require.NoError(t, err) // Ignore the port out[12] = 0 out[13] = 0 assert.Equal(t, expected, out) } func TestSOCKS5_Associate(t *testing.T) { locIP := net.ParseIP("127.0.0.1") // Create a local listener lAddr := &net.UDPAddr{IP: locIP, Port: 12399} 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 := UserPassAuthenticator{StaticCredentials{"foo": "bar"}} proxySrv := NewServer( WithAuthMethods([]Authenticator{cator}), WithLogger(NewLogger(log.New(os.Stdout, "socks5: ", log.LstdFlags))), ) // Start listening go func() { err := proxySrv.ListenAndServe("tcp", "127.0.0.1:12355") require.NoError(t, err) }() time.Sleep(10 * time.Millisecond) // Get a local conn conn, err := net.Dial("tcp", "127.0.0.1:12355") require.NoError(t, err) // Connect, auth and connec to local req := bytes.NewBuffer( []byte{ statute.VersionSocks5, 2, statute.MethodNoAuth, statute.MethodUserPassAuth, statute.UserPassAuthVersion, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r', }) reqHead := statute.Request{ Version: statute.VersionSocks5, Command: statute.CommandAssociate, Reserved: 0, DstAddr: statute.AddrSpec{ FQDN: "", IP: locIP, Port: lAddr.Port, AddrType: statute.ATYPIPv4, }, } req.Write(reqHead.Bytes()) // Send all the bytes conn.Write(req.Bytes()) // nolint: errcheck // Verify response expected := []byte{ statute.VersionSocks5, statute.MethodUserPassAuth, // use user password auth statute.UserPassAuthVersion, statute.AuthSuccess, // response auth success } out := make([]byte, len(expected)) 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, expected, out) rspHead, err := statute.ParseReply(conn) require.NoError(t, err) require.Equal(t, statute.VersionSocks5, rspHead.Version) require.Equal(t, statute.RepSuccess, rspHead.Response) // t.Logf("proxy bind listen port: %d", rspHead.BndAddr.Port) udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: locIP, Port: rspHead.BndAddr.Port, }) require.NoError(t, err) // Send a ping udpConn.Write(append([]byte{0, 0, 0, statute.ATYPIPv4, 0, 0, 0, 0, 0, 0}, []byte("ping")...)) // nolint: errcheck response := make([]byte, 1024) n, _, err := udpConn.ReadFrom(response) require.NoError(t, err) assert.Equal(t, []byte("pong"), response[n-4:n]) time.Sleep(time.Second * 1) } func Test_SocksWithProxy(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) require.Equal(t, []byte("ping"), buf) conn.Write([]byte("pong")) // nolint: errcheck }() lAddr := l.Addr().(*net.TCPAddr) // Create a socks server with UserPass auth. cator := UserPassAuthenticator{StaticCredentials{"foo": "bar"}} serv := NewServer( WithAuthMethods([]Authenticator{cator}), WithLogger(NewLogger(log.New(os.Stdout, "socks5: ", log.LstdFlags))), ) // Start socks server go func() { err := serv.ListenAndServe("tcp", "127.0.0.1:12395") require.NoError(t, err) }() time.Sleep(10 * time.Millisecond) // client dial, err := proxy.SOCKS5("tcp", "127.0.0.1:12395", &proxy.Auth{User: "foo", Password: "bar"}, proxy.Direct) require.NoError(t, err) // Connect, auth and connect to local conn, err := dial.Dial("tcp", lAddr.String()) require.NoError(t, err) // Send a ping 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) require.Equal(t, []byte("pong"), out) } /***************************** auth *******************************/ func TestNoAuth_Server(t *testing.T) { req := bytes.NewBuffer(nil) rsp := new(bytes.Buffer) s := NewServer(WithAuthMethods([]Authenticator{&NoAuthAuthenticator{}})) ctx, err := s.authenticate(rsp, req, "", []byte{statute.MethodNoAuth}) require.NoError(t, err) assert.Equal(t, statute.MethodNoAuth, ctx.Method) assert.Equal(t, []byte{statute.VersionSocks5, statute.MethodNoAuth}, rsp.Bytes()) } func TestPasswordAuth_Valid_Server(t *testing.T) { req := bytes.NewBuffer([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'}) rsp := new(bytes.Buffer) cator := UserPassAuthenticator{ StaticCredentials{"foo": "bar"}, } s := NewServer(WithAuthMethods([]Authenticator{cator})) ctx, err := s.authenticate(rsp, req, "", []byte{statute.MethodUserPassAuth}) require.NoError(t, err) assert.Equal(t, statute.MethodUserPassAuth, ctx.Method) val, ok := ctx.Payload["username"] require.True(t, ok) require.Equal(t, "foo", val) val, ok = ctx.Payload["password"] require.True(t, ok) require.Equal(t, "bar", val) assert.Equal(t, []byte{statute.VersionSocks5, statute.MethodUserPassAuth, 1, statute.AuthSuccess}, rsp.Bytes()) } func TestPasswordAuth_Invalid_Server(t *testing.T) { req := bytes.NewBuffer([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'z'}) rsp := new(bytes.Buffer) cator := UserPassAuthenticator{ StaticCredentials{"foo": "bar"}, } s := NewServer(WithAuthMethods([]Authenticator{cator})) ctx, err := s.authenticate(rsp, req, "", []byte{statute.MethodNoAuth, statute.MethodUserPassAuth}) require.True(t, errors.Is(err, statute.ErrUserAuthFailed)) require.Nil(t, ctx) assert.Equal(t, []byte{statute.VersionSocks5, statute.MethodUserPassAuth, 1, statute.AuthFailure}, rsp.Bytes()) } func TestNoSupportedAuth_Server(t *testing.T) { req := bytes.NewBuffer(nil) rsp := new(bytes.Buffer) cator := UserPassAuthenticator{ StaticCredentials{"foo": "bar"}, } s := NewServer(WithAuthMethods([]Authenticator{cator})) ctx, err := s.authenticate(rsp, req, "", []byte{statute.MethodNoAuth}) require.True(t, errors.Is(err, statute.ErrNoSupportedAuth)) require.Nil(t, ctx) assert.Equal(t, []byte{statute.VersionSocks5, statute.MethodNoAcceptable}, rsp.Bytes()) }