Browse Source

Add mux server and XUDP client for VMess

世界 3 years ago
parent
commit
63fc95b96d
13 changed files with 216 additions and 234 deletions
  1. 1 1
      go.mod
  2. 2 2
      go.sum
  3. 1 1
      option/vmess.go
  4. 2 1
      outbound/vless.go
  5. 12 2
      outbound/vmess.go
  6. 41 0
      test/config/vmess-mux-client.json
  7. 7 7
      test/go.mod
  8. 17 18
      test/go.sum
  9. 109 0
      test/mux_cool_test.go
  10. 1 1
      test/vmess_test.go
  11. 8 6
      transport/vless/client.go
  12. 15 21
      transport/vless/protocol.go
  13. 0 174
      transport/vless/xudp.go

+ 1 - 1
go.mod

@@ -27,7 +27,7 @@ require (
 	github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
 	github.com/sagernet/sing-tun v0.0.0-20220916073459-0032242c9617
-	github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12
+	github.com/sagernet/sing-vmess v0.0.0-20220917033734-9b634758039d
 	github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
 	github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
 	github.com/spf13/cobra v1.5.0

+ 2 - 2
go.sum

@@ -153,8 +153,8 @@ github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDe
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
 github.com/sagernet/sing-tun v0.0.0-20220916073459-0032242c9617 h1:fNTNmylhB/UjoBLusmrFu2B1fat4OCkDkQXTgrE7ZsE=
 github.com/sagernet/sing-tun v0.0.0-20220916073459-0032242c9617/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
-github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 h1:4HYGbTDDemgBVTmaspXbkgjJlXc3hYVjNxSddJndq8Y=
-github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
+github.com/sagernet/sing-vmess v0.0.0-20220917033734-9b634758039d h1:/GNWxSrQj4chFYk2jahIXNPdvUa9DW8IdgyqYFbq9oQ=
+github.com/sagernet/sing-vmess v0.0.0-20220917033734-9b634758039d/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=

+ 1 - 1
option/vmess.go

@@ -23,7 +23,7 @@ type VMessOutboundOptions struct {
 	AuthenticatedLength bool                   `json:"authenticated_length,omitempty"`
 	Network             NetworkList            `json:"network,omitempty"`
 	TLS                 *OutboundTLSOptions    `json:"tls,omitempty"`
-	PacketAddr          bool                   `json:"packet_addr,omitempty"`
+	PacketEncoding      string                 `json:"packet_encoding,omitempty"`
 	Multiplex           *MultiplexOptions      `json:"multiplex,omitempty"`
 	Transport           *V2RayTransportOptions `json:"transport,omitempty"`
 }

+ 2 - 1
outbound/vless.go

@@ -12,6 +12,7 @@ import (
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-box/transport/v2ray"
 	"github.com/sagernet/sing-box/transport/vless"
+	"github.com/sagernet/sing-dns"
 	"github.com/sagernet/sing-vmess/packetaddr"
 	"github.com/sagernet/sing/common"
 	E "github.com/sagernet/sing/common/exceptions"
@@ -131,7 +132,7 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
 	if h.xudp {
 		return h.client.DialXUDPPacketConn(conn, destination), nil
 	} else if h.packetAddr {
-		return packetaddr.NewConn(h.client.DialPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil
+		return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(h.client.DialPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination)), nil
 	} else {
 		return h.client.DialPacketConn(conn, destination), nil
 	}

+ 12 - 2
outbound/vmess.go

@@ -12,6 +12,7 @@ import (
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-box/transport/v2ray"
+	"github.com/sagernet/sing-dns"
 	"github.com/sagernet/sing-vmess"
 	"github.com/sagernet/sing-vmess/packetaddr"
 	"github.com/sagernet/sing/common"
@@ -31,6 +32,7 @@ type VMess struct {
 	tlsConfig       tls.Config
 	transport       adapter.V2RayClientTransport
 	packetAddr      bool
+	xudp            bool
 }
 
 func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) {
@@ -62,8 +64,14 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
 	if err != nil {
 		return nil, err
 	}
-	if outbound.multiplexDialer == nil && options.PacketAddr {
+	switch options.PacketEncoding {
+	case "":
+	case "packetaddr":
 		outbound.packetAddr = true
+	case "xudp":
+		outbound.xudp = true
+	default:
+		return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
 	}
 	var clientOptions []vmess.ClientOption
 	if options.GlobalPadding {
@@ -176,7 +184,9 @@ func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
 		return nil, err
 	}
 	if h.packetAddr {
-		return packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination), nil
+		return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination)), nil
+	} else if h.xudp {
+		return h.client.DialEarlyXUDPPacketConn(conn, destination), nil
 	} else {
 		return h.client.DialEarlyPacketConn(conn, destination), nil
 	}

+ 41 - 0
test/config/vmess-mux-client.json

@@ -0,0 +1,41 @@
+{
+  "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": "",
+                "alterId": 0,
+                "security": "none",
+                "experiments": ""
+              }
+            ]
+          }
+        ]
+      },
+      "mux": {
+        "enabled": true
+      }
+    }
+  ]
+}

+ 7 - 7
test/go.mod

@@ -10,7 +10,7 @@ require (
 	github.com/docker/docker v20.10.18+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/gofrs/uuid v4.3.0+incompatible
-	github.com/sagernet/sing v0.0.0-20220914045234-93cc53b60cee
+	github.com/sagernet/sing v0.0.0-20220916071326-834794b006ea
 	github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
 	github.com/spyzhov/ajson v0.7.1
 	github.com/stretchr/testify v1.8.0
@@ -21,6 +21,7 @@ require (
 
 require (
 	berty.tech/go-libtor v1.0.385 // indirect
+	github.com/Dreamacro/clash v1.11.8 // indirect
 	github.com/Microsoft/go-winio v0.5.1 // indirect
 	github.com/ajg/form v1.5.1 // indirect
 	github.com/andybalholm/brotli v1.0.4 // indirect
@@ -65,13 +66,12 @@ require (
 	github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
 	github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
 	github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb // indirect
-	github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 // indirect
-	github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8 // indirect
-	github.com/sagernet/sing-tun v0.0.0-20220914100102-057dd738a7f7 // indirect
-	github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 // indirect
+	github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b // indirect
+	github.com/sagernet/sing-tun v0.0.0-20220916073459-0032242c9617 // indirect
+	github.com/sagernet/sing-vmess v0.0.0-20220917033734-9b634758039d // indirect
 	github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 // indirect
 	github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e // indirect
-	github.com/sirupsen/logrus v1.8.1 // indirect
+	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
 	go.etcd.io/bbolt v1.3.6 // indirect
 	go.uber.org/atomic v1.10.0 // indirect
@@ -86,7 +86,7 @@ require (
 	golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
 	golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f // indirect
 	golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
-	golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0 // indirect
+	golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b // indirect
 	google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f // indirect
 	google.golang.org/grpc v1.49.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect

+ 17 - 18
test/go.sum

@@ -5,6 +5,8 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Dreamacro/clash v1.11.8 h1:t/sy3/tiihRlvV3SsliYFjj8rKpbLw5IJ2PymiHcwS8=
+github.com/Dreamacro/clash v1.11.8/go.mod h1:LsWCcJFoKuL1C5F2c0m/1690wihTHYSU3J+im09yTwQ=
 github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
 github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
 github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
@@ -105,8 +107,8 @@ github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8t
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
 github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
 github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
@@ -161,27 +163,25 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E
 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb h1:wc0yQ+SBn4TaTYRwpwvEm3nc4eRdxk6vtRbouLVZAzk=
 github.com/sagernet/quic-go v0.0.0-20220818150011-de611ab3e2bb/go.mod h1:MIccjRKnPTjWwAOpl+AUGWOkzyTd9tERytudxu+1ra4=
-github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0 h1:lQ4RFWY/wBYmXl/zJJCwQbhiEIbgEqC7j+nhZYkgwmU=
-github.com/sagernet/shadowsocksr v0.0.0-20220912092645-c9ab93f81bb0/go.mod h1:xSHLCsdgy5QIozXFEv6uDgMWzyrRdFFjr3TgL+juu6g=
 github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
 github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
-github.com/sagernet/sing v0.0.0-20220914045234-93cc53b60cee h1:+3w7+QWnhWi3Qz7+Xcais8zViHRUPIkmxq3eYZm/zvk=
-github.com/sagernet/sing v0.0.0-20220914045234-93cc53b60cee/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
-github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8 h1:Iyfl+Rm5jcDvXuy/jpOBI3eu35ujci50tkqYHHwwg+8=
-github.com/sagernet/sing-dns v0.0.0-20220913115644-aebff1dfbba8/go.mod h1:bPVnJ5gJ0WmUfN1bJP9Cis0ab8SSByx6JVzyLJjDMwA=
+github.com/sagernet/sing v0.0.0-20220916071326-834794b006ea h1:ZAWvZdeByPBBz3Vs+w3Erbh+DDo7D4biokoPhXl0nNU=
+github.com/sagernet/sing v0.0.0-20220916071326-834794b006ea/go.mod h1:x3NHUeJBQwV75L51zwmLKQdLtRvR+M4PmXkfQtU1vIY=
+github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b h1:cXCMNJ9heZ+c6l+qUcku60x9KyXo4SOAaJfg/6spOmU=
+github.com/sagernet/sing-dns v0.0.0-20220915084601-812e0864b45b/go.mod h1:SrvWLfOSlnFmH32CWXicfilAGgIXR0VjrH6yRbuXYww=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
 github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
-github.com/sagernet/sing-tun v0.0.0-20220914100102-057dd738a7f7 h1:zdvFDYMz8s0e9UmOxMk0wNGOKh64KfeWpx8UAbJJI60=
-github.com/sagernet/sing-tun v0.0.0-20220914100102-057dd738a7f7/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
-github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12 h1:4HYGbTDDemgBVTmaspXbkgjJlXc3hYVjNxSddJndq8Y=
-github.com/sagernet/sing-vmess v0.0.0-20220913015714-c4ab86d40e12/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
+github.com/sagernet/sing-tun v0.0.0-20220916073459-0032242c9617 h1:fNTNmylhB/UjoBLusmrFu2B1fat4OCkDkQXTgrE7ZsE=
+github.com/sagernet/sing-tun v0.0.0-20220916073459-0032242c9617/go.mod h1:5AhPUv9jWDQ3pv3Mj78SL/1TSjhoaj6WNASxRKLqXqM=
+github.com/sagernet/sing-vmess v0.0.0-20220917033734-9b634758039d h1:/GNWxSrQj4chFYk2jahIXNPdvUa9DW8IdgyqYFbq9oQ=
+github.com/sagernet/sing-vmess v0.0.0-20220917033734-9b634758039d/go.mod h1:u66Vv7NHXJWfeAmhh7JuJp/cwxmuQlM56QoZ7B7Mmd0=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
 github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
 github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 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=
@@ -218,7 +218,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM=
@@ -266,8 +265,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -295,10 +294,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220913120320-3275c407cedc h1:dpclq5m2YrqPGStKmtw7IcNbKLfbIqKXvNxDJKdIKYc=
 golang.org/x/sys v0.0.0-20220913120320-3275c407cedc/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -334,8 +333,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 h1:Ug9qvr1myri/zFN6xL17LSCBGFDnphBBhzmILHsM5TY=
 golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
-golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0 h1:5ZkdpbduT/g+9OtbSDvbF3KvfQG45CtH/ppO8FUmvCQ=
-golang.zx2c4.com/wireguard v0.0.0-20220904105730-b51010ba13f0/go.mod h1:enML0deDxY1ux+B6ANGiwtg0yAJi1rctkTpcHNAVPyg=
+golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b h1:qgrKnOfe1zyURRNdmDlGbN32i38Zjmw0B1+TMdHcOvg=
+golang.zx2c4.com/wireguard v0.0.0-20220829161405-d1d08426b27b/go.mod h1:6y4CqPAy54NwiN4nC8K+R1eMpQDB1P2d25qmunh2RSA=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=

+ 109 - 0
test/mux_cool_test.go

@@ -0,0 +1,109 @@
+package main
+
+import (
+	"net/netip"
+	"os"
+	"testing"
+
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/option"
+
+	"github.com/spyzhov/ajson"
+	"github.com/stretchr/testify/require"
+)
+
+func TestMuxCoolServer(t *testing.T) {
+	userId := newUUID()
+	content, err := os.ReadFile("config/vmess-mux-client.json")
+	require.NoError(t, err)
+	config, err := ajson.Unmarshal(content)
+	require.NoError(t, err)
+
+	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(userId.String())
+
+	content, err = ajson.Marshal(config)
+	require.NoError(t, err)
+
+	startDockerContainer(t, DockerOptions{
+		Image:      ImageV2RayCore,
+		Ports:      []uint16{serverPort, testPort},
+		EntryPoint: "v2ray",
+		Stdin:      content,
+	})
+
+	startInstance(t, option.Options{
+		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: userId.String(),
+						},
+					},
+				},
+			},
+		},
+	})
+
+	testSuit(t, clientPort, testPort)
+}
+
+func TestMuxCoolClient(t *testing.T) {
+	user := newUUID()
+	content, err := os.ReadFile("config/vmess-server.json")
+	require.NoError(t, err)
+	config, err := ajson.Unmarshal(content)
+	require.NoError(t, err)
+
+	inbound := config.MustKey("inbounds").MustIndex(0)
+	inbound.MustKey("port").SetNumeric(float64(serverPort))
+	inbound.MustKey("settings").MustKey("clients").MustIndex(0).MustKey("id").SetString(user.String())
+
+	content, err = ajson.Marshal(config)
+	require.NoError(t, err)
+
+	startDockerContainer(t, DockerOptions{
+		Image:      ImageXRayCore,
+		Ports:      []uint16{serverPort, testPort},
+		EntryPoint: "xray",
+		Stdin:      content,
+	})
+
+	startInstance(t, option.Options{
+		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,
+					},
+					UUID:           user.String(),
+					PacketEncoding: "xudp",
+				},
+			},
+		},
+	})
+	testSuit(t, clientPort, testPort)
+}

+ 1 - 1
test/vmess_test.go

@@ -306,7 +306,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo
 					AlterId:             alterId,
 					GlobalPadding:       globalPadding,
 					AuthenticatedLength: authenticatedLength,
-					PacketAddr:          packetAddr,
+					PacketEncoding:      "packetaddr",
 				},
 			},
 		},

+ 8 - 6
transport/vless/client.go

@@ -5,6 +5,7 @@ import (
 	"io"
 	"net"
 
+	"github.com/sagernet/sing-vmess"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/buf"
 	M "github.com/sagernet/sing/common/metadata"
@@ -25,20 +26,21 @@ func NewClient(userId string) (*Client, error) {
 }
 
 func (c *Client) DialEarlyConn(conn net.Conn, destination M.Socksaddr) *Conn {
-	return &Conn{Conn: conn, key: c.key, destination: destination}
+	return &Conn{Conn: conn, key: c.key, command: vmess.CommandTCP, destination: destination}
 }
 
 func (c *Client) DialPacketConn(conn net.Conn, destination M.Socksaddr) *PacketConn {
 	return &PacketConn{Conn: conn, key: c.key, destination: destination}
 }
 
-func (c *Client) DialXUDPPacketConn(conn net.Conn, destination M.Socksaddr) *XUDPConn {
-	return &XUDPConn{Conn: conn, key: c.key, destination: destination}
+func (c *Client) DialXUDPPacketConn(conn net.Conn, destination M.Socksaddr) vmess.PacketConn {
+	return vmess.NewXUDPConn(&Conn{Conn: conn, key: c.key, command: vmess.CommandMux, destination: destination}, destination)
 }
 
 type Conn struct {
 	net.Conn
 	key            []byte
+	command        byte
 	destination    M.Socksaddr
 	requestWritten bool
 	responseRead   bool
@@ -57,7 +59,7 @@ func (c *Conn) Read(b []byte) (n int, err error) {
 
 func (c *Conn) Write(b []byte) (n int, err error) {
 	if !c.requestWritten {
-		err = WriteRequest(c.Conn, Request{c.key, CommandTCP, c.destination}, b)
+		err = WriteRequest(c.Conn, Request{c.key, c.command, c.destination}, b)
 		if err == nil {
 			n = len(b)
 		}
@@ -100,7 +102,7 @@ func (c *PacketConn) Read(b []byte) (n int, err error) {
 
 func (c *PacketConn) Write(b []byte) (n int, err error) {
 	if !c.requestWritten {
-		err = WritePacketRequest(c.Conn, Request{c.key, CommandUDP, c.destination}, b)
+		err = WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination}, b)
 		if err == nil {
 			n = len(b)
 		}
@@ -119,7 +121,7 @@ func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) er
 	dataLen := buffer.Len()
 	binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(dataLen))
 	if !c.requestWritten {
-		err := WritePacketRequest(c.Conn, Request{c.key, CommandUDP, c.destination}, buffer.Bytes())
+		err := WritePacketRequest(c.Conn, Request{c.key, vmess.CommandUDP, c.destination}, buffer.Bytes())
 		c.requestWritten = true
 		return err
 	}

+ 15 - 21
transport/vless/protocol.go

@@ -4,6 +4,7 @@ import (
 	"encoding/binary"
 	"io"
 
+	"github.com/sagernet/sing-vmess"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/buf"
 	E "github.com/sagernet/sing/common/exceptions"
@@ -11,20 +12,7 @@ import (
 	"github.com/sagernet/sing/common/rw"
 )
 
-const (
-	Version    = 0
-	CommandTCP = 1
-	CommandUDP = 2
-	CommandMux = 3
-	NetworkUDP = 2
-)
-
-var AddressSerializer = M.NewSerializer(
-	M.AddressFamilyByte(0x01, M.AddressFamilyIPv4),
-	M.AddressFamilyByte(0x02, M.AddressFamilyFqdn),
-	M.AddressFamilyByte(0x03, M.AddressFamilyIPv6),
-	M.PortThenAddress(),
-)
+const Version = 0
 
 type Request struct {
 	UUID        []byte
@@ -38,7 +26,9 @@ func WriteRequest(writer io.Writer, request Request, payload []byte) error {
 	requestLen += 16 // uuid
 	requestLen += 1  // protobuf length
 	requestLen += 1  // command
-	requestLen += AddressSerializer.AddrPortLen(request.Destination)
+	if request.Command != vmess.CommandMux {
+		requestLen += vmess.AddressSerializer.AddrPortLen(request.Destination)
+	}
 	requestLen += len(payload)
 	_buffer := buf.StackNewSize(requestLen)
 	defer common.KeepAlive(_buffer)
@@ -48,10 +38,14 @@ func WriteRequest(writer io.Writer, request Request, payload []byte) error {
 		buffer.WriteByte(Version),
 		common.Error(buffer.Write(request.UUID)),
 		buffer.WriteByte(0),
-		buffer.WriteByte(CommandTCP),
-		AddressSerializer.WriteAddrPort(buffer, request.Destination),
-		common.Error(buffer.Write(payload)),
+		buffer.WriteByte(request.Command),
 	)
+
+	if request.Command != vmess.CommandMux {
+		common.Must(vmess.AddressSerializer.WriteAddrPort(buffer, request.Destination))
+	}
+
+	common.Must1(buffer.Write(payload))
 	return common.Error(writer.Write(buffer.Bytes()))
 }
 
@@ -61,7 +55,7 @@ func WritePacketRequest(writer io.Writer, request Request, payload []byte) error
 	requestLen += 16 // uuid
 	requestLen += 1  // protobuf length
 	requestLen += 1  // command
-	requestLen += AddressSerializer.AddrPortLen(request.Destination)
+	requestLen += vmess.AddressSerializer.AddrPortLen(request.Destination)
 	if len(payload) > 0 {
 		requestLen += 2
 		requestLen += len(payload)
@@ -74,8 +68,8 @@ func WritePacketRequest(writer io.Writer, request Request, payload []byte) error
 		buffer.WriteByte(Version),
 		common.Error(buffer.Write(request.UUID)),
 		buffer.WriteByte(0),
-		buffer.WriteByte(CommandUDP),
-		AddressSerializer.WriteAddrPort(buffer, request.Destination),
+		buffer.WriteByte(vmess.CommandUDP),
+		vmess.AddressSerializer.WriteAddrPort(buffer, request.Destination),
 		binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
 		common.Error(buffer.Write(payload)),
 	)

+ 0 - 174
transport/vless/xudp.go

@@ -1,174 +0,0 @@
-package vless
-
-import (
-	"encoding/binary"
-	"io"
-	"net"
-	"os"
-
-	"github.com/sagernet/sing/common"
-	"github.com/sagernet/sing/common/buf"
-	E "github.com/sagernet/sing/common/exceptions"
-	M "github.com/sagernet/sing/common/metadata"
-)
-
-type XUDPConn struct {
-	net.Conn
-	key            []byte
-	destination    M.Socksaddr
-	requestWritten bool
-	responseRead   bool
-}
-
-func (c *XUDPConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
-	return 0, nil, os.ErrInvalid
-}
-
-func (c *XUDPConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
-	start := buffer.Start()
-	if !c.responseRead {
-		err = ReadResponse(c.Conn)
-		if err != nil {
-			return
-		}
-		c.responseRead = true
-		buffer.FullReset()
-	}
-	_, err = buffer.ReadFullFrom(c.Conn, 6)
-	if err != nil {
-		return
-	}
-	var length uint16
-	err = binary.Read(buffer, binary.BigEndian, &length)
-	if err != nil {
-		return
-	}
-	header, err := buffer.ReadBytes(4)
-	if err != nil {
-		return
-	}
-	switch header[2] {
-	case 1:
-		// frame new
-		return M.Socksaddr{}, E.New("unexpected frame new")
-	case 2:
-		// frame keep
-		if length != 4 {
-			_, err = buffer.ReadFullFrom(c.Conn, int(length)-2)
-			if err != nil {
-				return
-			}
-			buffer.Advance(1)
-			destination, err = AddressSerializer.ReadAddrPort(buffer)
-			if err != nil {
-				return
-			}
-		} else {
-			_, err = buffer.ReadFullFrom(c.Conn, 2)
-			if err != nil {
-				return
-			}
-			destination = c.destination
-		}
-	case 3:
-		// frame end
-		return M.Socksaddr{}, io.EOF
-	case 4:
-		// frame keep alive
-	default:
-		return M.Socksaddr{}, E.New("unexpected frame: ", buffer.Byte(2))
-	}
-	// option error
-	if header[3]&2 == 2 {
-		return M.Socksaddr{}, E.Cause(net.ErrClosed, "remote closed")
-	}
-	// option data
-	if header[3]&1 != 1 {
-		buffer.Resize(start, 0)
-		return c.ReadPacket(buffer)
-	} else {
-		err = binary.Read(buffer, binary.BigEndian, &length)
-		if err != nil {
-			return
-		}
-		buffer.Resize(start, 0)
-		_, err = buffer.ReadFullFrom(c.Conn, int(length))
-		return
-	}
-}
-
-func (c *XUDPConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
-	destination := M.SocksaddrFromNet(addr)
-	headerLen := c.frontHeadroom(AddressSerializer.AddrPortLen(destination))
-	buffer := buf.NewSize(headerLen + len(p))
-	buffer.Advance(headerLen)
-	common.Must1(buffer.Write(p))
-	err = c.WritePacket(buffer, destination)
-	if err == nil {
-		n = len(p)
-	}
-	return
-}
-
-func (c *XUDPConn) frontHeadroom(addrLen int) int {
-	if !c.requestWritten {
-		var headerLen int
-		headerLen += 1  // version
-		headerLen += 16 // uuid
-		headerLen += 1  // protobuf length
-		headerLen += 1  // command
-		headerLen += 2  // frame len
-		headerLen += 5  // frame header
-		headerLen += addrLen
-		headerLen += 2 // payload len
-		return headerLen
-	} else {
-		return 7 + addrLen + 2
-	}
-}
-
-func (c *XUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
-	defer buffer.Release()
-	dataLen := buffer.Len()
-	addrLen := M.SocksaddrSerializer.AddrPortLen(destination)
-	if !c.requestWritten {
-		header := buf.With(buffer.ExtendHeader(c.frontHeadroom(addrLen)))
-		common.Must(
-			header.WriteByte(Version),
-			common.Error(header.Write(c.key)),
-			header.WriteByte(0),
-			header.WriteByte(CommandMux),
-			binary.Write(header, binary.BigEndian, uint16(5+addrLen)),
-			header.WriteByte(0),
-			header.WriteByte(0),
-			header.WriteByte(1), // frame type new
-			header.WriteByte(1), // option data
-			header.WriteByte(NetworkUDP),
-			AddressSerializer.WriteAddrPort(header, destination),
-			binary.Write(header, binary.BigEndian, uint16(dataLen)),
-		)
-		c.requestWritten = true
-	} else {
-		header := buffer.ExtendHeader(c.frontHeadroom(addrLen))
-		binary.BigEndian.PutUint16(header, uint16(5+addrLen))
-		header[2] = 0
-		header[3] = 0
-		header[4] = 2 // frame keep
-		header[5] = 1 // option data
-		header[6] = NetworkUDP
-		err := AddressSerializer.WriteAddrPort(buf.With(header[7:]), destination)
-		if err != nil {
-			return err
-		}
-		binary.BigEndian.PutUint16(header[7+addrLen:], uint16(dataLen))
-	}
-	return common.Error(c.Conn.Write(buffer.Bytes()))
-}
-
-func (c *XUDPConn) FrontHeadroom() int {
-	return c.frontHeadroom(M.MaxSocksaddrLength)
-}
-
-func (c *XUDPConn) Upstream() any {
-	return c.Conn
-}