From f2e9d8a4e004e1bdd5c0396b8ad9edf0dfc8572b Mon Sep 17 00:00:00 2001 From: Darien Raymond Date: Sun, 8 Jan 2017 01:06:35 +0100 Subject: [PATCH] socks client --- common/protocol/server_spec.go | 9 +- proxy/shadowsocks/client.go | 4 +- proxy/socks/client.go | 125 ++++++++++++++++ proxy/socks/protocol.go | 202 ++++++++++++++++++++++--- proxy/socks/server.go | 6 - proxy/socks/socks.go | 12 ++ testing/scenarios/socks_test.go | 254 ++++++++++++++++++++++++++++++++ 7 files changed, 583 insertions(+), 29 deletions(-) create mode 100644 proxy/socks/client.go create mode 100644 proxy/socks/socks.go create mode 100644 testing/scenarios/socks_test.go diff --git a/common/protocol/server_spec.go b/common/protocol/server_spec.go index d8444bf86..583dbd224 100644 --- a/common/protocol/server_spec.go +++ b/common/protocol/server_spec.go @@ -97,7 +97,14 @@ func (v *ServerSpec) AddUser(user *User) { func (v *ServerSpec) PickUser() *User { userCount := len(v.users) - return v.users[dice.Roll(userCount)] + switch userCount { + case 0: + return nil + case 1: + return v.users[0] + default: + return v.users[dice.Roll(userCount)] + } } func (v *ServerSpec) IsValid() bool { diff --git a/proxy/shadowsocks/client.go b/proxy/shadowsocks/client.go index ca2efc7b3..81d9513c8 100644 --- a/proxy/shadowsocks/client.go +++ b/proxy/shadowsocks/client.go @@ -166,13 +166,13 @@ func (v *Client) Dispatch(destination v2net.Destination, ray ray.OutboundRay) { type ClientFactory struct{} // StreamCapability implements OutboundHandlerFactory.StreamCapability(). -func (v *ClientFactory) StreamCapability() v2net.NetworkList { +func (ClientFactory) StreamCapability() v2net.NetworkList { return v2net.NetworkList{ Network: []v2net.Network{v2net.Network_TCP}, } } // Create implements OutboundHandlerFactory.Create(). -func (v *ClientFactory) Create(space app.Space, rawConfig interface{}, meta *proxy.OutboundHandlerMeta) (proxy.OutboundHandler, error) { +func (ClientFactory) Create(space app.Space, rawConfig interface{}, meta *proxy.OutboundHandlerMeta) (proxy.OutboundHandler, error) { return NewClient(rawConfig.(*ClientConfig), space, meta) } diff --git a/proxy/socks/client.go b/proxy/socks/client.go new file mode 100644 index 000000000..73b39276e --- /dev/null +++ b/proxy/socks/client.go @@ -0,0 +1,125 @@ +package socks + +import ( + "v2ray.com/core/app" + "v2ray.com/core/common/buf" + "v2ray.com/core/common/log" + "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/retry" + "v2ray.com/core/common/signal" + "v2ray.com/core/proxy" + "v2ray.com/core/transport/internet" + "v2ray.com/core/transport/ray" +) + +type Client struct { + serverPicker protocol.ServerPicker + meta *proxy.OutboundHandlerMeta +} + +func NewClient(config *ClientConfig, space app.Space, meta *proxy.OutboundHandlerMeta) (*Client, error) { + serverList := protocol.NewServerList() + for _, rec := range config.Server { + serverList.AddServer(protocol.NewServerSpecFromPB(*rec)) + } + client := &Client{ + serverPicker: protocol.NewRoundRobinServerPicker(serverList), + meta: meta, + } + + return client, nil +} + +func (c *Client) Dispatch(destination net.Destination, ray ray.OutboundRay) { + var server *protocol.ServerSpec + var conn internet.Connection + + err := retry.ExponentialBackoff(5, 100).On(func() error { + server = c.serverPicker.PickServer() + dest := server.Destination() + rawConn, err := internet.Dial(c.meta.Address, dest, c.meta.GetDialerOptions()) + if err != nil { + return err + } + conn = rawConn + + return nil + }) + + if err != nil { + log.Warning("Socks|Client: Failed to find an available destination.") + return + } + + defer conn.Close() + conn.SetReusable(false) + + request := &protocol.RequestHeader{ + Version: socks5Version, + Command: protocol.RequestCommandTCP, + Address: destination.Address, + Port: destination.Port, + } + if destination.Network == net.Network_UDP { + request.Command = protocol.RequestCommandUDP + } + + user := server.PickUser() + if user != nil { + request.User = user + } + + udpRequest, err := ClientHandshake(request, conn, conn) + if err != nil { + log.Warning("Socks|Client: Failed to establish connection to server: ", err) + return + } + + var requestFunc func() error + var responseFunc func() error + if request.Command == protocol.RequestCommandTCP { + requestFunc = func() error { + defer ray.OutboundInput().ForceClose() + return buf.PipeUntilEOF(ray.OutboundInput(), buf.NewWriter(conn)) + } + responseFunc = func() error { + defer ray.OutboundOutput().Close() + return buf.PipeUntilEOF(buf.NewReader(conn), ray.OutboundOutput()) + } + } else if request.Command == protocol.RequestCommandUDP { + udpConn, err := internet.Dial(c.meta.Address, udpRequest.Destination(), c.meta.GetDialerOptions()) + if err != nil { + log.Info("Socks|Client: Failed to create UDP connection: ", err) + return + } + defer udpConn.Close() + requestFunc = func() error { + defer ray.OutboundInput().ForceClose() + return buf.PipeUntilEOF(ray.OutboundInput(), &UDPWriter{request: request, writer: udpConn}) + } + responseFunc = func() error { + defer ray.OutboundOutput().Close() + reader := &UDPReader{reader: net.NewTimeOutReader(16, udpConn)} + return buf.PipeUntilEOF(reader, ray.OutboundOutput()) + } + } + + requestDone := signal.ExecuteAsync(requestFunc) + responseDone := signal.ExecuteAsync(responseFunc) + if err := signal.ErrorOrFinish2(requestDone, responseDone); err != nil { + log.Info("Socks|Client: Connection ends with ", err) + } +} + +type ClientFactory struct{} + +func (ClientFactory) StreamCapability() net.NetworkList { + return net.NetworkList{ + Network: []net.Network{net.Network_TCP}, + } +} + +func (ClientFactory) Create(space app.Space, rawConfig interface{}, meta *proxy.OutboundHandlerMeta) (proxy.OutboundHandler, error) { + return NewClient(rawConfig.(*ClientConfig), space, meta) +} diff --git a/proxy/socks/protocol.go b/proxy/socks/protocol.go index dc34edc0f..31f2db4f6 100644 --- a/proxy/socks/protocol.go +++ b/proxy/socks/protocol.go @@ -3,6 +3,8 @@ package socks import ( "io" + "fmt" + "v2ray.com/core/common/buf" "v2ray.com/core/common/errors" v2net "v2ray.com/core/common/net" @@ -90,7 +92,7 @@ func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol } var expectedAuth byte = authNotRequired - if len(s.config.Accounts) > 0 { + if s.config.AuthType == AuthType_PASSWORD { expectedAuth = authPassword } @@ -99,21 +101,27 @@ func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol return nil, errors.New("Socks|Server: No matching auth method.") } + if err := writeSocks5AuthenticationResponse(writer, expectedAuth); err != nil { + return nil, err + } + + fmt.Println("s a") if expectedAuth == authPassword { username, password, err := readUsernamePassword(reader) if err != nil { return nil, errors.Base(err).Message("Socks|Server: Failed to read username or password.") } + fmt.Println("s b") if !s.config.HasAccount(username, password) { writeSocks5AuthenticationResponse(writer, 0xFF) return nil, errors.Base(err).Message("Socks|Server: Invalid username or password.") } - } - if err := writeSocks5AuthenticationResponse(writer, 0x00); err != nil { - return nil, err + if err := writeSocks5AuthenticationResponse(writer, 0x00); err != nil { + return nil, err + } } - + fmt.Println("s c") buffer.Clear() if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil { return nil, err @@ -194,21 +202,27 @@ func readUsernamePassword(reader io.Reader) (string, string, error) { return "", "", err } nUsername := int(buffer.Byte(1)) + fmt.Println("s username", nUsername) buffer.Clear() if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, nUsername)); err != nil { return "", "", err } username := buffer.String() + fmt.Println("s username", username) + buffer.Clear() if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil { return "", "", err } nPassword := int(buffer.Byte(0)) + fmt.Println("s pwd", nPassword) + buffer.Clear() if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, nPassword)); err != nil { return "", "", err } password := buffer.String() + fmt.Println("s pwd", password) return username, password, nil } @@ -244,9 +258,7 @@ func writeSocks5AuthenticationResponse(writer io.Writer, auth byte) error { return err } -func writeSocks5Response(writer io.Writer, errCode byte, address v2net.Address, port v2net.Port) error { - buffer := buf.NewLocal(64) - buffer.AppendBytes(socks5Version, errCode, 0x00 /* reserved */) +func appendAddress(buffer *buf.Buffer, address v2net.Address, port v2net.Port) { switch address.Family() { case v2net.AddressFamilyIPv4: buffer.AppendBytes(0x01) @@ -259,6 +271,12 @@ func writeSocks5Response(writer io.Writer, errCode byte, address v2net.Address, buffer.AppendSupplier(serial.WriteString(address.Domain())) } buffer.AppendSupplier(serial.WriteUint16(port.Value())) +} + +func writeSocks5Response(writer io.Writer, errCode byte, address v2net.Address, port v2net.Port) error { + buffer := buf.NewLocal(64) + buffer.AppendBytes(socks5Version, errCode, 0x00 /* reserved */) + appendAddress(buffer, address, port) _, err := writer.Write(buffer.Bytes()) return err @@ -326,18 +344,162 @@ func DecodeUDPPacket(packet []byte) (*protocol.RequestHeader, []byte, error) { func EncodeUDPPacket(request *protocol.RequestHeader, data []byte) *buf.Buffer { b := buf.NewSmall() b.AppendBytes(0, 0, 0 /* Fragment */) - switch request.Address.Family() { - case v2net.AddressFamilyIPv4: - b.AppendBytes(addrTypeIPv4) - b.Append(request.Address.IP()) - case v2net.AddressFamilyIPv6: - b.AppendBytes(addrTypeIPv6) - b.Append(request.Address.IP()) - case v2net.AddressFamilyDomain: - b.AppendBytes(addrTypeDomain, byte(len(request.Address.Domain()))) - b.AppendSupplier(serial.WriteString(request.Address.Domain())) - } - b.AppendSupplier(serial.WriteUint16(request.Port.Value())) + appendAddress(b, request.Address, request.Port) b.Append(data) return b } + +type UDPReader struct { + reader io.Reader +} + +func (r *UDPReader) Read() (*buf.Buffer, error) { + b := buf.NewSmall() + if err := b.AppendSupplier(buf.ReadFrom(r.reader)); err != nil { + return nil, err + } + _, data, err := DecodeUDPPacket(b.Bytes()) + if err != nil { + return nil, err + } + b.Clear() + b.Append(data) + return b, nil +} + +type UDPWriter struct { + request *protocol.RequestHeader + writer io.Writer +} + +func (w *UDPWriter) Write(b *buf.Buffer) error { + eb := EncodeUDPPacket(w.request, b.Bytes()) + b.Release() + defer eb.Release() + if _, err := w.writer.Write(eb.Bytes()); err != nil { + return err + } + return nil +} + +func ClientHandshake(request *protocol.RequestHeader, reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) { + authByte := byte(authNotRequired) + if request.User != nil { + authByte = byte(authPassword) + } + authRequest := []byte{socks5Version, 0x01, authByte} + if _, err := writer.Write(authRequest); err != nil { + return nil, err + } + + b := buf.NewLocal(64) + if err := b.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil { + return nil, err + } + + if b.Byte(0) != socks5Version { + return nil, errors.New("Socks|Client: Unexpected server version: ", b.Byte(0)) + } + if b.Byte(1) != authByte { + return nil, errors.New("Socks|Client: auth method not supported.") + } + + if authByte == authPassword { + rawAccount, err := request.User.GetTypedAccount() + if err != nil { + return nil, err + } + account := rawAccount.(*Account) + + fmt.Println("c username", account.Username) + fmt.Println("c pwd", account.Password) + b.Clear() + b.AppendBytes(socks5Version, byte(len(account.Username))) + b.Append([]byte(account.Username)) + b.AppendBytes(byte(len(account.Password))) + b.Append([]byte(account.Password)) + fmt.Println("c a") + if _, err := writer.Write(b.Bytes()); err != nil { + return nil, err + } + fmt.Println("c b") + b.Clear() + if err := b.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil { + return nil, err + } + fmt.Println("c c") + if b.Byte(1) != 0x00 { + return nil, errors.New("Socks|Client: Server rejects account: ", b.Byte(1)) + } + } + + b.Clear() + + command := byte(cmdTCPConnect) + if request.Command == protocol.RequestCommandUDP { + command = byte(cmdUDPPort) + } + b.AppendBytes(socks5Version, command, 0x00 /* reserved */) + appendAddress(b, request.Address, request.Port) + fmt.Println("c e") + if _, err := writer.Write(b.Bytes()); err != nil { + return nil, err + } + + b.Clear() + fmt.Println("c f") + if err := b.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil { + return nil, err + } + + resp := b.Byte(1) + if resp != 0x00 { + return nil, errors.New("Socks|Client: Server rejects request: ", resp) + } + + addrType := b.Byte(3) + + b.Clear() + + var address v2net.Address + switch addrType { + case addrTypeIPv4: + if err := b.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil { + return nil, err + } + address = v2net.IPAddress(b.Bytes()) + case addrTypeIPv6: + if err := b.AppendSupplier(buf.ReadFullFrom(reader, 16)); err != nil { + return nil, err + } + address = v2net.IPAddress(b.Bytes()) + case addrTypeDomain: + if err := b.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil { + return nil, err + } + domainLength := int(b.Byte(0)) + if err := b.AppendSupplier(buf.ReadFullFrom(reader, domainLength)); err != nil { + return nil, err + } + address = v2net.DomainAddress(string(b.BytesFrom(-domainLength))) + default: + return nil, errors.New("Socks|Server: Unknown address type: ", addrType) + } + + if err := b.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil { + return nil, err + } + port := v2net.PortFromBytes(b.BytesFrom(-2)) + + if request.Command == protocol.RequestCommandUDP { + udpRequest := &protocol.RequestHeader{ + Version: socks5Version, + Command: protocol.RequestCommandUDP, + Address: address, + Port: port, + } + return udpRequest, nil + } + + return nil, nil +} diff --git a/proxy/socks/server.go b/proxy/socks/server.go index cc36614aa..a62456f98 100644 --- a/proxy/socks/server.go +++ b/proxy/socks/server.go @@ -7,14 +7,12 @@ import ( "v2ray.com/core/app" "v2ray.com/core/app/dispatcher" - "v2ray.com/core/common" "v2ray.com/core/common/buf" "v2ray.com/core/common/bufio" "v2ray.com/core/common/errors" "v2ray.com/core/common/log" v2net "v2ray.com/core/common/net" "v2ray.com/core/common/protocol" - "v2ray.com/core/common/serial" "v2ray.com/core/common/signal" "v2ray.com/core/proxy" "v2ray.com/core/transport/internet" @@ -193,7 +191,3 @@ func (v *ServerFactory) StreamCapability() v2net.NetworkList { func (v *ServerFactory) Create(space app.Space, rawConfig interface{}, meta *proxy.InboundHandlerMeta) (proxy.InboundHandler, error) { return NewServer(rawConfig.(*ServerConfig), space, meta), nil } - -func init() { - common.Must(proxy.RegisterInboundHandlerCreator(serial.GetMessageType(new(ServerConfig)), new(ServerFactory))) -} diff --git a/proxy/socks/socks.go b/proxy/socks/socks.go new file mode 100644 index 000000000..e89d90f53 --- /dev/null +++ b/proxy/socks/socks.go @@ -0,0 +1,12 @@ +package socks + +import ( + "v2ray.com/core/common" + "v2ray.com/core/common/serial" + "v2ray.com/core/proxy" +) + +func init() { + common.Must(proxy.RegisterOutboundHandlerCreator(serial.GetMessageType((*ClientConfig)(nil)), new(ClientFactory))) + common.Must(proxy.RegisterInboundHandlerCreator(serial.GetMessageType((*ServerConfig)(nil)), new(ServerFactory))) +} diff --git a/testing/scenarios/socks_test.go b/testing/scenarios/socks_test.go new file mode 100644 index 000000000..648893536 --- /dev/null +++ b/testing/scenarios/socks_test.go @@ -0,0 +1,254 @@ +package scenarios + +import ( + "net" + "testing" + + socks1 "h12.me/socks" + "v2ray.com/core" + v2net "v2ray.com/core/common/net" + "v2ray.com/core/common/protocol" + "v2ray.com/core/common/serial" + "v2ray.com/core/proxy/dokodemo" + "v2ray.com/core/proxy/freedom" + "v2ray.com/core/proxy/socks" + "v2ray.com/core/testing/assert" + "v2ray.com/core/testing/servers/tcp" + "v2ray.com/core/testing/servers/udp" +) + +func TestSocksBridgeTCP(t *testing.T) { + assert := assert.On(t) + + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + assert.Error(err).IsNil() + defer tcpServer.Close() + + serverPort := pickPort() + serverConfig := &core.Config{ + Inbound: []*core.InboundConnectionConfig{ + { + PortRange: v2net.SinglePortRange(serverPort), + ListenOn: v2net.NewIPOrDomain(v2net.LocalHostIP), + Settings: serial.ToTypedMessage(&socks.ServerConfig{ + AuthType: socks.AuthType_PASSWORD, + Accounts: map[string]string{ + "Test Account": "Test Password", + }, + Address: v2net.NewIPOrDomain(v2net.LocalHostIP), + UdpEnabled: false, + }), + }, + }, + Outbound: []*core.OutboundConnectionConfig{ + { + Settings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := pickPort() + clientConfig := &core.Config{ + Inbound: []*core.InboundConnectionConfig{ + { + PortRange: v2net.SinglePortRange(clientPort), + ListenOn: v2net.NewIPOrDomain(v2net.LocalHostIP), + Settings: serial.ToTypedMessage(&dokodemo.Config{ + Address: v2net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &v2net.NetworkList{ + Network: []v2net.Network{v2net.Network_TCP}, + }, + }), + }, + }, + Outbound: []*core.OutboundConnectionConfig{ + { + Settings: serial.ToTypedMessage(&socks.ClientConfig{ + Server: []*protocol.ServerEndpoint{ + { + Address: v2net.NewIPOrDomain(v2net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&socks.Account{ + Username: "Test Account", + Password: "Test Password", + }), + }, + }, + }, + }, + }), + }, + }, + } + + assert.Error(InitializeServerConfig(serverConfig)).IsNil() + assert.Error(InitializeServerConfig(clientConfig)).IsNil() + + conn, err := net.DialTCP("tcp", nil, &net.TCPAddr{ + IP: []byte{127, 0, 0, 1}, + Port: int(clientPort), + }) + assert.Error(err).IsNil() + + payload := "test payload" + nBytes, err := conn.Write([]byte(payload)) + assert.Error(err).IsNil() + assert.Int(nBytes).Equals(len(payload)) + + response := make([]byte, 1024) + nBytes, err = conn.Read(response) + assert.Error(err).IsNil() + assert.Bytes(response[:nBytes]).Equals(xor([]byte(payload))) + assert.Error(conn.Close()).IsNil() + + CloseAllServers() +} + +func TestSocks4(t *testing.T) { + assert := assert.On(t) + + tcpServer := tcp.Server{ + MsgProcessor: xor, + } + dest, err := tcpServer.Start() + assert.Error(err).IsNil() + defer tcpServer.Close() + + serverPort := pickPort() + serverConfig := &core.Config{ + Inbound: []*core.InboundConnectionConfig{ + { + PortRange: v2net.SinglePortRange(serverPort), + ListenOn: v2net.NewIPOrDomain(v2net.LocalHostIP), + Settings: serial.ToTypedMessage(&socks.ServerConfig{ + AuthType: socks.AuthType_NO_AUTH, + Address: v2net.NewIPOrDomain(v2net.LocalHostIP), + UdpEnabled: false, + }), + }, + }, + Outbound: []*core.OutboundConnectionConfig{ + { + Settings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + assert.Error(InitializeServerConfig(serverConfig)).IsNil() + + dialer := socks1.DialSocksProxy(socks1.SOCKS4, v2net.TCPDestination(v2net.LocalHostIP, serverPort).NetAddr()) + conn, err := dialer("tcp", dest.NetAddr()) + assert.Error(err).IsNil() + + payload := "test payload" + nBytes, err := conn.Write([]byte(payload)) + assert.Error(err).IsNil() + assert.Int(nBytes).Equals(len(payload)) + + response := make([]byte, 1024) + nBytes, err = conn.Read(response) + assert.Error(err).IsNil() + assert.Bytes(response[:nBytes]).Equals(xor([]byte(payload))) + assert.Error(conn.Close()).IsNil() + + CloseAllServers() +} + +func TestSocksBridageUDP(t *testing.T) { + assert := assert.On(t) + + udpServer := udp.Server{ + MsgProcessor: xor, + } + dest, err := udpServer.Start() + assert.Error(err).IsNil() + defer udpServer.Close() + + serverPort := pickPort() + serverConfig := &core.Config{ + Inbound: []*core.InboundConnectionConfig{ + { + PortRange: v2net.SinglePortRange(serverPort), + ListenOn: v2net.NewIPOrDomain(v2net.LocalHostIP), + Settings: serial.ToTypedMessage(&socks.ServerConfig{ + AuthType: socks.AuthType_PASSWORD, + Accounts: map[string]string{ + "Test Account": "Test Password", + }, + Address: v2net.NewIPOrDomain(v2net.LocalHostIP), + UdpEnabled: true, + }), + }, + }, + Outbound: []*core.OutboundConnectionConfig{ + { + Settings: serial.ToTypedMessage(&freedom.Config{}), + }, + }, + } + + clientPort := pickPort() + clientConfig := &core.Config{ + Inbound: []*core.InboundConnectionConfig{ + { + PortRange: v2net.SinglePortRange(clientPort), + ListenOn: v2net.NewIPOrDomain(v2net.LocalHostIP), + Settings: serial.ToTypedMessage(&dokodemo.Config{ + Address: v2net.NewIPOrDomain(dest.Address), + Port: uint32(dest.Port), + NetworkList: &v2net.NetworkList{ + Network: []v2net.Network{v2net.Network_TCP, v2net.Network_UDP}, + }, + }), + }, + }, + Outbound: []*core.OutboundConnectionConfig{ + { + Settings: serial.ToTypedMessage(&socks.ClientConfig{ + Server: []*protocol.ServerEndpoint{ + { + Address: v2net.NewIPOrDomain(v2net.LocalHostIP), + Port: uint32(serverPort), + User: []*protocol.User{ + { + Account: serial.ToTypedMessage(&socks.Account{ + Username: "Test Account", + Password: "Test Password", + }), + }, + }, + }, + }, + }), + }, + }, + } + + assert.Error(InitializeServerConfig(serverConfig)).IsNil() + assert.Error(InitializeServerConfig(clientConfig)).IsNil() + + conn, err := net.DialUDP("udp", nil, &net.UDPAddr{ + IP: []byte{127, 0, 0, 1}, + Port: int(clientPort), + }) + assert.Error(err).IsNil() + + payload := "dokodemo request." + nBytes, err := conn.Write([]byte(payload)) + assert.Error(err).IsNil() + assert.Int(nBytes).Equals(len(payload)) + + response := make([]byte, 1024) + nBytes, err = conn.Read(response) + assert.Error(err).IsNil() + assert.Bytes(response[:nBytes]).Equals(xor([]byte(payload))) + assert.Error(conn.Close()).IsNil() + + CloseAllServers() +}