1
0
Эх сурвалжийг харах

Add vmess compatibility test

世界 3 жил өмнө
parent
commit
3fb011712b

+ 6 - 2
common/badjson/json.go

@@ -17,12 +17,16 @@ func decodeJSON(decoder *json.Decoder) (any, error) {
 		case '{':
 			var object JSONObject
 			err = object.decodeJSON(decoder)
+			if err != nil {
+				return nil, err
+			}
+			rawToken, err = decoder.Token()
 			if err != nil {
 				return nil, err
 			} else if rawToken != json.Delim('}') {
 				return nil, E.New("excepted object end, but got ", rawToken)
 			}
-			return object, nil
+			return &object, nil
 		case '[':
 			var array JSONArray[any]
 			err = array.decodeJSON(decoder)
@@ -35,7 +39,7 @@ func decodeJSON(decoder *json.Decoder) (any, error) {
 			} else if rawToken != json.Delim(']') {
 				return nil, E.New("excepted array end, but got ", rawToken)
 			}
-			return &array, nil
+			return array, nil
 		default:
 			return nil, E.New("excepted object or array end: ", token)
 		}

+ 16 - 2
test/box_test.go

@@ -54,11 +54,25 @@ func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
 	}
 	t.Run("tcp", func(t *testing.T) {
 		t.Parallel()
-		require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
+		var err error
+		for retry := 0; retry < 3; retry++ {
+			err = testLargeDataWithConn(t, testPort, dialTCP)
+			if err == nil {
+				break
+			}
+		}
+		require.NoError(t, err)
 	})
 	t.Run("udp", func(t *testing.T) {
 		t.Parallel()
-		require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
+		var err error
+		for retry := 0; retry < 3; retry++ {
+			err = testLargeDataWithPacketConn(t, testPort, dialUDP)
+			if err == nil {
+				break
+			}
+		}
+		require.NoError(t, err)
 	})
 	// require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
 	// require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))

+ 14 - 5
test/clash_test.go

@@ -14,6 +14,7 @@ import (
 	"time"
 
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing/common/control"
 	F "github.com/sagernet/sing/common/format"
 
 	"github.com/docker/docker/api/types"
@@ -27,11 +28,13 @@ import (
 const (
 	ImageShadowsocksRustServer = "ghcr.io/shadowsocks/ssserver-rust:latest"
 	ImageShadowsocksRustClient = "ghcr.io/shadowsocks/sslocal-rust:latest"
+	ImageV2RayCore             = "v2fly/v2fly-core:latest"
 )
 
 var allImages = []string{
 	ImageShadowsocksRustServer,
 	ImageShadowsocksRustClient,
+	ImageV2RayCore,
 }
 
 var (
@@ -200,7 +203,9 @@ func testPingPongWithConn(t *testing.T, port uint16, cc func() (net.Conn, error)
 
 func testPingPongWithPacketConn(t *testing.T, port uint16, pcc func() (net.PacketConn, error)) error {
 	l, err := listenPacket("udp", ":"+F.ToString(port))
-	require.NoError(t, err)
+	if err != nil {
+		return err
+	}
 	defer l.Close()
 
 	rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: int(port)}
@@ -345,7 +350,9 @@ func testLargeDataWithConn(t *testing.T, port uint16, cc func() (net.Conn, error
 
 func testLargeDataWithPacketConn(t *testing.T, port uint16, pcc func() (net.PacketConn, error)) error {
 	l, err := listenPacket("udp", ":"+F.ToString(port))
-	require.NoError(t, err)
+	if err != nil {
+		return err
+	}
 	defer l.Close()
 
 	rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: int(port)}
@@ -467,8 +474,8 @@ func testPacketConnTimeout(t *testing.T, pcc func() (net.PacketConn, error)) err
 }
 
 func listen(network, address string) (net.Listener, error) {
-	lc := net.ListenConfig{}
-
+	var lc net.ListenConfig
+	lc.Control = control.ReuseAddr()
 	var lastErr error
 	for i := 0; i < 5; i++ {
 		l, err := lc.Listen(context.Background(), network, address)
@@ -483,9 +490,11 @@ func listen(network, address string) (net.Listener, error) {
 }
 
 func listenPacket(network, address string) (net.PacketConn, error) {
+	var lc net.ListenConfig
+	lc.Control = control.ReuseAddr()
 	var lastErr error
 	for i := 0; i < 5; i++ {
-		l, err := net.ListenPacket(network, address)
+		l, err := lc.ListenPacket(context.Background(), network, address)
 		if err == nil {
 			return l, nil
 		}

+ 37 - 0
test/config/vmess-client.json

@@ -0,0 +1,37 @@
+{
+  "log": {
+    "loglevel": "debug"
+  },
+  "inbounds": [
+    {
+      "listen": "127.0.0.1",
+      "port": "1080",
+      "protocol": "socks",
+      "settings": {
+        "auth": "noauth",
+        "udp": true,
+        "ip": "127.0.0.1"
+      }
+    }
+  ],
+  "outbounds": [
+    {
+      "protocol": "vmess",
+      "settings": {
+        "vnext": [
+          {
+            "address": "127.0.0.1",
+            "port": 1234,
+            "users": [
+              {
+                "id": "",
+                "security": "",
+                "experiments": ""
+              }
+            ]
+          }
+        ]
+      }
+    }
+  ]
+}

+ 25 - 0
test/config/vmess-server.json

@@ -0,0 +1,25 @@
+{
+  "log": {
+    "loglevel": "debug"
+  },
+  "inbounds": [
+    {
+      "listen": "0.0.0.0",
+      "port": 1234,
+      "protocol": "vmess",
+      "settings": {
+        "clients": [
+          {
+            "id": "b831381d-6324-4d53-ad4f-8cda48b30811",
+            "alterId": 0
+          }
+        ]
+      }
+    }
+  ],
+  "outbounds": [
+    {
+      "protocol": "freedom"
+    }
+  ]
+}

+ 30 - 3
test/docker_test.go

@@ -21,6 +21,7 @@ type DockerOptions struct {
 	Cmd        []string
 	Env        []string
 	Bind       []string
+	Stdin      []byte
 }
 
 func startDockerContainer(t *testing.T, options DockerOptions) {
@@ -28,9 +29,19 @@ func startDockerContainer(t *testing.T, options DockerOptions) {
 	require.NoError(t, err)
 	defer dockerClient.Close()
 
+	writeStdin := len(options.Stdin) > 0
+
 	var containerOptions container.Config
+
+	if writeStdin {
+		containerOptions.OpenStdin = true
+		containerOptions.StdinOnce = true
+	}
+
 	containerOptions.Image = options.Image
-	containerOptions.Entrypoint = []string{options.EntryPoint}
+	if options.EntryPoint != "" {
+		containerOptions.Entrypoint = []string{options.EntryPoint}
+	}
 	containerOptions.Cmd = options.Cmd
 	containerOptions.Env = options.Env
 	containerOptions.ExposedPorts = make(nat.PortSet)
@@ -57,13 +68,29 @@ func startDockerContainer(t *testing.T, options DockerOptions) {
 	t.Cleanup(func() {
 		cleanContainer(dockerContainer.ID)
 	})
+
 	require.NoError(t, dockerClient.ContainerStart(context.Background(), dockerContainer.ID, types.ContainerStartOptions{}))
+
+	if writeStdin {
+		stdinAttach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, types.ContainerAttachOptions{
+			Stdin:  writeStdin,
+			Stream: true,
+		})
+		require.NoError(t, err)
+		_, err = stdinAttach.Conn.Write(options.Stdin)
+		require.NoError(t, err)
+		stdinAttach.Close()
+	}
+
 	/*attach, err := dockerClient.ContainerAttach(context.Background(), dockerContainer.ID, types.ContainerAttachOptions{
-		Logs: true, Stream: true, Stdout: true, Stderr: true,
+		Stdout: true,
+		Stderr: true,
+		Logs:   true,
+		Stream: true,
 	})
 	require.NoError(t, err)
 	go func() {
-		attach.Reader.WriteTo(os.Stderr)
+		stdcopy.StdCopy(os.Stderr, os.Stderr, attach.Reader)
 	}()*/
 	time.Sleep(time.Second)
 }

+ 1 - 0
test/go.mod

@@ -11,6 +11,7 @@ require (
 	github.com/docker/go-connections v0.4.0
 	github.com/gofrs/uuid v4.2.0+incompatible
 	github.com/sagernet/sing v0.0.0-20220718035659-3d74b823ed56
+	github.com/spyzhov/ajson v0.7.1
 	github.com/stretchr/testify v1.8.0
 	golang.org/x/net v0.0.0-20220708220712-1185a9018129
 )

+ 2 - 0
test/go.sum

@@ -68,6 +68,8 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
 github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spyzhov/ajson v0.7.1 h1:1MDIlPc6x0zjNtpa7tDzRAyFAvRX+X8ZsvtYz5lZg6A=
+github.com/spyzhov/ajson v0.7.1/go.mod h1:63V+CGM6f1Bu/p4nLIN8885ojBdt88TbLoSFzyqMuVA=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=

+ 188 - 20
test/vmess_test.go

@@ -2,64 +2,232 @@ package main
 
 import (
 	"net/netip"
+	"os"
 	"testing"
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 
 	"github.com/gofrs/uuid"
+	"github.com/spyzhov/ajson"
 	"github.com/stretchr/testify/require"
 )
 
-func TestVMessSelf(t *testing.T) {
+func TestVMess(t *testing.T) {
 	t.Parallel()
 	for _, security := range []string{
 		"zero",
 	} {
 		t.Run(security, func(t *testing.T) {
-			testVMessSelf0(t, security)
+			testVMess0(t, security)
 		})
 	}
 	for _, security := range []string{
 		"aes-128-gcm", "chacha20-poly1305", "aes-128-cfb",
 	} {
 		t.Run(security, func(t *testing.T) {
-			testVMessSelf1(t, security)
+			testVMess1(t, security)
 		})
 	}
 }
 
-func testVMessSelf0(t *testing.T, security string) {
+func testVMess0(t *testing.T, security string) {
 	t.Parallel()
 	user, err := uuid.DefaultGenerator.NewV4()
 	require.NoError(t, err)
-	t.Run("default", func(t *testing.T) {
-		testVMessSelf2(t, security, user.String(), false, false)
+	t.Run("self", func(t *testing.T) {
+		testVMessSelf(t, security, user, false, false)
 	})
-	t.Run("padding", func(t *testing.T) {
-		testVMessSelf2(t, security, user.String(), true, false)
+	t.Run("self-padding", func(t *testing.T) {
+		testVMessSelf(t, security, user, true, false)
+	})
+	t.Run("outbound", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
+	})
+	t.Run("outbound-padding", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, true, false, 0)
+	})
+	t.Run("outbound-legacy", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, false, false, 1)
+	})
+	t.Run("outbound-legacy-padding", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, true, false, 1)
 	})
 }
 
-func testVMessSelf1(t *testing.T, security string) {
+func testVMess1(t *testing.T, security string) {
 	t.Parallel()
 	user, err := uuid.DefaultGenerator.NewV4()
 	require.NoError(t, err)
-	t.Run("default", func(t *testing.T) {
-		testVMessSelf2(t, security, user.String(), false, false)
+	t.Run("self", func(t *testing.T) {
+		testVMessSelf(t, security, user, false, false)
+	})
+	t.Run("self-padding", func(t *testing.T) {
+		testVMessSelf(t, security, user, true, false)
+	})
+	t.Run("self-authid", func(t *testing.T) {
+		testVMessSelf(t, security, user, false, true)
+	})
+	t.Run("self-padding-authid", func(t *testing.T) {
+		testVMessSelf(t, security, user, true, true)
+	})
+	t.Run("inbound", func(t *testing.T) {
+		testVMessInboundWithV2Ray(t, security, user, false)
+	})
+	t.Run("inbound-authid", func(t *testing.T) {
+		testVMessInboundWithV2Ray(t, security, user, true)
+	})
+	t.Run("outbound", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, false, false, 0)
+	})
+	t.Run("outbound-padding", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, true, false, 0)
+	})
+	t.Run("outbound-authid", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, false, true, 0)
+	})
+	t.Run("outbound-padding-authid", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, true, true, 0)
 	})
-	t.Run("padding", func(t *testing.T) {
-		testVMessSelf2(t, security, user.String(), true, false)
+	t.Run("outbound-legacy", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, false, false, 1)
 	})
-	t.Run("authid", func(t *testing.T) {
-		testVMessSelf2(t, security, user.String(), false, true)
+	t.Run("outbound-legacy-padding", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, true, false, 1)
 	})
-	t.Run("padding-authid", func(t *testing.T) {
-		testVMessSelf2(t, security, user.String(), true, true)
+	t.Run("outbound-legacy-authid", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, false, true, 1)
 	})
+	t.Run("outbound-legacy-padding-authid", func(t *testing.T) {
+		testVMessOutboundWithV2Ray(t, security, user, true, true, 1)
+	})
+}
+
+func testVMessInboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, authenticatedLength bool) {
+	t.Parallel()
+
+	content, err := os.ReadFile("config/vmess-client.json")
+	require.NoError(t, err)
+	config, err := ajson.Unmarshal(content)
+	require.NoError(t, err)
+
+	serverPort := mkPort(t)
+	clientPort := mkPort(t)
+	testPort := mkPort(t)
+
+	config.MustKey("inbounds").MustIndex(0).MustKey("port").SetNumeric(float64(clientPort))
+	outbound := config.MustKey("outbounds").MustIndex(0).MustKey("settings").MustKey("vnext").MustIndex(0)
+	outbound.MustKey("port").SetNumeric(float64(serverPort))
+	user := outbound.MustKey("users").MustIndex(0)
+	user.MustKey("id").SetString(uuid.String())
+	user.MustKey("security").SetString(security)
+	var experiments string
+	if authenticatedLength {
+		experiments += "AuthenticatedLength"
+	}
+	user.MustKey("experiments").SetString(experiments)
+
+	content, err = ajson.Marshal(config)
+	require.NoError(t, err)
+
+	startDockerContainer(t, DockerOptions{
+		Image:      ImageV2RayCore,
+		Ports:      []uint16{serverPort, testPort},
+		EntryPoint: "v2ray",
+		Stdin:      content,
+		Env:        []string{"V2RAY_VMESS_AEAD_FORCED=false"},
+	})
+
+	startInstance(t, option.Options{
+		Log: &option.LogOption{
+			Level: "error",
+		},
+		Inbounds: []option.Inbound{
+			{
+				Type: C.TypeVMess,
+				VMessOptions: option.VMessInboundOptions{
+					ListenOptions: option.ListenOptions{
+						Listen:     option.ListenAddress(netip.IPv4Unspecified()),
+						ListenPort: serverPort,
+					},
+					Users: []option.VMessUser{
+						{
+							Name: "sekai",
+							UUID: uuid.String(),
+						},
+					},
+				},
+			},
+		},
+	})
+
+	testSuit(t, clientPort, testPort)
+}
+
+func testVMessOutboundWithV2Ray(t *testing.T, security string, uuid uuid.UUID, globalPadding bool, authenticatedLength bool, alterId int) {
+	t.Parallel()
+
+	content, err := os.ReadFile("config/vmess-server.json")
+	require.NoError(t, err)
+	config, err := ajson.Unmarshal(content)
+	require.NoError(t, err)
+
+	serverPort := mkPort(t)
+	clientPort := mkPort(t)
+	testPort := mkPort(t)
+
+	inbound := config.MustKey("inbounds").MustIndex(0)
+	inbound.MustKey("port").SetNumeric(float64(serverPort))
+	inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(uuid.String())
+	inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("alterId").SetNumeric(float64(alterId))
+
+	content, err = ajson.Marshal(config)
+	require.NoError(t, err)
+
+	startDockerContainer(t, DockerOptions{
+		Image:      ImageV2RayCore,
+		Ports:      []uint16{serverPort, testPort},
+		EntryPoint: "v2ray",
+		Stdin:      content,
+		Env:        []string{"V2RAY_VMESS_AEAD_FORCED=false"},
+	})
+
+	startInstance(t, option.Options{
+		Log: &option.LogOption{
+			Level: "error",
+		},
+		Inbounds: []option.Inbound{
+			{
+				Type: C.TypeMixed,
+				MixedOptions: option.HTTPMixedInboundOptions{
+					ListenOptions: option.ListenOptions{
+						Listen:     option.ListenAddress(netip.IPv4Unspecified()),
+						ListenPort: clientPort,
+					},
+				},
+			},
+		},
+		Outbounds: []option.Outbound{
+			{
+				Type: C.TypeVMess,
+				VMessOptions: option.VMessOutboundOptions{
+					ServerOptions: option.ServerOptions{
+						Server:     "127.0.0.1",
+						ServerPort: serverPort,
+					},
+					Security:            security,
+					UUID:                uuid.String(),
+					GlobalPadding:       globalPadding,
+					AuthenticatedLength: authenticatedLength,
+					AlterId:             alterId,
+				},
+			},
+		},
+	})
+	testSuit(t, clientPort, testPort)
 }
 
-func testVMessSelf2(t *testing.T, security string, uuid string, globalPadding bool, authenticatedLength bool) {
+func testVMessSelf(t *testing.T, security string, uuid uuid.UUID, globalPadding bool, authenticatedLength bool) {
 	t.Parallel()
 	serverPort := mkPort(t)
 	clientPort := mkPort(t)
@@ -89,7 +257,7 @@ func testVMessSelf2(t *testing.T, security string, uuid string, globalPadding bo
 					Users: []option.VMessUser{
 						{
 							Name: "sekai",
-							UUID: uuid,
+							UUID: uuid.String(),
 						},
 					},
 				},
@@ -108,7 +276,7 @@ func testVMessSelf2(t *testing.T, security string, uuid string, globalPadding bo
 						ServerPort: serverPort,
 					},
 					Security:            security,
-					UUID:                uuid,
+					UUID:                uuid.String(),
 					GlobalPadding:       globalPadding,
 					AuthenticatedLength: authenticatedLength,
 				},