Darien Raymond 8 سال پیش
والد
کامیت
f2e9d8a4e0
7فایلهای تغییر یافته به همراه583 افزوده شده و 29 حذف شده
  1. 8 1
      common/protocol/server_spec.go
  2. 2 2
      proxy/shadowsocks/client.go
  3. 125 0
      proxy/socks/client.go
  4. 182 20
      proxy/socks/protocol.go
  5. 0 6
      proxy/socks/server.go
  6. 12 0
      proxy/socks/socks.go
  7. 254 0
      testing/scenarios/socks_test.go

+ 8 - 1
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 {

+ 2 - 2
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)
 }

+ 125 - 0
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)
+}

+ 182 - 20
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
+}

+ 0 - 6
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)))
-}

+ 12 - 0
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)))
+}

+ 254 - 0
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()
+}