Repository: v2fly/v2ray-core
Pull request #2596 (2023-07-09 05:08:03)

fix(transport): support to set permission for UDS and correctly close lock file
Fixes: #2554.

This PR add support to set permission for UDS when set listen field like `"listen": "/dev/shm/v2fly.sock,0666"`. And move the "close lock file" logic to system listen creator. As the previous logic would _never_ close and remove the lock file (the parent context could not got the value set by child).




codecov-commenter: ## [Codecov](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly) Report
Patch coverage: **`3.57`**% and no project coverage change.
> Comparison is base [(`5c995d9`)](https://app.codecov.io/gh/v2fly/v2ray-core/commit/5c995d97f43e8e97ee0cb3d1aa12450d3968c8d2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly) 38.29% compared to head [(`d03d2cf`)](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly) 38.30%.

:exclamation: Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please [install the Github App Integration](https://github.com/apps/codecov) for your organization. [Read more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly).

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #2596   +/-   ##
=======================================
  Coverage   38.29%   38.30%           
=======================================
  Files         637      637           
  Lines       38122    38115    -7     
=======================================
  Hits        14599    14599           
+ Misses      21911    21907    -4     
+ Partials     1612     1609    -3     
```


| [Impacted Files](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly) | Coverage Δ | |
|---|---|---|
| [transport/internet/grpc/hub.go](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly#diff-dHJhbnNwb3J0L2ludGVybmV0L2dycGMvaHViLmdv) | `3.12% <ø> (+0.13%)` | :arrow_up: |
| [transport/internet/http/hub.go](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly#diff-dHJhbnNwb3J0L2ludGVybmV0L2h0dHAvaHViLmdv) | `56.03% <ø> (+2.31%)` | :arrow_up: |
| [transport/internet/tcp/hub.go](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly#diff-dHJhbnNwb3J0L2ludGVybmV0L3RjcC9odWIuZ28=) | `50.00% <ø> (+3.62%)` | :arrow_up: |
| [transport/internet/transportcommon/listener.go](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly#diff-dHJhbnNwb3J0L2ludGVybmV0L3RyYW5zcG9ydGNvbW1vbi9saXN0ZW5lci5nbw==) | `0.00% <0.00%> (ø)` | |
| [transport/internet/websocket/hub.go](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly#diff-dHJhbnNwb3J0L2ludGVybmV0L3dlYnNvY2tldC9odWIuZ28=) | `58.33% <ø> (+2.88%)` | :arrow_up: |
| [transport/internet/system\_listener.go](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly#diff-dHJhbnNwb3J0L2ludGVybmV0L3N5c3RlbV9saXN0ZW5lci5nbw==) | `32.85% <4.16%> (-15.22%)` | :arrow_down: |

... and [5 files with indirect coverage changes](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/v2fly/v2ray-core/pull/2596?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=v2fly).


Patch:
diff --git a/.github/linters/.golangci.yml b/.github/linters/.golangci.yml
index cb5d188d..07df100d 100644
--- a/.github/linters/.golangci.yml
+++ b/.github/linters/.golangci.yml
@@ -52,3 +52,9 @@ linters-settings:
       - name: blank-imports
         severity: warning
         disabled: true
+  depguard:
+    rules:
+      Main:
+        deny:
+          - pkg: "github.com/pkg/errors"
+            desc: Should be replaced by standard lib errors package
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index 75760cbe..8381f195 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -20,7 +20,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Set up Go 1.x
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v4
         with:
           go-version: ^1.19
 
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9713479e..c57361e9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -74,6 +74,8 @@ jobs:
             goarch: arm64
           - goos: linux
             goarch: riscv64
+          - goos: linux
+            goarch: loong64
           - goos: windows
             goarch: arm64
           - goos: android
@@ -117,9 +119,9 @@ jobs:
           echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
 
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v4
         with:
-          go-version: ^1.20
+          go-version: ^1.21
 
       - name: Get project dependencies
         run: go mod download
@@ -185,9 +187,9 @@ jobs:
           fetch-depth: 0
 
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v4
         with:
-          go-version: ^1.20
+          go-version: ^1.21
 
       - uses: actions/download-artifact@v3
         with:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index c25b8aec..40dbd63c 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -8,7 +8,7 @@ jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/stale@v7
+      - uses: actions/stale@v8
         with:
           repo-token: ${{ secrets.GITHUB_TOKEN }}
           stale-issue-message: "This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days"
diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go
index 5e2a63d0..6e1430e6 100644
--- a/app/dns/nameserver_doh.go
+++ b/app/dns/nameserver_doh.go
@@ -231,7 +231,7 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
 			}
 
 			dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
-				Protocol:       "https",
+				Protocol:       "tls",
 				SkipDNSResolve: true,
 			})
 
diff --git a/app/proxyman/outbound/outbound.go b/app/proxyman/outbound/outbound.go
index f44691a8..300a535f 100644
--- a/app/proxyman/outbound/outbound.go
+++ b/app/proxyman/outbound/outbound.go
@@ -82,9 +82,6 @@ func (m *Manager) GetDefaultHandler() outbound.Handler {
 	m.access.RLock()
 	defer m.access.RUnlock()
 
-	if m.defaultHandler == nil {
-		return nil
-	}
 	return m.defaultHandler
 }
 
diff --git a/app/router/weight.go b/app/router/weight.go
index ec7711b9..dc6e90b5 100644
--- a/app/router/weight.go
+++ b/app/router/weight.go
@@ -4,6 +4,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"sync"
 )
 
 type weightScaler func(value, weight float64) float64
@@ -26,10 +27,13 @@ type WeightManager struct {
 	cache         map[string]float64
 	scaler        weightScaler
 	defaultWeight float64
+	mu            sync.Mutex
 }
 
 // Get gets the weight of specified tag
 func (s *WeightManager) Get(tag string) float64 {
+	s.mu.Lock()
+	defer s.mu.Unlock()
 	weight, ok := s.cache[tag]
 	if ok {
 		return weight
diff --git a/app/tun/config.pb.go b/app/tun/config.pb.go
new file mode 100644
index 00000000..20f686fa
--- /dev/null
+++ b/app/tun/config.pb.go
@@ -0,0 +1,263 @@
+package tun
+
+import (
+	routercommon "github.com/v2fly/v2ray-core/v5/app/router/routercommon"
+	packetaddr "github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
+	_ "github.com/v2fly/v2ray-core/v5/common/protoext"
+	internet "github.com/v2fly/v2ray-core/v5/transport/internet"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Config struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name                  string                    `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Mtu                   uint32                    `protobuf:"varint,2,opt,name=mtu,proto3" json:"mtu,omitempty"`
+	UserLevel             uint32                    `protobuf:"varint,3,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
+	PacketEncoding        packetaddr.PacketAddrType `protobuf:"varint,4,opt,name=packet_encoding,json=packetEncoding,proto3,enum=v2ray.core.net.packetaddr.PacketAddrType" json:"packet_encoding,omitempty"`
+	Tag                   string                    `protobuf:"bytes,5,opt,name=tag,proto3" json:"tag,omitempty"`
+	Ips                   []*routercommon.CIDR      `protobuf:"bytes,6,rep,name=ips,proto3" json:"ips,omitempty"`
+	Routes                []*routercommon.CIDR      `protobuf:"bytes,7,rep,name=routes,proto3" json:"routes,omitempty"`
+	EnablePromiscuousMode bool                      `protobuf:"varint,8,opt,name=enable_promiscuous_mode,json=enablePromiscuousMode,proto3" json:"enable_promiscuous_mode,omitempty"`
+	EnableSpoofing        bool                      `protobuf:"varint,9,opt,name=enable_spoofing,json=enableSpoofing,proto3" json:"enable_spoofing,omitempty"`
+	SocketSettings        *internet.SocketConfig    `protobuf:"bytes,10,opt,name=socket_settings,json=socketSettings,proto3" json:"socket_settings,omitempty"`
+}
+
+func (x *Config) Reset() {
+	*x = Config{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_app_tun_config_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Config) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Config) ProtoMessage() {}
+
+func (x *Config) ProtoReflect() protoreflect.Message {
+	mi := &file_app_tun_config_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Config.ProtoReflect.Descriptor instead.
+func (*Config) Descriptor() ([]byte, []int) {
+	return file_app_tun_config_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Config) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *Config) GetMtu() uint32 {
+	if x != nil {
+		return x.Mtu
+	}
+	return 0
+}
+
+func (x *Config) GetUserLevel() uint32 {
+	if x != nil {
+		return x.UserLevel
+	}
+	return 0
+}
+
+func (x *Config) GetPacketEncoding() packetaddr.PacketAddrType {
+	if x != nil {
+		return x.PacketEncoding
+	}
+	return packetaddr.PacketAddrType(0)
+}
+
+func (x *Config) GetTag() string {
+	if x != nil {
+		return x.Tag
+	}
+	return ""
+}
+
+func (x *Config) GetIps() []*routercommon.CIDR {
+	if x != nil {
+		return x.Ips
+	}
+	return nil
+}
+
+func (x *Config) GetRoutes() []*routercommon.CIDR {
+	if x != nil {
+		return x.Routes
+	}
+	return nil
+}
+
+func (x *Config) GetEnablePromiscuousMode() bool {
+	if x != nil {
+		return x.EnablePromiscuousMode
+	}
+	return false
+}
+
+func (x *Config) GetEnableSpoofing() bool {
+	if x != nil {
+		return x.EnableSpoofing
+	}
+	return false
+}
+
+func (x *Config) GetSocketSettings() *internet.SocketConfig {
+	if x != nil {
+		return x.SocketSettings
+	}
+	return nil
+}
+
+var File_app_tun_config_proto protoreflect.FileDescriptor
+
+var file_app_tun_config_proto_rawDesc = []byte{
+	0x0a, 0x14, 0x61, 0x70, 0x70, 0x2f, 0x74, 0x75, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
+	0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x74, 0x75, 0x6e, 0x1a, 0x24, 0x61, 0x70, 0x70, 0x2f,
+	0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6d,
+	0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78,
+	0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x1a, 0x22, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70,
+	0x61, 0x63, 0x6b, 0x65, 0x74, 0x61, 0x64, 0x64, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
+	0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xfc, 0x03, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x74, 0x75, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0d, 0x52, 0x03, 0x6d, 0x74, 0x75, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72,
+	0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, 0x73,
+	0x65, 0x72, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x52, 0x0a, 0x0f, 0x70, 0x61, 0x63, 0x6b, 0x65,
+	0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x29, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x6e, 0x65,
+	0x74, 0x2e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x61, 0x64, 0x64, 0x72, 0x2e, 0x50, 0x61, 0x63,
+	0x6b, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0e, 0x70, 0x61, 0x63,
+	0x6b, 0x65, 0x74, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x74,
+	0x61, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x3a, 0x0a,
+	0x03, 0x69, 0x70, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x76, 0x32, 0x72,
+	0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
+	0x65, 0x72, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
+	0x43, 0x49, 0x44, 0x52, 0x52, 0x03, 0x69, 0x70, 0x73, 0x12, 0x40, 0x0a, 0x06, 0x72, 0x6f, 0x75,
+	0x74, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61,
+	0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
+	0x72, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43,
+	0x49, 0x44, 0x52, 0x52, 0x06, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x65,
+	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x6d, 0x69, 0x73, 0x63, 0x75, 0x6f, 0x75,
+	0x73, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x65, 0x6e,
+	0x61, 0x62, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x6d, 0x69, 0x73, 0x63, 0x75, 0x6f, 0x75, 0x73, 0x4d,
+	0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x73, 0x70,
+	0x6f, 0x6f, 0x66, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x65, 0x6e,
+	0x61, 0x62, 0x6c, 0x65, 0x53, 0x70, 0x6f, 0x6f, 0x66, 0x69, 0x6e, 0x67, 0x12, 0x54, 0x0a, 0x0f,
+	0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18,
+	0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
+	0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
+	0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x52, 0x0e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
+	0x67, 0x73, 0x3a, 0x12, 0x82, 0xb5, 0x18, 0x0e, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63,
+	0x65, 0x12, 0x03, 0x74, 0x75, 0x6e, 0x42, 0x57, 0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32,
+	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x74, 0x75, 0x6e,
+	0x50, 0x01, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76,
+	0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f,
+	0x76, 0x35, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x74, 0x75, 0x6e, 0xaa, 0x02, 0x12, 0x56, 0x32, 0x52,
+	0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x54, 0x75, 0x6e, 0x62,
+	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_app_tun_config_proto_rawDescOnce sync.Once
+	file_app_tun_config_proto_rawDescData = file_app_tun_config_proto_rawDesc
+)
+
+func file_app_tun_config_proto_rawDescGZIP() []byte {
+	file_app_tun_config_proto_rawDescOnce.Do(func() {
+		file_app_tun_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_app_tun_config_proto_rawDescData)
+	})
+	return file_app_tun_config_proto_rawDescData
+}
+
+var file_app_tun_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_app_tun_config_proto_goTypes = []interface{}{
+	(*Config)(nil),                 // 0: v2ray.core.app.tun.Config
+	(packetaddr.PacketAddrType)(0), // 1: v2ray.core.net.packetaddr.PacketAddrType
+	(*routercommon.CIDR)(nil),      // 2: v2ray.core.app.router.routercommon.CIDR
+	(*internet.SocketConfig)(nil),  // 3: v2ray.core.transport.internet.SocketConfig
+}
+var file_app_tun_config_proto_depIdxs = []int32{
+	1, // 0: v2ray.core.app.tun.Config.packet_encoding:type_name -> v2ray.core.net.packetaddr.PacketAddrType
+	2, // 1: v2ray.core.app.tun.Config.ips:type_name -> v2ray.core.app.router.routercommon.CIDR
+	2, // 2: v2ray.core.app.tun.Config.routes:type_name -> v2ray.core.app.router.routercommon.CIDR
+	3, // 3: v2ray.core.app.tun.Config.socket_settings:type_name -> v2ray.core.transport.internet.SocketConfig
+	4, // [4:4] is the sub-list for method output_type
+	4, // [4:4] is the sub-list for method input_type
+	4, // [4:4] is the sub-list for extension type_name
+	4, // [4:4] is the sub-list for extension extendee
+	0, // [0:4] is the sub-list for field type_name
+}
+
+func init() { file_app_tun_config_proto_init() }
+func file_app_tun_config_proto_init() {
+	if File_app_tun_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_app_tun_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Config); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_app_tun_config_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_app_tun_config_proto_goTypes,
+		DependencyIndexes: file_app_tun_config_proto_depIdxs,
+		MessageInfos:      file_app_tun_config_proto_msgTypes,
+	}.Build()
+	File_app_tun_config_proto = out.File
+	file_app_tun_config_proto_rawDesc = nil
+	file_app_tun_config_proto_goTypes = nil
+	file_app_tun_config_proto_depIdxs = nil
+}
diff --git a/app/tun/config.proto b/app/tun/config.proto
new file mode 100644
index 00000000..3e255c14
--- /dev/null
+++ b/app/tun/config.proto
@@ -0,0 +1,30 @@
+syntax = "proto3";
+
+package v2ray.core.app.tun;
+option csharp_namespace = "V2Ray.Core.App.Tun";
+option go_package = "github.com/v2fly/v2ray-core/v5/app/tun";
+option java_package = "com.v2ray.core.app.tun";
+option java_multiple_files = true;
+
+
+import "app/router/routercommon/common.proto";
+import "common/protoext/extensions.proto";
+import "common/net/packetaddr/config.proto";
+import "transport/internet/config.proto";
+
+message Config {
+    option (v2ray.core.common.protoext.message_opt).type = "service";
+    option (v2ray.core.common.protoext.message_opt).short_name = "tun";
+
+    
+    string name = 1;
+    uint32 mtu = 2;
+    uint32 user_level = 3;
+    v2ray.core.net.packetaddr.PacketAddrType packet_encoding = 4;
+    string tag = 5;
+    repeated v2ray.core.app.router.routercommon.CIDR ips = 6;
+    repeated v2ray.core.app.router.routercommon.CIDR routes = 7;
+    bool enable_promiscuous_mode = 8;
+    bool enable_spoofing = 9;
+    v2ray.core.transport.internet.SocketConfig socket_settings = 10;
+}
\ No newline at end of file
diff --git a/app/tun/device/device.go b/app/tun/device/device.go
new file mode 100644
index 00000000..598c070e
--- /dev/null
+++ b/app/tun/device/device.go
@@ -0,0 +1,22 @@
+package device
+
+import (
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+)
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
+
+type Device interface {
+	stack.LinkEndpoint
+
+	common.Closable
+}
+
+type Options struct {
+	Name string
+	MTU  uint32
+}
+
+type DeviceConstructor func(Options) (Device, error)
diff --git a/app/tun/device/errors.generated.go b/app/tun/device/errors.generated.go
new file mode 100644
index 00000000..82f07e1e
--- /dev/null
+++ b/app/tun/device/errors.generated.go
@@ -0,0 +1,9 @@
+package device
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/app/tun/device/gvisor/errors.generated.go b/app/tun/device/gvisor/errors.generated.go
new file mode 100644
index 00000000..315b5ba4
--- /dev/null
+++ b/app/tun/device/gvisor/errors.generated.go
@@ -0,0 +1,9 @@
+package gvisor
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/app/tun/device/gvisor/gvisor.go b/app/tun/device/gvisor/gvisor.go
new file mode 100644
index 00000000..d20c18c3
--- /dev/null
+++ b/app/tun/device/gvisor/gvisor.go
@@ -0,0 +1,3 @@
+package gvisor
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
diff --git a/app/tun/device/gvisor/gvisor_linux.go b/app/tun/device/gvisor/gvisor_linux.go
new file mode 100644
index 00000000..00ea930f
--- /dev/null
+++ b/app/tun/device/gvisor/gvisor_linux.go
@@ -0,0 +1,110 @@
+//go:build linux && ((linux && amd64) || (linux && arm64))
+// +build linux
+// +build linux,amd64 linux,arm64
+
+package gvisor
+
+import (
+	"fmt"
+	"unsafe"
+
+	"golang.org/x/sys/unix"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+
+	"github.com/v2fly/v2ray-core/v5/app/tun/device"
+
+	"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
+	"gvisor.dev/gvisor/pkg/tcpip/link/rawfile"
+	"gvisor.dev/gvisor/pkg/tcpip/link/tun"
+)
+
+const (
+	ifReqSize = unix.IFNAMSIZ + 64
+)
+
+type GvisorTUN struct {
+	stack.LinkEndpoint
+
+	options device.Options
+
+	fd  int
+	mtu uint32 // real MTU
+}
+
+func New(options device.Options) (device.Device, error) {
+	t := &GvisorTUN{options: options}
+
+	if len(options.Name) > unix.IFNAMSIZ {
+		return nil, newError("name too long").AtError()
+	}
+
+	fd, err := tun.Open(options.Name)
+	if err != nil {
+		return nil, newError("failed to open tun device").Base(err).AtError()
+	}
+	t.fd = fd
+
+	if options.MTU > 0 {
+		setMTU(options.Name, int(options.MTU))
+	}
+
+	mtu, err := rawfile.GetMTU(options.Name)
+	if err != nil {
+		return nil, newError("failed to get mtu").Base(err).AtError()
+	}
+	t.mtu = mtu
+
+	linkEndpoint, err := fdbased.New(&fdbased.Options{
+		FDs: []int{fd},
+		MTU: mtu,
+		// TUN is not need to process ethernet header.
+		EthernetHeader: false,
+		// Readv is the default dispatch mode and is the least performant of the
+		// dispatch options but the one that is supported by all underlying FD
+		// types.
+		PacketDispatchMode:    fdbased.Readv,
+		MaxSyscallHeaderBytes: 0x00,
+	})
+	if err != nil {
+		return nil, newError("failed to create link endpoint").Base(err).AtError()
+	}
+	t.LinkEndpoint = linkEndpoint
+
+	return t, nil
+}
+
+func (t *GvisorTUN) Close() error {
+	return unix.Close(t.fd)
+}
+
+// Modified from golang.zx2c4.com/wireguard/tun/tun_linux.go
+func setMTU(name string, n int) error {
+	// open datagram socket
+	fd, err := unix.Socket(
+		unix.AF_INET,
+		unix.SOCK_DGRAM|unix.SOCK_CLOEXEC,
+		0,
+	)
+	if err != nil {
+		return err
+	}
+
+	defer unix.Close(fd)
+
+	// do ioctl call
+	var ifr [ifReqSize]byte
+	copy(ifr[:], name)
+	*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n)
+	_, _, errno := unix.Syscall(
+		unix.SYS_IOCTL,
+		uintptr(fd),
+		uintptr(unix.SIOCSIFMTU),
+		uintptr(unsafe.Pointer(&ifr[0])),
+	)
+
+	if errno != 0 {
+		return fmt.Errorf("failed to set MTU of TUN device: %w", errno)
+	}
+
+	return nil
+}
diff --git a/app/tun/device/gvisor/gvisor_others.go b/app/tun/device/gvisor/gvisor_others.go
new file mode 100644
index 00000000..baf22ac5
--- /dev/null
+++ b/app/tun/device/gvisor/gvisor_others.go
@@ -0,0 +1,10 @@
+//go:build !linux || (linux && !(amd64 || arm64))
+// +build !linux linux,!amd64,!arm64
+
+package gvisor
+
+import "github.com/v2fly/v2ray-core/v5/app/tun/device"
+
+func New(options device.Options) (device.Device, error) {
+	return nil, newError("not supported").AtError()
+}
diff --git a/app/tun/device/linkWriterToWriter.go b/app/tun/device/linkWriterToWriter.go
new file mode 100644
index 00000000..2f47a0b1
--- /dev/null
+++ b/app/tun/device/linkWriterToWriter.go
@@ -0,0 +1,35 @@
+package device
+
+import (
+	"io"
+
+	"github.com/v2fly/v2ray-core/v5/common/errors"
+
+	"gvisor.dev/gvisor/pkg/buffer"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+)
+
+func NewLinkWriterToWriter(writer stack.LinkWriter) io.Writer {
+	return &linkWriterToWriter{writer: writer}
+}
+
+type linkWriterToWriter struct {
+	writer stack.LinkWriter
+}
+
+func (l linkWriterToWriter) Write(p []byte) (n int, err error) {
+	buffer := buffer.MakeWithData(p)
+	packetBufferPtr := stack.NewPacketBuffer(stack.PacketBufferOptions{
+		Payload: buffer,
+		OnRelease: func() {
+			buffer.Release()
+		},
+	})
+	packetList := stack.PacketBufferList{}
+	packetList.PushBack(packetBufferPtr)
+	_, terr := l.writer.WritePackets(packetList)
+	if terr != nil {
+		return 0, newError("failed to write packet").Base(errors.New(terr.String())).AtError()
+	}
+	return len(p), nil
+}
diff --git a/app/tun/errors.generated.go b/app/tun/errors.generated.go
new file mode 100644
index 00000000..0f01e11d
--- /dev/null
+++ b/app/tun/errors.generated.go
@@ -0,0 +1,9 @@
+package tun
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/app/tun/handler.go b/app/tun/handler.go
new file mode 100644
index 00000000..75e8485f
--- /dev/null
+++ b/app/tun/handler.go
@@ -0,0 +1,1 @@
+package tun
diff --git a/app/tun/handler_tcp.go b/app/tun/handler_tcp.go
new file mode 100644
index 00000000..b0041237
--- /dev/null
+++ b/app/tun/handler_tcp.go
@@ -0,0 +1,168 @@
+package tun
+
+import (
+	"context"
+	"time"
+
+	"gvisor.dev/gvisor/pkg/tcpip"
+	"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
+	"gvisor.dev/gvisor/pkg/tcpip/header"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
+	"gvisor.dev/gvisor/pkg/waiter"
+
+	tun_net "github.com/v2fly/v2ray-core/v5/app/tun/net"
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/log"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/session"
+	"github.com/v2fly/v2ray-core/v5/common/signal"
+	"github.com/v2fly/v2ray-core/v5/common/task"
+	"github.com/v2fly/v2ray-core/v5/features/policy"
+	"github.com/v2fly/v2ray-core/v5/features/routing"
+	internet "github.com/v2fly/v2ray-core/v5/transport/internet"
+)
+
+const (
+	rcvWnd      = 0 // default settings
+	maxInFlight = 2 << 10
+)
+
+type tcpConn struct {
+	*gonet.TCPConn
+	id stack.TransportEndpointID
+}
+
+func (c *tcpConn) ID() *stack.TransportEndpointID {
+	return &c.id
+}
+
+type TCPHandler struct {
+	ctx           context.Context
+	dispatcher    routing.Dispatcher
+	policyManager policy.Manager
+	config        *Config
+}
+
+func SetTCPHandler(ctx context.Context, dispatcher routing.Dispatcher, policyManager policy.Manager, config *Config) StackOption {
+	return func(s *stack.Stack) error {
+		tcpForwarder := tcp.NewForwarder(s, rcvWnd, maxInFlight, func(r *tcp.ForwarderRequest) {
+			wg := new(waiter.Queue)
+			linkedEndpoint, err := r.CreateEndpoint(wg)
+			if err != nil {
+				r.Complete(true)
+				return
+			}
+			defer r.Complete(false)
+
+			if config.SocketSettings != nil {
+				if err := applySocketOptions(s, linkedEndpoint, config.SocketSettings); err != nil {
+					newError("failed to apply socket options: ", err).WriteToLog(session.ExportIDToError(ctx))
+				}
+			}
+
+			conn := &tcpConn{
+				TCPConn: gonet.NewTCPConn(wg, linkedEndpoint),
+				id:      r.ID(),
+			}
+
+			handler := &TCPHandler{
+				ctx:           ctx,
+				dispatcher:    dispatcher,
+				policyManager: policyManager,
+				config:        config,
+			}
+
+			go handler.Handle(conn)
+		})
+
+		s.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
+
+		return nil
+	}
+}
+
+func (h *TCPHandler) Handle(conn tun_net.TCPConn) error {
+	defer conn.Close()
+	id := conn.ID()
+	ctx := session.ContextWithInbound(h.ctx, &session.Inbound{Tag: h.config.Tag})
+	sessionPolicy := h.policyManager.ForLevel(h.config.UserLevel)
+
+	dest := net.TCPDestination(tun_net.AddressFromTCPIPAddr(id.LocalAddress), net.Port(id.LocalPort))
+	src := net.TCPDestination(tun_net.AddressFromTCPIPAddr(id.RemoteAddress), net.Port(id.RemotePort))
+	ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
+		From:   src,
+		To:     dest,
+		Status: log.AccessAccepted,
+		Reason: "",
+	})
+	ctx, cancel := context.WithCancel(ctx)
+	timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
+	link, err := h.dispatcher.Dispatch(ctx, dest)
+	if err != nil {
+		return newError("failed to dispatch").Base(err)
+	}
+
+	responseDone := func() error {
+		defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
+
+		if err := buf.Copy(link.Reader, buf.NewWriter(conn), buf.UpdateActivity(timer)); err != nil {
+			return newError("failed to transport all TCP response").Base(err)
+		}
+
+		return nil
+	}
+
+	requestDone := func() error {
+		defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
+
+		if err := buf.Copy(buf.NewReader(conn), link.Writer, buf.UpdateActivity(timer)); err != nil {
+			return newError("failed to transport all TCP request").Base(err)
+		}
+
+		return nil
+	}
+
+	requestDoneAndCloseWriter := task.OnSuccess(requestDone, task.Close(link.Writer))
+	if err := task.Run(h.ctx, requestDoneAndCloseWriter, responseDone); err != nil {
+		common.Interrupt(link.Reader)
+		common.Interrupt(link.Writer)
+		return newError("connection ends").Base(err)
+	}
+
+	return nil
+}
+
+func applySocketOptions(s *stack.Stack, endpoint tcpip.Endpoint, config *internet.SocketConfig) tcpip.Error {
+	if config.TcpKeepAliveInterval > 0 {
+		interval := tcpip.KeepaliveIntervalOption(time.Duration(config.TcpKeepAliveInterval) * time.Second)
+		if err := endpoint.SetSockOpt(&interval); err != nil {
+			return err
+		}
+	}
+
+	if config.TcpKeepAliveIdle > 0 {
+		idle := tcpip.KeepaliveIdleOption(time.Duration(config.TcpKeepAliveIdle) * time.Second)
+		if err := endpoint.SetSockOpt(&idle); err != nil {
+			return err
+		}
+	}
+
+	if config.TcpKeepAliveInterval > 0 || config.TcpKeepAliveIdle > 0 {
+		endpoint.SocketOptions().SetKeepAlive(true)
+	}
+	{
+		var sendBufferSizeRangeOption tcpip.TCPSendBufferSizeRangeOption
+		if err := s.TransportProtocolOption(header.TCPProtocolNumber, &sendBufferSizeRangeOption); err == nil {
+			endpoint.SocketOptions().SetReceiveBufferSize(int64(sendBufferSizeRangeOption.Default), false)
+		}
+
+		var receiveBufferSizeRangeOption tcpip.TCPReceiveBufferSizeRangeOption
+		if err := s.TransportProtocolOption(header.TCPProtocolNumber, &receiveBufferSizeRangeOption); err == nil {
+			endpoint.SocketOptions().SetSendBufferSize(int64(receiveBufferSizeRangeOption.Default), false)
+		}
+	}
+
+	return nil
+}
diff --git a/app/tun/handler_udp.go b/app/tun/handler_udp.go
new file mode 100644
index 00000000..3a755743
--- /dev/null
+++ b/app/tun/handler_udp.go
@@ -0,0 +1,99 @@
+package tun
+
+import (
+	"context"
+
+	"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+	gvisor_udp "gvisor.dev/gvisor/pkg/tcpip/transport/udp"
+	"gvisor.dev/gvisor/pkg/waiter"
+
+	tun_net "github.com/v2fly/v2ray-core/v5/app/tun/net"
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	udp_proto "github.com/v2fly/v2ray-core/v5/common/protocol/udp"
+	"github.com/v2fly/v2ray-core/v5/common/session"
+	"github.com/v2fly/v2ray-core/v5/features/policy"
+	"github.com/v2fly/v2ray-core/v5/features/routing"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/udp"
+)
+
+type UDPHandler struct {
+	ctx           context.Context
+	dispatcher    routing.Dispatcher
+	policyManager policy.Manager
+	config        *Config
+}
+
+type udpConn struct {
+	*gonet.UDPConn
+	id stack.TransportEndpointID
+}
+
+func (c *udpConn) ID() *stack.TransportEndpointID {
+	return &c.id
+}
+
+func SetUDPHandler(ctx context.Context, dispatcher routing.Dispatcher, policyManager policy.Manager, config *Config) StackOption {
+	return func(s *stack.Stack) error {
+		udpForwarder := gvisor_udp.NewForwarder(s, func(r *gvisor_udp.ForwarderRequest) {
+			wg := new(waiter.Queue)
+			linkedEndpoint, err := r.CreateEndpoint(wg)
+			if err != nil {
+				newError("failed to create endpoint: ", err).WriteToLog(session.ExportIDToError(ctx))
+				return
+			}
+
+			conn := &udpConn{
+				UDPConn: gonet.NewUDPConn(s, wg, linkedEndpoint),
+				id:      r.ID(),
+			}
+
+			handler := &UDPHandler{
+				ctx:           ctx,
+				dispatcher:    dispatcher,
+				policyManager: policyManager,
+				config:        config,
+			}
+			go handler.Handle(conn)
+		})
+		s.SetTransportProtocolHandler(gvisor_udp.ProtocolNumber, udpForwarder.HandlePacket)
+		return nil
+	}
+}
+
+func (h *UDPHandler) Handle(conn tun_net.UDPConn) error {
+	defer conn.Close()
+	id := conn.ID()
+	ctx := session.ContextWithInbound(h.ctx, &session.Inbound{Tag: h.config.Tag})
+
+	udpDispatcherConstructor := udp.NewSplitDispatcher
+
+	dest := net.UDPDestination(tun_net.AddressFromTCPIPAddr(id.LocalAddress), net.Port(id.LocalPort))
+	src := net.UDPDestination(tun_net.AddressFromTCPIPAddr(id.RemoteAddress), net.Port(id.RemotePort))
+
+	udpServer := udpDispatcherConstructor(h.dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
+		if _, err := conn.WriteTo(packet.Payload.Bytes(), &net.UDPAddr{
+			IP:   src.Address.IP(),
+			Port: int(src.Port),
+		}); err != nil {
+			newError("failed to write UDP packet").Base(err).WriteToLog()
+		}
+	})
+
+	for {
+		select {
+		case <-ctx.Done():
+			return nil
+		default:
+			var buffer [2048]byte
+			n, _, err := conn.ReadFrom(buffer[:])
+			if err != nil {
+				return newError("failed to read UDP packet").Base(err)
+			}
+			currentPacketCtx := ctx
+
+			udpServer.Dispatch(currentPacketCtx, dest, buf.FromBytes(buffer[:n]))
+		}
+	}
+}
diff --git a/app/tun/net/net.go b/app/tun/net/net.go
new file mode 100644
index 00000000..8f091d47
--- /dev/null
+++ b/app/tun/net/net.go
@@ -0,0 +1,25 @@
+package net
+
+import (
+	"github.com/v2fly/v2ray-core/v5/common/net"
+
+	"gvisor.dev/gvisor/pkg/tcpip"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+)
+
+type TCPConn interface {
+	net.Conn
+
+	ID() *stack.TransportEndpointID
+}
+
+type UDPConn interface {
+	net.Conn
+	net.PacketConn
+
+	ID() *stack.TransportEndpointID
+}
+
+func AddressFromTCPIPAddr(addr tcpip.Address) net.Address {
+	return net.IPAddress(addr.AsSlice())
+}
diff --git a/app/tun/option.go b/app/tun/option.go
new file mode 100644
index 00000000..0a635929
--- /dev/null
+++ b/app/tun/option.go
@@ -0,0 +1,112 @@
+package tun
+
+import (
+	"gvisor.dev/gvisor/pkg/tcpip"
+	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
+	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
+
+	"github.com/v2fly/v2ray-core/v5/app/router/routercommon"
+)
+
+func CreateNIC(id tcpip.NICID, linkEndpoint stack.LinkEndpoint) StackOption {
+	return func(s *stack.Stack) error {
+		if err := s.CreateNICWithOptions(id, linkEndpoint,
+			stack.NICOptions{
+				Disabled: false,
+				QDisc:    nil,
+			}); err != nil {
+			return newError("failed to create NIC:", err)
+		}
+		return nil
+	}
+}
+
+func SetPromiscuousMode(id tcpip.NICID, enable bool) StackOption {
+	return func(s *stack.Stack) error {
+		if err := s.SetPromiscuousMode(id, enable); err != nil {
+			return newError("failed to set promiscuous mode:", err)
+		}
+		return nil
+	}
+}
+
+func SetSpoofing(id tcpip.NICID, enable bool) StackOption {
+	return func(s *stack.Stack) error {
+		if err := s.SetSpoofing(id, enable); err != nil {
+			return newError("failed to set spoofing:", err)
+		}
+		return nil
+	}
+}
+
+func AddProtocolAddress(id tcpip.NICID, ips []*routercommon.CIDR) StackOption {
+	return func(s *stack.Stack) error {
+		for _, ip := range ips {
+			tcpIPAddr := tcpip.AddrFrom4Slice(ip.Ip)
+			protocolAddress := tcpip.ProtocolAddress{
+				AddressWithPrefix: tcpip.AddressWithPrefix{
+					Address:   tcpIPAddr,
+					PrefixLen: int(ip.Prefix),
+				},
+			}
+
+			switch tcpIPAddr.Len() {
+			case 4:
+				protocolAddress.Protocol = ipv4.ProtocolNumber
+			case 16:
+				protocolAddress.Protocol = ipv6.ProtocolNumber
+			default:
+				return newError("invalid IP address length:", tcpIPAddr.Len())
+			}
+
+			if err := s.AddProtocolAddress(id, protocolAddress, stack.AddressProperties{}); err != nil {
+				return newError("failed to add protocol address:", err)
+			}
+		}
+
+		return nil
+	}
+}
+
+func SetRouteTable(id tcpip.NICID, routes []*routercommon.CIDR) StackOption {
+	return func(s *stack.Stack) error {
+		s.SetRouteTable(func() (table []tcpip.Route) {
+			for _, cidrs := range routes {
+				subnet := tcpip.AddressWithPrefix{
+					Address:   tcpip.AddrFrom4Slice(cidrs.Ip),
+					PrefixLen: int(cidrs.Prefix),
+				}.Subnet()
+				route := tcpip.Route{
+					Destination: subnet,
+					NIC:         id,
+				}
+				table = append(table, route)
+			}
+			return
+		}())
+
+		return nil
+	}
+}
+
+func SetTCPSendBufferSize(size int) StackOption {
+	return func(s *stack.Stack) error {
+		sendBufferSizeRangeOption := tcpip.TCPSendBufferSizeRangeOption{Min: tcp.MinBufferSize, Default: size, Max: tcp.MaxBufferSize}
+		if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &sendBufferSizeRangeOption); err != nil {
+			return newError("failed to set tcp send buffer size:", err)
+		}
+		return nil
+	}
+}
+
+func SetTCPReceiveBufferSize(size int) StackOption {
+	return func(s *stack.Stack) error {
+		receiveBufferSizeRangeOption := tcpip.TCPReceiveBufferSizeRangeOption{Min: tcp.MinBufferSize, Default: size, Max: tcp.MaxBufferSize}
+		if err := s.SetTransportProtocolOption(tcp.ProtocolNumber, &receiveBufferSizeRangeOption); err != nil {
+			return newError("failed to set tcp receive buffer size:", err)
+		}
+		return nil
+	}
+}
diff --git a/app/tun/packetaddradaptar.go b/app/tun/packetaddradaptar.go
new file mode 100644
index 00000000..d8554892
--- /dev/null
+++ b/app/tun/packetaddradaptar.go
@@ -0,0 +1,41 @@
+package tun
+
+import (
+	"gvisor.dev/gvisor/pkg/tcpip"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+
+	"github.com/v2fly/v2ray-core/v5/app/tun/device"
+	"github.com/v2fly/v2ray-core/v5/app/tun/tunsorter"
+)
+
+func NewDeviceWithSorter(overlay device.Device, sorter *tunsorter.TunSorter) device.Device {
+	return &packetAddrDevice{
+		Device: overlay,
+		sorter: sorter,
+	}
+}
+
+type packetAddrDevice struct {
+	device.Device
+	sorter *tunsorter.TunSorter
+
+	secondaryDispatcher stack.NetworkDispatcher
+}
+
+func (p *packetAddrDevice) DeliverNetworkPacket(protocol tcpip.NetworkProtocolNumber, pkt stack.PacketBufferPtr) {
+	buf := pkt.ToBuffer()
+	_, err := p.sorter.OnPacketReceived(buf.Flatten())
+	if err != nil {
+		p.secondaryDispatcher.DeliverNetworkPacket(protocol, pkt)
+	}
+}
+
+func (p *packetAddrDevice) DeliverLinkPacket(protocol tcpip.NetworkProtocolNumber, pkt stack.PacketBufferPtr) {
+	// TODO implement me
+	panic("implement me")
+}
+
+func (p *packetAddrDevice) Attach(dispatcher stack.NetworkDispatcher) {
+	p.secondaryDispatcher = dispatcher
+	p.Device.Attach(p)
+}
diff --git a/app/tun/packetparse/errors.generated.go b/app/tun/packetparse/errors.generated.go
new file mode 100644
index 00000000..f1e216a1
--- /dev/null
+++ b/app/tun/packetparse/errors.generated.go
@@ -0,0 +1,9 @@
+package packetparse
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/app/tun/packetparse/packetParse.go b/app/tun/packetparse/packetParse.go
new file mode 100644
index 00000000..68e33c4c
--- /dev/null
+++ b/app/tun/packetparse/packetParse.go
@@ -0,0 +1,3 @@
+package packetparse
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
diff --git a/app/tun/packetparse/udp.go b/app/tun/packetparse/udp.go
new file mode 100644
index 00000000..5f66e2ef
--- /dev/null
+++ b/app/tun/packetparse/udp.go
@@ -0,0 +1,123 @@
+package packetparse
+
+import (
+	"github.com/google/gopacket"
+	"github.com/google/gopacket/layers"
+
+	"github.com/v2fly/v2ray-core/v5/common/net"
+)
+
+var (
+	errNotIPPacket  = newError("not an IP packet")
+	errNotUDPPacket = newError("not a UDP packet")
+)
+
+var nullDestination = net.UnixDestination(net.DomainAddress("null"))
+
+func TryParseAsUDPPacket(packet []byte) (src, dst net.Destination, data []byte, err error) {
+	parsedPacket := gopacket.NewPacket(packet, layers.LayerTypeIPv4, gopacket.DecodeOptions{
+		Lazy:                     true,
+		NoCopy:                   false,
+		SkipDecodeRecovery:       false,
+		DecodeStreamsAsDatagrams: false,
+	})
+
+	var srcIP net.Address
+	var dstIP net.Address
+	ipv4Layer := parsedPacket.Layer(layers.LayerTypeIPv4)
+
+	if ipv4Layer == nil {
+		parsedPacketAsIPv6 := gopacket.NewPacket(packet, layers.LayerTypeIPv6, gopacket.DecodeOptions{
+			Lazy:                     true,
+			NoCopy:                   false,
+			SkipDecodeRecovery:       false,
+			DecodeStreamsAsDatagrams: false,
+		})
+		ipv6Layer := parsedPacketAsIPv6.Layer(layers.LayerTypeIPv6)
+		if ipv6Layer == nil {
+			return nullDestination, nullDestination, nil, errNotIPPacket
+		}
+		ipv6 := ipv6Layer.(*layers.IPv6)
+		srcIP = net.IPAddress(ipv6.SrcIP)
+		dstIP = net.IPAddress(ipv6.DstIP)
+
+		parsedPacket = parsedPacketAsIPv6
+	} else {
+		ipv4 := ipv4Layer.(*layers.IPv4)
+		srcIP = net.IPAddress(ipv4.SrcIP)
+		dstIP = net.IPAddress(ipv4.DstIP)
+	}
+
+	udpLayer := parsedPacket.Layer(layers.LayerTypeUDP)
+	if udpLayer == nil {
+		return nullDestination, nullDestination, nil, errNotUDPPacket
+	}
+	udp := udpLayer.(*layers.UDP)
+	srcPort := net.Port(udp.SrcPort)
+	dstPort := net.Port(udp.DstPort)
+
+	src = net.UDPDestination(srcIP, srcPort)
+	dst = net.UDPDestination(dstIP, dstPort)
+	data = udp.Payload
+	return // nolint: nakedret
+}
+
+func TryConstructUDPPacket(src, dst net.Destination, data []byte) ([]byte, error) {
+	if src.Address.Family().IsIPv4() && dst.Address.Family().IsIPv4() {
+		return constructIPv4UDPPacket(src, dst, data)
+	}
+	if src.Address.Family().IsIPv6() && dst.Address.Family().IsIPv6() {
+		return constructIPv6UDPPacket(src, dst, data)
+	}
+	return nil, newError("not supported")
+}
+
+func constructIPv4UDPPacket(src, dst net.Destination, data []byte) ([]byte, error) {
+	ipv4 := &layers.IPv4{
+		Version:  4,
+		Protocol: layers.IPProtocolUDP,
+		SrcIP:    src.Address.IP(),
+		DstIP:    dst.Address.IP(),
+	}
+	udp := &layers.UDP{
+		SrcPort: layers.UDPPort(src.Port),
+		DstPort: layers.UDPPort(dst.Port),
+	}
+	err := udp.SetNetworkLayerForChecksum(ipv4)
+	if err != nil {
+		return nil, err
+	}
+	buffer := gopacket.NewSerializeBuffer()
+	if err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{
+		FixLengths:       true,
+		ComputeChecksums: true,
+	}, ipv4, udp, gopacket.Payload(data)); err != nil {
+		return nil, err
+	}
+	return buffer.Bytes(), nil
+}
+
+func constructIPv6UDPPacket(src, dst net.Destination, data []byte) ([]byte, error) {
+	ipv6 := &layers.IPv6{
+		Version:    6,
+		NextHeader: layers.IPProtocolUDP,
+		SrcIP:      src.Address.IP(),
+		DstIP:      dst.Address.IP(),
+	}
+	udp := &layers.UDP{
+		SrcPort: layers.UDPPort(src.Port),
+		DstPort: layers.UDPPort(dst.Port),
+	}
+	err := udp.SetNetworkLayerForChecksum(ipv6)
+	if err != nil {
+		return nil, err
+	}
+	buffer := gopacket.NewSerializeBuffer()
+	if err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{
+		FixLengths:       true,
+		ComputeChecksums: true,
+	}, ipv6, udp, gopacket.Payload(data)); err != nil {
+		return nil, err
+	}
+	return buffer.Bytes(), nil
+}
diff --git a/app/tun/stack.go b/app/tun/stack.go
new file mode 100644
index 00000000..4b104a95
--- /dev/null
+++ b/app/tun/stack.go
@@ -0,0 +1,59 @@
+package tun
+
+import (
+	"gvisor.dev/gvisor/pkg/tcpip"
+	"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
+	"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+	"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
+	"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
+	"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
+)
+
+type StackOption func(*stack.Stack) error
+
+func (t *TUN) CreateStack(linkedEndpoint stack.LinkEndpoint) (*stack.Stack, error) {
+	s := stack.New(stack.Options{
+		NetworkProtocols: []stack.NetworkProtocolFactory{
+			ipv4.NewProtocol,
+			ipv6.NewProtocol,
+		},
+		TransportProtocols: []stack.TransportProtocolFactory{
+			tcp.NewProtocol,
+			udp.NewProtocol,
+			icmp.NewProtocol4,
+			icmp.NewProtocol6,
+		},
+	})
+
+	nicID := tcpip.NICID(s.UniqueID())
+
+	opts := []StackOption{
+		SetTCPHandler(t.ctx, t.dispatcher, t.policyManager, t.config),
+		SetUDPHandler(t.ctx, t.dispatcher, t.policyManager, t.config),
+
+		CreateNIC(nicID, linkedEndpoint),
+		AddProtocolAddress(nicID, t.config.Ips),
+		SetRouteTable(nicID, t.config.Routes),
+		SetPromiscuousMode(nicID, t.config.EnablePromiscuousMode),
+		SetSpoofing(nicID, t.config.EnableSpoofing),
+	}
+
+	if t.config.SocketSettings != nil {
+		if size := t.config.SocketSettings.TxBufSize; size != 0 {
+			opts = append(opts, SetTCPSendBufferSize(int(size)))
+		}
+
+		if size := t.config.SocketSettings.RxBufSize; size != 0 {
+			opts = append(opts, SetTCPReceiveBufferSize(int(size)))
+		}
+	}
+
+	for _, opt := range opts {
+		if err := opt(s); err != nil {
+			return nil, err
+		}
+	}
+
+	return s, nil
+}
diff --git a/app/tun/tun.go b/app/tun/tun.go
new file mode 100644
index 00000000..ed86f146
--- /dev/null
+++ b/app/tun/tun.go
@@ -0,0 +1,87 @@
+//go:build !confonly
+// +build !confonly
+
+package tun
+
+import (
+	"context"
+
+	"gvisor.dev/gvisor/pkg/tcpip/stack"
+
+	core "github.com/v2fly/v2ray-core/v5"
+	"github.com/v2fly/v2ray-core/v5/app/tun/device"
+	"github.com/v2fly/v2ray-core/v5/app/tun/device/gvisor"
+	"github.com/v2fly/v2ray-core/v5/app/tun/tunsorter"
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
+	"github.com/v2fly/v2ray-core/v5/features/policy"
+	"github.com/v2fly/v2ray-core/v5/features/routing"
+)
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
+
+type TUN struct {
+	ctx           context.Context
+	dispatcher    routing.Dispatcher
+	policyManager policy.Manager
+	config        *Config
+
+	stack *stack.Stack
+}
+
+func (t *TUN) Type() interface{} {
+	return (*TUN)(nil)
+}
+
+func (t *TUN) Start() error {
+	DeviceConstructor := gvisor.New
+	tunDevice, err := DeviceConstructor(device.Options{
+		Name: t.config.Name,
+		MTU:  t.config.Mtu,
+	})
+	if err != nil {
+		return newError("failed to create device").Base(err).AtError()
+	}
+
+	if t.config.PacketEncoding != packetaddr.PacketAddrType_None {
+		writer := device.NewLinkWriterToWriter(tunDevice)
+		sorter := tunsorter.NewTunSorter(writer, t.dispatcher, t.config.PacketEncoding, t.ctx)
+		tunDeviceLayered := NewDeviceWithSorter(tunDevice, sorter)
+		tunDevice = tunDeviceLayered
+	}
+
+	stack, err := t.CreateStack(tunDevice)
+	if err != nil {
+		return newError("failed to create stack").Base(err).AtError()
+	}
+	t.stack = stack
+
+	return nil
+}
+
+func (t *TUN) Close() error {
+	if t.stack != nil {
+		t.stack.Close()
+		t.stack.Wait()
+	}
+	return nil
+}
+
+func (t *TUN) Init(ctx context.Context, config *Config, dispatcher routing.Dispatcher, policyManager policy.Manager) error {
+	t.ctx = ctx
+	t.config = config
+	t.dispatcher = dispatcher
+	t.policyManager = policyManager
+
+	return nil
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		tun := new(TUN)
+		err := core.RequireFeatures(ctx, func(d routing.Dispatcher, p policy.Manager) error {
+			return tun.Init(ctx, config.(*Config), d, p)
+		})
+		return tun, err
+	}))
+}
diff --git a/app/tun/tunsorter/errors.generated.go b/app/tun/tunsorter/errors.generated.go
new file mode 100644
index 00000000..bec96d6c
--- /dev/null
+++ b/app/tun/tunsorter/errors.generated.go
@@ -0,0 +1,9 @@
+package tunsorter
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/app/tun/tunsorter/tunsorter.go b/app/tun/tunsorter/tunsorter.go
new file mode 100644
index 00000000..d8ed5d51
--- /dev/null
+++ b/app/tun/tunsorter/tunsorter.go
@@ -0,0 +1,103 @@
+package tunsorter
+
+import (
+	"context"
+	"io"
+	"sync"
+
+	"github.com/v2fly/v2ray-core/v5/app/tun/packetparse"
+	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
+	vudp "github.com/v2fly/v2ray-core/v5/common/protocol/udp"
+	"github.com/v2fly/v2ray-core/v5/features/routing"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/udp"
+)
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
+
+func NewTunSorter(tunWriter io.Writer, dispatcher routing.Dispatcher, packetAddrType packetaddr.PacketAddrType, ctx context.Context) *TunSorter {
+	return &TunSorter{
+		tunWriter:      tunWriter,
+		dispatcher:     dispatcher,
+		packetAddrType: packetAddrType,
+		ctx:            ctx,
+	}
+}
+
+type TunSorter struct {
+	tunWriter      io.Writer
+	dispatcher     routing.Dispatcher
+	packetAddrType packetaddr.PacketAddrType
+
+	trackedConnections sync.Map
+	ctx                context.Context
+}
+
+func (t *TunSorter) OnPacketReceived(b []byte) (n int, err error) {
+	src, dst, data, err := packetparse.TryParseAsUDPPacket(b)
+	if err != nil {
+		return 0, err
+	}
+	conn := newTrackedUDPConnection(src, t)
+	trackedConnection, loaded := t.trackedConnections.LoadOrStore(src.String(), conn)
+	conn = trackedConnection.(*trackedUDPConnection)
+	if !loaded {
+		t.onNewConnection(conn)
+	}
+	conn.onNewPacket(dst, data)
+	return len(b), nil
+}
+
+func (t *TunSorter) onNewConnection(connection *trackedUDPConnection) {
+	udpDispatcherConstructor := udp.NewSplitDispatcher
+	switch t.packetAddrType { // nolint: gocritic
+	case packetaddr.PacketAddrType_Packet:
+		ctx := context.WithValue(t.ctx, udp.DispatcherConnectionTerminationSignalReceiverMark, connection) // nolint:staticcheck
+		packetAddrDispatcherFactory := udp.NewPacketAddrDispatcherCreator(ctx)
+		udpDispatcherConstructor = packetAddrDispatcherFactory.NewPacketAddrDispatcher
+	}
+	udpDispatcher := udpDispatcherConstructor(t.dispatcher, func(ctx context.Context, packet *vudp.Packet) {
+		connection.onWritePacket(packet.Source, packet.Payload.Bytes())
+	})
+	connection.packetDispatcher = udpDispatcher
+}
+
+func (t *TunSorter) onWritePacket(src net.Destination, dest net.Destination, data []byte) {
+	data, err := packetparse.TryConstructUDPPacket(src, dest, data)
+	if err != nil {
+		newError("failed to construct udp packet").Base(err).WriteToLog()
+		return
+	}
+	_, err = t.tunWriter.Write(data)
+	if err != nil {
+		newError("failed to write udp packet to tun").Base(err).WriteToLog()
+		return
+	}
+}
+
+func newTrackedUDPConnection(src net.Destination, tunSorter *TunSorter) *trackedUDPConnection {
+	return &trackedUDPConnection{
+		tunSorter: tunSorter,
+		src:       src,
+	}
+}
+
+type trackedUDPConnection struct {
+	packetDispatcher udp.DispatcherI
+	tunSorter        *TunSorter
+	src              net.Destination
+}
+
+func (t *trackedUDPConnection) onNewPacket(dst net.Destination, data []byte) {
+	t.packetDispatcher.Dispatch(context.Background(), dst, buf.FromBytes(data))
+}
+
+func (t *trackedUDPConnection) onWritePacket(src net.Destination, data []byte) {
+	t.tunSorter.onWritePacket(src, t.src, data)
+}
+
+func (t *trackedUDPConnection) Close() error {
+	t.tunSorter.trackedConnections.Delete(t.src.String())
+	return nil
+}
diff --git a/common/cmdarg/arg.go b/common/cmdarg/arg.go
index 8a0958d4..52ccb7d6 100644
--- a/common/cmdarg/arg.go
+++ b/common/cmdarg/arg.go
@@ -3,7 +3,7 @@ package cmdarg
 import (
 	"bytes"
 	"io"
-	"io/ioutil"
+	"os"
 )
 
 // LoadArg loads one arg, maybe an remote url, or local file path
@@ -18,7 +18,7 @@ func LoadArg(arg string) (out io.Reader, err error) {
 
 // LoadArgToBytes loads one arg to []byte, maybe an remote url, or local file path
 func LoadArgToBytes(arg string) (out []byte, err error) {
-	out, err = ioutil.ReadFile(arg)
+	out, err = os.ReadFile(arg)
 	if err != nil {
 		return
 	}
diff --git a/common/protocol/http/sniff.go b/common/protocol/http/sniff.go
index 98e4e908..fb60eea3 100644
--- a/common/protocol/http/sniff.go
+++ b/common/protocol/http/sniff.go
@@ -11,25 +11,12 @@ import (
 
 type version byte
 
-const (
-	HTTP1 version = iota
-	HTTP2
-)
-
 type SniffHeader struct {
-	version version
-	host    string
+	host string
 }
 
 func (h *SniffHeader) Protocol() string {
-	switch h.version {
-	case HTTP1:
-		return "http1"
-	case HTTP2:
-		return "http2"
-	default:
-		return "unknown"
-	}
+	return "http1"
 }
 
 func (h *SniffHeader) Domain() string {
@@ -62,9 +49,7 @@ func SniffHTTP(b []byte) (*SniffHeader, error) {
 		return nil, err
 	}
 
-	sh := &SniffHeader{
-		version: HTTP1,
-	}
+	sh := &SniffHeader{}
 
 	headers := bytes.Split(b, []byte{'\n'})
 	for i := 1; i < len(headers); i++ {
diff --git a/common/protocol/quic/cipher_suite.go b/common/protocol/quic/cipher_suite.go
new file mode 100644
index 00000000..798a03af
--- /dev/null
+++ b/common/protocol/quic/cipher_suite.go
@@ -0,0 +1,23 @@
+package quic
+
+import (
+	"crypto"
+	"crypto/cipher"
+	_ "crypto/tls"
+	_ "unsafe"
+)
+
+// copied from github.com/quic-go/quic-go/internal/qtls/cipher_suite_go121.go
+
+type cipherSuiteTLS13 struct {
+	ID     uint16
+	KeyLen int
+	AEAD   func(key, fixedNonce []byte) cipher.AEAD
+	Hash   crypto.Hash
+}
+
+// github.com/quic-go/quic-go/internal/handshake/cipher_suite.go describes these cipher suite implementations are copied from the standard library crypto/tls package.
+// So we can user go:linkname to implement the same feature.
+
+//go:linkname aeadAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
+func aeadAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
diff --git a/common/protocol/quic/qtls_go119.go b/common/protocol/quic/qtls_go119.go
deleted file mode 100644
index 4ab25af8..00000000
--- a/common/protocol/quic/qtls_go119.go
+++ /dev/null
@@ -1,18 +0,0 @@
-//go:build go1.19 && !go1.20
-
-package quic
-
-import (
-	"crypto/cipher"
-
-	"github.com/quic-go/qtls-go1-19"
-)
-
-type (
-	// A CipherSuiteTLS13 is a cipher suite for TLS 1.3
-	CipherSuiteTLS13 = qtls.CipherSuiteTLS13
-)
-
-func AEADAESGCMTLS13(key, fixedNonce []byte) cipher.AEAD {
-	return qtls.AEADAESGCMTLS13(key, fixedNonce)
-}
diff --git a/common/protocol/quic/qtls_go120.go b/common/protocol/quic/qtls_go120.go
deleted file mode 100644
index e182c75b..00000000
--- a/common/protocol/quic/qtls_go120.go
+++ /dev/null
@@ -1,18 +0,0 @@
-//go:build go1.20
-
-package quic
-
-import (
-	"crypto/cipher"
-
-	"github.com/quic-go/qtls-go1-20"
-)
-
-type (
-	// A CipherSuiteTLS13 is a cipher suite for TLS 1.3
-	CipherSuiteTLS13 = qtls.CipherSuiteTLS13
-)
-
-func AEADAESGCMTLS13(key, fixedNonce []byte) cipher.AEAD {
-	return qtls.AEADAESGCMTLS13(key, fixedNonce)
-}
diff --git a/common/protocol/quic/sniff.go b/common/protocol/quic/sniff.go
index d88c2945..f8fcd0ba 100644
--- a/common/protocol/quic/sniff.go
+++ b/common/protocol/quic/sniff.go
@@ -37,10 +37,10 @@ const (
 var (
 	quicSaltOld  = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
 	quicSalt     = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
-	initialSuite = &CipherSuiteTLS13{
+	initialSuite = &cipherSuiteTLS13{
 		ID:     tls.TLS_AES_128_GCM_SHA256,
 		KeyLen: 16,
-		AEAD:   AEADAESGCMTLS13,
+		AEAD:   aeadAESGCMTLS13,
 		Hash:   crypto.SHA256,
 	}
 	errNotQuic        = errors.New("not quic")
@@ -153,7 +153,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
 
 	key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
 	iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
-	cipher := AEADAESGCMTLS13(key, iv)
+	cipher := aeadAESGCMTLS13(key, iv)
 	nonce := cache.Extend(int32(cipher.NonceSize()))
 	binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber))
 	decrypted, err := cipher.Open(b[extHdrLen:extHdrLen], nonce, data, b[:extHdrLen])
diff --git a/core.go b/core.go
index ee0e2107..c9ab606c 100644
--- a/core.go
+++ b/core.go
@@ -18,7 +18,7 @@ import (
 )
 
 var (
-	version  = "5.7.0"
+	version  = "5.8.0"
 	build    = "Custom"
 	codename = "V2Fly, a community-driven edition of V2Ray."
 	intro    = "A unified platform for anti-censorship."
diff --git a/go.mod b/go.mod
index b9a5fd92..852b3cd5 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/v2fly/v2ray-core/v5
 
-go 1.19
+go 1.20
 
 require (
 	github.com/adrg/xdg v0.4.0
@@ -10,16 +10,15 @@ require (
 	github.com/golang/mock v1.6.0
 	github.com/golang/protobuf v1.5.3
 	github.com/google/go-cmp v0.5.9
+	github.com/google/gopacket v1.1.17
 	github.com/gorilla/websocket v1.5.0
 	github.com/jhump/protoreflect v1.15.1
 	github.com/miekg/dns v1.1.54
 	github.com/mustafaturan/bus v1.0.2
 	github.com/pelletier/go-toml v1.9.5
 	github.com/pires/go-proxyproto v0.7.0
-	github.com/quic-go/qtls-go1-19 v0.3.2
-	github.com/quic-go/qtls-go1-20 v0.2.2
-	github.com/quic-go/quic-go v0.36.0
-	github.com/refraction-networking/utls v1.3.2
+	github.com/quic-go/quic-go v0.39.0
+	github.com/refraction-networking/utls v1.5.4
 	github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
 	github.com/stretchr/testify v1.8.4
 	github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08
@@ -28,13 +27,14 @@ require (
 	github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432
 	go.starlark.net v0.0.0-20230612165344-9532f5667272
 	go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
-	golang.org/x/crypto v0.9.0
-	golang.org/x/net v0.10.0
-	golang.org/x/sync v0.2.0
-	golang.org/x/sys v0.9.0
-	google.golang.org/grpc v1.55.0
+	golang.org/x/crypto v0.14.0
+	golang.org/x/net v0.17.0
+	golang.org/x/sync v0.4.0
+	golang.org/x/sys v0.13.0
+	google.golang.org/grpc v1.57.0
 	google.golang.org/protobuf v1.31.0
 	gopkg.in/yaml.v3 v3.0.1
+	gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1
 	h12.io/socks v1.0.3
 )
 
@@ -44,16 +44,18 @@ require (
 	github.com/andybalholm/brotli v1.0.5 // indirect
 	github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d // indirect
 	github.com/bufbuild/protocompile v0.5.1 // indirect
+	github.com/cloudflare/circl v1.3.3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
 	github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
-	github.com/gaukas/godicttls v0.0.3 // indirect
+	github.com/gaukas/godicttls v0.0.4 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
+	github.com/google/btree v1.1.2 // indirect
 	github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
-	github.com/klauspost/compress v1.16.5 // indirect
+	github.com/klauspost/compress v1.16.7 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
 	github.com/klauspost/reedsolomon v1.11.7 // indirect
 	github.com/leodido/go-urn v1.2.4 // indirect
@@ -67,12 +69,15 @@ require (
 	github.com/pion/sctp v1.8.7 // indirect
 	github.com/pion/transport/v2 v2.2.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
 	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
 	github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 // indirect
 	github.com/xtaci/smux v1.5.24 // indirect
-	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
-	golang.org/x/mod v0.10.0 // indirect
-	golang.org/x/text v0.10.0 // indirect
-	golang.org/x/tools v0.9.3 // indirect
+	go.uber.org/mock v0.3.0 // indirect
+	golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
+	golang.org/x/mod v0.13.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
+	golang.org/x/time v0.3.0 // indirect
+	golang.org/x/tools v0.14.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
 )
diff --git a/go.sum b/go.sum
index a01a8e69..cbec81a2 100644
--- a/go.sum
+++ b/go.sum
@@ -42,6 +42,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
+github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -66,8 +68,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
 github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
-github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
-github.com/gaukas/godicttls v0.0.3/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
+github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
+github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
 github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
@@ -112,6 +114,8 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
+github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -121,6 +125,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY=
 github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -171,8 +176,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
-github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
+github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
 github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -260,14 +265,12 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
-github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
-github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
-github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
-github.com/quic-go/quic-go v0.36.0 h1:JIrO7p7Ug6hssFcARjWDiqS2RAKJHCiwPxBAA989rbI=
-github.com/quic-go/quic-go v0.36.0/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
-github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
-github.com/refraction-networking/utls v1.3.2/go.mod h1:fmoaOww2bxzzEpIKOebIsnBvjQpqP7L2vcm/9KUfm/E=
+github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
+github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
+github.com/quic-go/quic-go v0.39.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3So=
+github.com/quic-go/quic-go v0.39.0/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
+github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
+github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -339,6 +342,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
 go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
+go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo=
@@ -355,15 +360,15 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
-golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
-golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
-golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
+golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
+golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -381,8 +386,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
-golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
+golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -405,8 +410,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
-golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
-golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -419,8 +424,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
-golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
+golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -452,8 +457,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
-golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -468,10 +473,12 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
-golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
+golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -495,8 +502,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
-golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
+golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
+golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -527,8 +534,8 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
-google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
+google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
+google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -558,6 +565,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
 gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1 h1:qDCwdCWECGnwQSQC01Dpnp09fRHxJs9PbktotUqG+hs=
+gvisor.dev/gvisor v0.0.0-20231020174304-b8a429915ff1/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8=
 h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
 h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/infra/conf/geodata/memconservative/cache.go b/infra/conf/geodata/memconservative/cache.go
index 386086cb..307db9d6 100644
--- a/infra/conf/geodata/memconservative/cache.go
+++ b/infra/conf/geodata/memconservative/cache.go
@@ -1,7 +1,7 @@
 package memconservative
 
 import (
-	"io/ioutil"
+	"os"
 	"strings"
 
 	"google.golang.org/protobuf/proto"
@@ -53,7 +53,7 @@ func (g GeoIPCache) Unmarshal(filename, code string) (*routercommon.GeoIP, error
 	case errFailedToReadBytes, errFailedToReadExpectedLenBytes,
 		errInvalidGeodataFile, errInvalidGeodataVarintLength:
 		newError("failed to decode geoip file: ", filename, ", fallback to the original ReadFile method")
-		geoipBytes, err = ioutil.ReadFile(asset)
+		geoipBytes, err = os.ReadFile(asset)
 		if err != nil {
 			return nil, err
 		}
@@ -118,7 +118,7 @@ func (g GeoSiteCache) Unmarshal(filename, code string) (*routercommon.GeoSite, e
 	case errFailedToReadBytes, errFailedToReadExpectedLenBytes,
 		errInvalidGeodataFile, errInvalidGeodataVarintLength:
 		newError("failed to decode geoip file: ", filename, ", fallback to the original ReadFile method")
-		geositeBytes, err = ioutil.ReadFile(asset)
+		geositeBytes, err = os.ReadFile(asset)
 		if err != nil {
 			return nil, err
 		}
diff --git a/infra/conf/v5cfg/root.go b/infra/conf/v5cfg/root.go
index ae05f925..d70c9af9 100644
--- a/infra/conf/v5cfg/root.go
+++ b/infra/conf/v5cfg/root.go
@@ -3,6 +3,7 @@ package v5cfg
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 
 	"github.com/golang/protobuf/proto"
 	"google.golang.org/protobuf/types/known/anypb"
@@ -30,7 +31,7 @@ func (c RootConfig) BuildV5(ctx context.Context) (proto.Message, error) {
 	if c.LogConfig != nil {
 		logConfMsgUnpacked, err := loadHeterogeneousConfigFromRawJSON("service", "log", c.LogConfig)
 		if err != nil {
-			return nil, err
+			return nil, newError("failed to parse Log config").Base(err)
 		}
 		logConfMsg = serial.ToTypedMessage(logConfMsgUnpacked)
 	} else {
@@ -43,7 +44,7 @@ func (c RootConfig) BuildV5(ctx context.Context) (proto.Message, error) {
 	if c.RouterConfig != nil {
 		routerConfig, err := loadHeterogeneousConfigFromRawJSON("service", "router", c.RouterConfig)
 		if err != nil {
-			return nil, err
+			return nil, newError("failed to parse Router config").Base(err)
 		}
 		config.App = append(config.App, serial.ToTypedMessage(routerConfig))
 	}
@@ -75,7 +76,7 @@ func (c RootConfig) BuildV5(ctx context.Context) (proto.Message, error) {
 	for serviceName, service := range c.Services {
 		servicePackedConfig, err := loadHeterogeneousConfigFromRawJSON("service", serviceName, service)
 		if err != nil {
-			return nil, err
+			return nil, newError(fmt.Sprintf("failed to parse %v config in Services", serviceName)).Base(err)
 		}
 		config.App = append(config.App, serial.ToTypedMessage(servicePackedConfig))
 	}
diff --git a/infra/vprotogen/main.go b/infra/vprotogen/main.go
index 2551808c..df78fa65 100644
--- a/infra/vprotogen/main.go
+++ b/infra/vprotogen/main.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"go/build"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"os/exec"
@@ -48,7 +47,7 @@ func GetRuntimeEnv(key string) (string, error) {
 	}
 	var data []byte
 	var runtimeEnv string
-	data, readErr := ioutil.ReadFile(file)
+	data, readErr := os.ReadFile(file)
 	if readErr != nil {
 		return "", readErr
 	}
diff --git a/main/commands/helpers/fs.go b/main/commands/helpers/fs.go
index 855ee19b..542a52ed 100644
--- a/main/commands/helpers/fs.go
+++ b/main/commands/helpers/fs.go
@@ -2,7 +2,6 @@ package helpers
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -10,7 +9,7 @@ import (
 
 // ReadDir finds files according to extensions in the dir
 func ReadDir(dir string, extensions []string) ([]string, error) {
-	confs, err := ioutil.ReadDir(dir)
+	confs, err := os.ReadDir(dir)
 	if err != nil {
 		return nil, err
 	}
diff --git a/main/commands/run.go b/main/commands/run.go
index 1e88b84b..066bfae1 100644
--- a/main/commands/run.go
+++ b/main/commands/run.go
@@ -2,7 +2,6 @@ package commands
 
 import (
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/signal"
@@ -113,7 +112,7 @@ func dirExists(file string) bool {
 }
 
 func readConfDir(dirPath string, extension []string) cmdarg.Arg {
-	confs, err := ioutil.ReadDir(dirPath)
+	confs, err := os.ReadDir(dirPath)
 	if err != nil {
 		base.Fatalf("failed to read dir %s: %s", dirPath, err)
 	}
diff --git a/main/distro/all/all.go b/main/distro/all/all.go
index 9bcbc14c..cdf4613b 100644
--- a/main/distro/all/all.go
+++ b/main/distro/all/all.go
@@ -34,6 +34,7 @@ import (
 	_ "github.com/v2fly/v2ray-core/v5/app/instman"
 	_ "github.com/v2fly/v2ray-core/v5/app/observatory"
 	_ "github.com/v2fly/v2ray-core/v5/app/restfulapi"
+	_ "github.com/v2fly/v2ray-core/v5/app/tun"
 
 	// Inbound and outbound proxies.
 	_ "github.com/v2fly/v2ray-core/v5/proxy/blackhole"
diff --git a/proxy/http/server.go b/proxy/http/server.go
index 26dc66b8..d698f3fa 100644
--- a/proxy/http/server.go
+++ b/proxy/http/server.go
@@ -242,9 +242,7 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
 		request.Header.Set("User-Agent", "")
 	}
 
-	content := &session.Content{
-		Protocol: "http/1.1",
-	}
+	content := &session.Content{}
 
 	content.SetAttribute(":method", strings.ToUpper(request.Method))
 	content.SetAttribute(":path", request.URL.Path)
diff --git a/proxy/shadowsocks/config.go b/proxy/shadowsocks/config.go
index 1a0cb7a2..e7cf8a88 100644
--- a/proxy/shadowsocks/config.go
+++ b/proxy/shadowsocks/config.go
@@ -6,11 +6,9 @@ import (
 	"crypto/cipher"
 	"crypto/md5"
 	"crypto/sha1"
-	"encoding/json"
 	"io"
 	"strings"
 
-	"github.com/golang/protobuf/jsonpb"
 	"golang.org/x/crypto/chacha20poly1305"
 	"golang.org/x/crypto/hkdf"
 
@@ -212,27 +210,13 @@ func (NoneCipher) DecodePacket(key []byte, b *buf.Buffer) error {
 	return nil
 }
 
-func (c *CipherType) UnmarshalJSONPB(unmarshaler *jsonpb.Unmarshaler, bytes []byte) error {
-	var method string
-
-	if err := json.Unmarshal(bytes, &method); err != nil {
-		return err
-	}
-
-	if *c = CipherFromString(method); *c == CipherType_UNKNOWN {
-		return newError("unknown cipher method: ", method)
-	}
-
-	return nil
-}
-
 func CipherFromString(c string) CipherType {
 	switch strings.ToLower(c) {
-	case "aes-128-gcm", "aead_aes_128_gcm":
+	case "aes-128-gcm", "aes_128_gcm", "aead_aes_128_gcm":
 		return CipherType_AES_128_GCM
-	case "aes-256-gcm", "aead_aes_256_gcm":
+	case "aes-256-gcm", "aes_256_gcm", "aead_aes_256_gcm":
 		return CipherType_AES_256_GCM
-	case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305":
+	case "chacha20-poly1305", "chacha20_poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305":
 		return CipherType_CHACHA20_POLY1305
 	case "none", "plain":
 		return CipherType_NONE
diff --git a/proxy/shadowsocks/simplified/config.go b/proxy/shadowsocks/simplified/config.go
index 3e9ee1ba..0bbdd18a 100644
--- a/proxy/shadowsocks/simplified/config.go
+++ b/proxy/shadowsocks/simplified/config.go
@@ -2,6 +2,9 @@ package simplified
 
 import (
 	"context"
+	"encoding/json"
+
+	"github.com/golang/protobuf/jsonpb"
 
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common/protocol"
@@ -9,6 +12,20 @@ import (
 	"github.com/v2fly/v2ray-core/v5/proxy/shadowsocks"
 )
 
+func (c *CipherTypeWrapper) UnmarshalJSONPB(unmarshaler *jsonpb.Unmarshaler, bytes []byte) error {
+	var method string
+
+	if err := json.Unmarshal(bytes, &method); err != nil {
+		return err
+	}
+
+	if c.Value = shadowsocks.CipherFromString(method); c.Value == shadowsocks.CipherType_UNKNOWN {
+		return newError("unknown cipher method: ", method)
+	}
+
+	return nil
+}
+
 func init() {
 	common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 		simplifiedServer := config.(*ServerConfig)
@@ -16,7 +33,7 @@ func init() {
 			User: &protocol.User{
 				Account: serial.ToTypedMessage(&shadowsocks.Account{
 					Password:   simplifiedServer.Password,
-					CipherType: simplifiedServer.Method,
+					CipherType: simplifiedServer.Method.Value,
 				}),
 			},
 			Network:        simplifiedServer.Networks.GetNetwork(),
@@ -37,7 +54,7 @@ func init() {
 						{
 							Account: serial.ToTypedMessage(&shadowsocks.Account{
 								Password:                       simplifiedClient.Password,
-								CipherType:                     simplifiedClient.Method,
+								CipherType:                     simplifiedClient.Method.Value,
 								ExperimentReducedIvHeadEntropy: simplifiedClient.ExperimentReducedIvHeadEntropy,
 							}),
 						},
diff --git a/proxy/shadowsocks/simplified/config.pb.go b/proxy/shadowsocks/simplified/config.pb.go
index aa317c1f..ec01869b 100644
--- a/proxy/shadowsocks/simplified/config.pb.go
+++ b/proxy/shadowsocks/simplified/config.pb.go
@@ -1,3 +1,9 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.30.0
+// 	protoc        v3.21.12
+// source: proxy/shadowsocks/simplified/config.proto
+
 package simplified
 
 import (
@@ -23,7 +29,7 @@ type ServerConfig struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Method         shadowsocks.CipherType    `protobuf:"varint,1,opt,name=method,proto3,enum=v2ray.core.proxy.shadowsocks.CipherType" json:"method,omitempty"`
+	Method         *CipherTypeWrapper        `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
 	Password       string                    `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
 	Networks       *net.NetworkList          `protobuf:"bytes,3,opt,name=networks,proto3" json:"networks,omitempty"`
 	PacketEncoding packetaddr.PacketAddrType `protobuf:"varint,4,opt,name=packet_encoding,json=packetEncoding,proto3,enum=v2ray.core.net.packetaddr.PacketAddrType" json:"packet_encoding,omitempty"`
@@ -61,11 +67,11 @@ func (*ServerConfig) Descriptor() ([]byte, []int) {
 	return file_proxy_shadowsocks_simplified_config_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *ServerConfig) GetMethod() shadowsocks.CipherType {
+func (x *ServerConfig) GetMethod() *CipherTypeWrapper {
 	if x != nil {
 		return x.Method
 	}
-	return shadowsocks.CipherType(0)
+	return nil
 }
 
 func (x *ServerConfig) GetPassword() string {
@@ -94,11 +100,11 @@ type ClientConfig struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Address                        *net.IPOrDomain        `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
-	Port                           uint32                 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
-	Method                         shadowsocks.CipherType `protobuf:"varint,3,opt,name=method,proto3,enum=v2ray.core.proxy.shadowsocks.CipherType" json:"method,omitempty"`
-	Password                       string                 `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"`
-	ExperimentReducedIvHeadEntropy bool                   `protobuf:"varint,90001,opt,name=experiment_reduced_iv_head_entropy,json=experimentReducedIvHeadEntropy,proto3" json:"experiment_reduced_iv_head_entropy,omitempty"`
+	Address                        *net.IPOrDomain    `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+	Port                           uint32             `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
+	Method                         *CipherTypeWrapper `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
+	Password                       string             `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"`
+	ExperimentReducedIvHeadEntropy bool               `protobuf:"varint,90001,opt,name=experiment_reduced_iv_head_entropy,json=experimentReducedIvHeadEntropy,proto3" json:"experiment_reduced_iv_head_entropy,omitempty"`
 }
 
 func (x *ClientConfig) Reset() {
@@ -147,11 +153,11 @@ func (x *ClientConfig) GetPort() uint32 {
 	return 0
 }
 
-func (x *ClientConfig) GetMethod() shadowsocks.CipherType {
+func (x *ClientConfig) GetMethod() *CipherTypeWrapper {
 	if x != nil {
 		return x.Method
 	}
-	return shadowsocks.CipherType(0)
+	return nil
 }
 
 func (x *ClientConfig) GetPassword() string {
@@ -168,6 +174,53 @@ func (x *ClientConfig) GetExperimentReducedIvHeadEntropy() bool {
 	return false
 }
 
+type CipherTypeWrapper struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Value shadowsocks.CipherType `protobuf:"varint,1,opt,name=value,proto3,enum=v2ray.core.proxy.shadowsocks.CipherType" json:"value,omitempty"`
+}
+
+func (x *CipherTypeWrapper) Reset() {
+	*x = CipherTypeWrapper{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_shadowsocks_simplified_config_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CipherTypeWrapper) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CipherTypeWrapper) ProtoMessage() {}
+
+func (x *CipherTypeWrapper) ProtoReflect() protoreflect.Message {
+	mi := &file_proxy_shadowsocks_simplified_config_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CipherTypeWrapper.ProtoReflect.Descriptor instead.
+func (*CipherTypeWrapper) Descriptor() ([]byte, []int) {
+	return file_proxy_shadowsocks_simplified_config_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CipherTypeWrapper) GetValue() shadowsocks.CipherType {
+	if x != nil {
+		return x.Value
+	}
+	return shadowsocks.CipherType(0)
+}
+
 var File_proxy_shadowsocks_simplified_config_proto protoreflect.FileDescriptor
 
 var file_proxy_shadowsocks_simplified_config_proto_rawDesc = []byte{
@@ -185,35 +238,37 @@ var file_proxy_shadowsocks_simplified_config_proto_rawDesc = []byte{
 	0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x61, 0x64, 0x64,
 	0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e,
 	0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b,
-	0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa0,
+	0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xae,
 	0x02, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-	0x40, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
-	0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f,
-	0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x43,
-	0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f,
-	0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x3e, 0x0a,
-	0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
-	0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c,
-	0x69, 0x73, 0x74, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x52, 0x0a,
-	0x0f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x72, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x61, 0x64,
-	0x64, 0x72, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x54, 0x79, 0x70,
-	0x65, 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e,
-	0x67, 0x3a, 0x1e, 0x82, 0xb5, 0x18, 0x09, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
-	0x82, 0xb5, 0x18, 0x0d, 0x12, 0x0b, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b,
-	0x73, 0x22, 0xac, 0x02, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
-	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72,
-	0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
-	0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70,
-	0x6f, 0x72, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
-	0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63,
-	0x6b, 0x73, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x6d,
+	0x52, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x3a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x73,
+	0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72,
+	0x54, 0x79, 0x70, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x06, 0x6d, 0x65, 0x74,
+	0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12,
+	0x3e, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+	0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12,
+	0x52, 0x0a, 0x0f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69,
+	0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79,
+	0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74,
+	0x61, 0x64, 0x64, 0x72, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x54,
+	0x79, 0x70, 0x65, 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x6e, 0x63, 0x6f, 0x64,
+	0x69, 0x6e, 0x67, 0x3a, 0x1a, 0x82, 0xb5, 0x18, 0x16, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75,
+	0x6e, 0x64, 0x12, 0x0b, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x22,
+	0xba, 0x02, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f,
+	0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a,
+	0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72,
+	0x74, 0x12, 0x52, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x3a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73,
+	0x2e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x43, 0x69, 0x70, 0x68,
+	0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x06, 0x6d,
 	0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
 	0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
 	0x64, 0x12, 0x4c, 0x0a, 0x22, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
@@ -221,19 +276,24 @@ var file_proxy_shadowsocks_simplified_config_proto_rawDesc = []byte{
 	0x65, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x18, 0x91, 0xbf, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
 	0x1e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x64, 0x75, 0x63,
 	0x65, 0x64, 0x49, 0x76, 0x48, 0x65, 0x61, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x3a,
-	0x1f, 0x82, 0xb5, 0x18, 0x0a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x82,
-	0xb5, 0x18, 0x0d, 0x12, 0x0b, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73,
-	0x42, 0x96, 0x01, 0x0a, 0x2b, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77,
-	0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64,
-	0x50, 0x01, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76,
-	0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f,
-	0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73,
-	0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0xaa,
-	0x02, 0x27, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f,
-	0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x53,
-	0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x1b, 0x82, 0xb5, 0x18, 0x17, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12,
+	0x0b, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x22, 0x53, 0x0a, 0x11,
+	0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65,
+	0x72, 0x12, 0x3e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72,
+	0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e,
+	0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x42, 0x96, 0x01, 0x0a, 0x2b, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
+	0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f,
+	0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65,
+	0x64, 0x50, 0x01, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+	0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
+	0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77,
+	0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64,
+	0xaa, 0x02, 0x27, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72,
+	0x6f, 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e,
+	0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
 }
 
 var (
@@ -248,26 +308,28 @@ func file_proxy_shadowsocks_simplified_config_proto_rawDescGZIP() []byte {
 	return file_proxy_shadowsocks_simplified_config_proto_rawDescData
 }
 
-var file_proxy_shadowsocks_simplified_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_proxy_shadowsocks_simplified_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
 var file_proxy_shadowsocks_simplified_config_proto_goTypes = []interface{}{
 	(*ServerConfig)(nil),           // 0: v2ray.core.proxy.shadowsocks.simplified.ServerConfig
 	(*ClientConfig)(nil),           // 1: v2ray.core.proxy.shadowsocks.simplified.ClientConfig
-	(shadowsocks.CipherType)(0),    // 2: v2ray.core.proxy.shadowsocks.CipherType
+	(*CipherTypeWrapper)(nil),      // 2: v2ray.core.proxy.shadowsocks.simplified.CipherTypeWrapper
 	(*net.NetworkList)(nil),        // 3: v2ray.core.common.net.NetworkList
 	(packetaddr.PacketAddrType)(0), // 4: v2ray.core.net.packetaddr.PacketAddrType
 	(*net.IPOrDomain)(nil),         // 5: v2ray.core.common.net.IPOrDomain
+	(shadowsocks.CipherType)(0),    // 6: v2ray.core.proxy.shadowsocks.CipherType
 }
 var file_proxy_shadowsocks_simplified_config_proto_depIdxs = []int32{
-	2, // 0: v2ray.core.proxy.shadowsocks.simplified.ServerConfig.method:type_name -> v2ray.core.proxy.shadowsocks.CipherType
+	2, // 0: v2ray.core.proxy.shadowsocks.simplified.ServerConfig.method:type_name -> v2ray.core.proxy.shadowsocks.simplified.CipherTypeWrapper
 	3, // 1: v2ray.core.proxy.shadowsocks.simplified.ServerConfig.networks:type_name -> v2ray.core.common.net.NetworkList
 	4, // 2: v2ray.core.proxy.shadowsocks.simplified.ServerConfig.packet_encoding:type_name -> v2ray.core.net.packetaddr.PacketAddrType
 	5, // 3: v2ray.core.proxy.shadowsocks.simplified.ClientConfig.address:type_name -> v2ray.core.common.net.IPOrDomain
-	2, // 4: v2ray.core.proxy.shadowsocks.simplified.ClientConfig.method:type_name -> v2ray.core.proxy.shadowsocks.CipherType
-	5, // [5:5] is the sub-list for method output_type
-	5, // [5:5] is the sub-list for method input_type
-	5, // [5:5] is the sub-list for extension type_name
-	5, // [5:5] is the sub-list for extension extendee
-	0, // [0:5] is the sub-list for field type_name
+	2, // 4: v2ray.core.proxy.shadowsocks.simplified.ClientConfig.method:type_name -> v2ray.core.proxy.shadowsocks.simplified.CipherTypeWrapper
+	6, // 5: v2ray.core.proxy.shadowsocks.simplified.CipherTypeWrapper.value:type_name -> v2ray.core.proxy.shadowsocks.CipherType
+	6, // [6:6] is the sub-list for method output_type
+	6, // [6:6] is the sub-list for method input_type
+	6, // [6:6] is the sub-list for extension type_name
+	6, // [6:6] is the sub-list for extension extendee
+	0, // [0:6] is the sub-list for field type_name
 }
 
 func init() { file_proxy_shadowsocks_simplified_config_proto_init() }
@@ -300,6 +362,18 @@ func file_proxy_shadowsocks_simplified_config_proto_init() {
 				return nil
 			}
 		}
+		file_proxy_shadowsocks_simplified_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CipherTypeWrapper); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -307,7 +381,7 @@ func file_proxy_shadowsocks_simplified_config_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_proxy_shadowsocks_simplified_config_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   2,
+			NumMessages:   3,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/proxy/shadowsocks/simplified/config.proto b/proxy/shadowsocks/simplified/config.proto
index 3677862b..936a8d61 100644
--- a/proxy/shadowsocks/simplified/config.proto
+++ b/proxy/shadowsocks/simplified/config.proto
@@ -16,7 +16,7 @@ message ServerConfig{
   option (v2ray.core.common.protoext.message_opt).type = "inbound";
   option (v2ray.core.common.protoext.message_opt).short_name = "shadowsocks";
 
-  v2ray.core.proxy.shadowsocks.CipherType method = 1;
+  CipherTypeWrapper method = 1;
   string password = 2;
   v2ray.core.common.net.NetworkList networks = 3;
   v2ray.core.net.packetaddr.PacketAddrType packet_encoding = 4;
@@ -28,7 +28,11 @@ message ClientConfig {
 
   v2ray.core.common.net.IPOrDomain address = 1;
   uint32 port = 2;
-  v2ray.core.proxy.shadowsocks.CipherType method = 3;
+  CipherTypeWrapper method = 3;
   string password = 4;
   bool experiment_reduced_iv_head_entropy = 90001;
+}
+
+message CipherTypeWrapper {
+  v2ray.core.proxy.shadowsocks.CipherType value = 1;
 }
\ No newline at end of file
diff --git a/proxy/shadowsocks/simplified/errors.generated.go b/proxy/shadowsocks/simplified/errors.generated.go
new file mode 100644
index 00000000..ec7517f2
--- /dev/null
+++ b/proxy/shadowsocks/simplified/errors.generated.go
@@ -0,0 +1,9 @@
+package simplified
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/release/friendly-filenames.json b/release/friendly-filenames.json
index f395e8e2..63ea7a8f 100644
--- a/release/friendly-filenames.json
+++ b/release/friendly-filenames.json
@@ -18,6 +18,7 @@
   "linux-mipsle": { "friendlyName": "linux-mips32le" },
   "linux-mips": { "friendlyName": "linux-mips32" },
   "linux-riscv64": { "friendlyName": "linux-riscv64" },
+  "linux-loong64": { "friendlyName": "linux-loong64" },
   "openbsd-386": { "friendlyName": "openbsd-32" },
   "openbsd-amd64": { "friendlyName": "openbsd-64" },
   "openbsd-arm6": { "friendlyName": "openbsd-arm32-v6" },
@@ -28,4 +29,4 @@
   "windows-arm64": { "friendlyName": "windows-arm64-v8a" },
   "windows-arm7": { "friendlyName": "windows-arm32-v7a" },
   "android-arm64": { "friendlyName": "android-arm64-v8a" }
-}
\ No newline at end of file
+}
diff --git a/release/user-package.sh b/release/user-package.sh
index 8bfb0c4f..9c709bef 100755
--- a/release/user-package.sh
+++ b/release/user-package.sh
@@ -95,7 +95,7 @@ PKGSUFFIX=
 
 for arg in "$@"; do
 	case $arg in
-	386 | arm* | mips* | ppc64* | riscv64 | s390x)
+	386 | arm* | mips* | ppc64* | riscv64 | loong64 | s390x)
 		GOARCH=$arg
 		;;
 	windows)
diff --git a/transport/internet/domainsocket/config.go b/transport/internet/domainsocket/config.go
index 24c819e4..d80fba91 100644
--- a/transport/internet/domainsocket/config.go
+++ b/transport/internet/domainsocket/config.go
@@ -16,8 +16,8 @@ func (c *Config) GetUnixAddr() (*net.UnixAddr, error) {
 	if path == "" {
 		return nil, newError("empty domain socket path")
 	}
-	if c.Abstract && path[0] != '@' {
-		path = "@" + path
+	if c.Abstract && path[0] != '\x00' {
+		path = "\x00" + path
 	}
 	if c.Abstract && c.Padding {
 		raw := []byte(path)
diff --git a/transport/internet/domainsocket/config.pb.go b/transport/internet/domainsocket/config.pb.go
index f601c4f1..a28f962b 100644
--- a/transport/internet/domainsocket/config.pb.go
+++ b/transport/internet/domainsocket/config.pb.go
@@ -1,6 +1,7 @@
 package domainsocket
 
 import (
+	_ "github.com/v2fly/v2ray-core/v5/common/protoext"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	reflect "reflect"
@@ -92,23 +93,27 @@ var file_transport_internet_domainsocket_config_proto_rawDesc = []byte{
 	0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2a,
 	0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73,
 	0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6f,
-	0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x22, 0x52, 0x0a, 0x06, 0x43, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x62, 0x73, 0x74,
-	0x72, 0x61, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x62, 0x73, 0x74,
-	0x72, 0x61, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x9f,
-	0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72,
-	0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
-	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65,
-	0x74, 0x50, 0x01, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
-	0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
-	0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e,
-	0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63,
-	0x6b, 0x65, 0x74, 0xaa, 0x02, 0x2a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65,
-	0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72,
-	0x6e, 0x65, 0x74, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74,
-	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65,
+	0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x71, 0x0a, 0x06,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x62,
+	0x73, 0x74, 0x72, 0x61, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x62,
+	0x73, 0x74, 0x72, 0x61, 0x63, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e,
+	0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
+	0x3a, 0x1d, 0x82, 0xb5, 0x18, 0x19, 0x0a, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
+	0x74, 0x12, 0x0c, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x42,
+	0x9f, 0x01, 0x0a, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
+	0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
+	0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f, 0x63, 0x6b,
+	0x65, 0x74, 0x50, 0x01, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
+	0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
+	0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69,
+	0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x6f,
+	0x63, 0x6b, 0x65, 0x74, 0xaa, 0x02, 0x2a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72,
+	0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x6f, 0x63, 0x6b, 0x65,
+	0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/transport/internet/domainsocket/config.proto b/transport/internet/domainsocket/config.proto
index 1188b595..b6b98b4d 100644
--- a/transport/internet/domainsocket/config.proto
+++ b/transport/internet/domainsocket/config.proto
@@ -6,7 +6,12 @@ option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/domainsoc
 option java_package = "com.v2ray.core.transport.internet.domainsocket";
 option java_multiple_files = true;
 
+import "common/protoext/extensions.proto";
+
 message Config {
+  option (v2ray.core.common.protoext.message_opt).type = "transport";
+  option (v2ray.core.common.protoext.message_opt).short_name = "domainsocket";
+
   // Path of the domain socket. This overrides the IP/Port parameter from
   // upstream caller.
   string path = 1;
diff --git a/transport/internet/grpc/hub.go b/transport/internet/grpc/hub.go
index 172145b7..523b2dcc 100644
--- a/transport/internet/grpc/hub.go
+++ b/transport/internet/grpc/hub.go
@@ -23,7 +23,6 @@ type Listener struct {
 	handler internet.ConnHandler
 	local   net.Addr
 	config  *Config
-	locker  *internet.FileLocker // for unix domain socket
 
 	s *grpc.Server
 }
@@ -96,10 +95,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, settings *i
 				newError("failed to listen on ", address).Base(err).AtError().WriteToLog(session.ExportIDToError(ctx))
 				return
 			}
-			locker := ctx.Value(address.Domain())
-			if locker != nil {
-				listener.locker = locker.(*internet.FileLocker)
-			}
 		} else { // tcp
 			streamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{
 				IP:   address.IP(),
diff --git a/transport/internet/http/config.pb.go b/transport/internet/http/config.pb.go
index c7ce3c38..531e588f 100644
--- a/transport/internet/http/config.pb.go
+++ b/transport/internet/http/config.pb.go
@@ -1,6 +1,7 @@
 package http
 
 import (
+	_ "github.com/v2fly/v2ray-core/v5/common/protoext"
 	http "github.com/v2fly/v2ray-core/v5/transport/internet/headers/http"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
@@ -93,29 +94,32 @@ var file_transport_internet_http_config_proto_rawDesc = []byte{
 	0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
 	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x22, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
 	0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
-	0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x1a, 0x2c, 0x74, 0x72, 0x61, 0x6e,
-	0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68,
-	0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x94, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28,
-	0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6d,
-	0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74,
-	0x68, 0x6f, 0x64, 0x12, 0x4a, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
-	0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
-	0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70,
-	0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42,
-	0x87, 0x01, 0x0a, 0x26, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
+	0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65,
+	0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2c, 0x74, 0x72,
+	0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
+	0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x01, 0x0a, 0x06, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74,
+	0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a,
+	0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d,
+	0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x4a, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18,
+	0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
 	0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74,
-	0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x36, 0x67, 0x69,
-	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76,
-	0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61,
-	0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f,
-	0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, 0x22, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72,
-	0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65,
-	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74,
+	0x74, 0x70, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65,
+	0x72, 0x3a, 0x1b, 0x82, 0xb5, 0x18, 0x17, 0x0a, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
+	0x72, 0x74, 0x12, 0x02, 0x68, 0x32, 0x8a, 0xff, 0x29, 0x04, 0x68, 0x74, 0x74, 0x70, 0x42, 0x87,
+	0x01, 0x0a, 0x26, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72,
+	0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x36, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32,
+	0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61, 0x6e,
+	0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68,
+	0x74, 0x74, 0x70, 0xaa, 0x02, 0x22, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65,
+	0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72,
+	0x6e, 0x65, 0x74, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/transport/internet/http/config.proto b/transport/internet/http/config.proto
index add92f59..027ab9cd 100644
--- a/transport/internet/http/config.proto
+++ b/transport/internet/http/config.proto
@@ -6,9 +6,15 @@ option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/http";
 option java_package = "com.v2ray.core.transport.internet.http";
 option java_multiple_files = true;
 
+import "common/protoext/extensions.proto";
+
 import "transport/internet/headers/http/config.proto";
 
 message Config {
+  option (v2ray.core.common.protoext.message_opt).type = "transport";
+  option (v2ray.core.common.protoext.message_opt).short_name = "h2";
+
+  option (v2ray.core.common.protoext.message_opt).transport_original_name = "http";
   repeated string host = 1;
   string path = 2;
   string method = 3;
diff --git a/transport/internet/http/dialer.go b/transport/internet/http/dialer.go
index f214e957..afeae38d 100644
--- a/transport/internet/http/dialer.go
+++ b/transport/internet/http/dialer.go
@@ -3,6 +3,7 @@ package http
 import (
 	"context"
 	gotls "crypto/tls"
+	gonet "net"
 	"net/http"
 	"net/url"
 	"sync"
@@ -14,7 +15,7 @@ import (
 	"github.com/v2fly/v2ray-core/v5/common/buf"
 	"github.com/v2fly/v2ray-core/v5/common/net"
 	"github.com/v2fly/v2ray-core/v5/transport/internet"
-	"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/security"
 	"github.com/v2fly/v2ray-core/v5/transport/pipe"
 )
 
@@ -25,7 +26,7 @@ var (
 
 type dialerCanceller func()
 
-func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.Config, streamSettings *internet.MemoryStreamConfig) (*http.Client, dialerCanceller) {
+func getHTTPClient(ctx context.Context, dest net.Destination, securityEngine *security.Engine, streamSettings *internet.MemoryStreamConfig) (*http.Client, dialerCanceller) {
 	globalDialerAccess.Lock()
 	defer globalDialerAccess.Unlock()
 
@@ -44,7 +45,7 @@ func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.C
 	}
 
 	transport := &http2.Transport{
-		DialTLS: func(network string, addr string, tlsConfig *gotls.Config) (net.Conn, error) {
+		DialTLSContext: func(ctx context.Context, network, addr string, tlsConfig *gotls.Config) (gonet.Conn, error) {
 			rawHost, rawPort, err := net.SplitHostPort(addr)
 			if err != nil {
 				return nil, err
@@ -64,22 +65,26 @@ func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.C
 				return nil, err
 			}
 
-			cn := gotls.Client(pconn, tlsConfig)
-			if err := cn.Handshake(); err != nil {
+			cn, err := (*securityEngine).Client(pconn,
+				security.OptionWithDestination{Dest: dest})
+			if err != nil {
 				return nil, err
 			}
-			if !tlsConfig.InsecureSkipVerify {
-				if err := cn.VerifyHostname(tlsConfig.ServerName); err != nil {
-					return nil, err
+
+			protocol := ""
+			if connAPLNGetter, ok := cn.(security.ConnectionApplicationProtocol); ok {
+				connectionALPN, err := connAPLNGetter.GetConnectionApplicationProtocol()
+				if err != nil {
+					return nil, newError("failed to get connection ALPN").Base(err).AtWarning()
 				}
+				protocol = connectionALPN
 			}
-			state := cn.ConnectionState()
-			if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
-				return nil, newError("http2: unexpected ALPN protocol " + p + "; want q" + http2.NextProtoTLS).AtError()
+
+			if protocol != http2.NextProtoTLS {
+				return nil, newError("http2: unexpected ALPN protocol " + protocol + "; want q" + http2.NextProtoTLS).AtError()
 			}
 			return cn, nil
 		},
-		TLSClientConfig: tlsSettings.GetTLSConfig(tls.WithDestination(dest)),
 	}
 
 	client := &http.Client{
@@ -93,11 +98,14 @@ func getHTTPClient(ctx context.Context, dest net.Destination, tlsSettings *tls.C
 // Dial dials a new TCP connection to the given destination.
 func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
 	httpSettings := streamSettings.ProtocolSettings.(*Config)
-	tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
-	if tlsConfig == nil {
+	securityEngine, err := security.CreateSecurityEngineFromSettings(ctx, streamSettings)
+	if err != nil {
+		return nil, newError("unable to create security engine").Base(err)
+	}
+	if securityEngine == nil {
 		return nil, newError("TLS must be enabled for http transport.").AtWarning()
 	}
-	client, canceller := getHTTPClient(ctx, dest, tlsConfig, streamSettings)
+	client, canceller := getHTTPClient(ctx, dest, &securityEngine, streamSettings)
 
 	opts := pipe.OptionsFromContext(ctx)
 	preader, pwriter := pipe.New(opts...)
diff --git a/transport/internet/http/hub.go b/transport/internet/http/hub.go
index b41aac01..80fd70a7 100644
--- a/transport/internet/http/hub.go
+++ b/transport/internet/http/hub.go
@@ -25,7 +25,6 @@ type Listener struct {
 	handler internet.ConnHandler
 	local   net.Addr
 	config  *Config
-	locker  *internet.FileLocker // for unix domain socket
 }
 
 func (l *Listener) Addr() net.Addr {
@@ -33,9 +32,6 @@ func (l *Listener) Addr() net.Addr {
 }
 
 func (l *Listener) Close() error {
-	if l.locker != nil {
-		l.locker.Release()
-	}
 	return l.server.Close()
 }
 
@@ -171,10 +167,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
 				newError("failed to listen on ", address).Base(err).AtError().WriteToLog(session.ExportIDToError(ctx))
 				return
 			}
-			locker := ctx.Value(address.Domain())
-			if locker != nil {
-				listener.locker = locker.(*internet.FileLocker)
-			}
 		} else { // tcp
 			streamListener, err = internet.ListenSystem(ctx, &net.TCPAddr{
 				IP:   address.IP(),
diff --git a/transport/internet/request/roundtripper/httprt/httprt.go b/transport/internet/request/roundtripper/httprt/httprt.go
index a6551666..30f1ad66 100644
--- a/transport/internet/request/roundtripper/httprt/httprt.go
+++ b/transport/internet/request/roundtripper/httprt/httprt.go
@@ -75,8 +75,7 @@ type httpTripperServer struct {
 	listener net.Listener
 	assembly request.TransportServerAssembly
 
-	listenAddress net.Addr
-	config        *ServerConfig
+	config *ServerConfig
 }
 
 func (h *httpTripperServer) OnTransportServerAssemblyReady(assembly request.TransportServerAssembly) {
diff --git a/transport/internet/system_listener.go b/transport/internet/system_listener.go
index 5ddb4814..ef54228d 100644
--- a/transport/internet/system_listener.go
+++ b/transport/internet/system_listener.go
@@ -2,7 +2,10 @@ package internet
 
 import (
 	"context"
+	"os"
 	"runtime"
+	"strconv"
+	"strings"
 	"syscall"
 	"time"
 
@@ -20,6 +23,19 @@ type DefaultListener struct {
 	controllers []controller
 }
 
+type combinedListener struct {
+	net.Listener
+	locker *FileLocker // for unix domain socket
+}
+
+func (l *combinedListener) Close() error {
+	if l.locker != nil {
+		l.locker.Release()
+		l.locker = nil
+	}
+	return l.Listener.Close()
+}
+
 func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []controller) func(network, address string, c syscall.RawConn) error {
 	return func(network, address string, c syscall.RawConn) error {
 		return c.Control(func(fd uintptr) {
@@ -42,9 +58,12 @@ func getControlFunc(ctx context.Context, sockopt *SocketConfig, controllers []co
 
 func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *SocketConfig) (net.Listener, error) {
 	var lc net.ListenConfig
-	var l net.Listener
-	var err error
 	var network, address string
+	// callback is called after the Listen function returns
+	// this is used to wrap the listener and do some post processing
+	callback := func(l net.Listener, err error) (net.Listener, error) {
+		return l, err
+	}
 	switch addr := addr.(type) {
 	case *net.TCPAddr:
 		network = addr.Network()
@@ -66,20 +85,48 @@ func (dl *DefaultListener) Listen(ctx context.Context, addr net.Addr, sockopt *S
 				address = string(fullAddr)
 			}
 		} else {
+			// normal unix domain socket
+			var fileMode *os.FileMode
+			// parse file mode from address
+			if s := strings.Split(address, ","); len(s) == 2 {
+				fMode, err := strconv.ParseUint(s[1], 8, 32)
+				if err != nil {
+					return nil, newError("failed to parse file mode").Base(err)
+				}
+				address = s[0]
+				fm := os.FileMode(fMode)
+				fileMode = &fm
+			}
 			// normal unix domain socket needs lock
 			locker := &FileLocker{
 				path: address + ".lock",
 			}
-			err := locker.Acquire()
-			if err != nil {
+			if err := locker.Acquire(); err != nil {
 				return nil, err
 			}
-			ctx = context.WithValue(ctx, address, locker) // nolint: revive,staticcheck
+			// set file mode for unix domain socket when it is created
+			callback = func(l net.Listener, err error) (net.Listener, error) {
+				if err != nil {
+					locker.Release()
+					return nil, err
+				}
+				l = &combinedListener{Listener: l, locker: locker}
+				if fileMode == nil {
+					return l, err
+				}
+				if cerr := os.Chmod(address, *fileMode); cerr != nil {
+					// failed to set file mode, close the listener
+					l.Close()
+					return nil, newError("failed to set file mode for file: ", address).Base(cerr)
+				}
+				return l, err
+			}
 		}
 	}
 
-	l, err = lc.Listen(ctx, network, address)
-	if sockopt != nil && sockopt.AcceptProxyProtocol {
+	l, err := lc.Listen(ctx, network, address)
+	l, err = callback(l, err)
+	if err == nil && sockopt != nil && sockopt.AcceptProxyProtocol {
 		policyFunc := func(upstream net.Addr) (proxyproto.Policy, error) { return proxyproto.REQUIRE, nil }
 		l = &proxyproto.Listener{Listener: l, Policy: policyFunc}
 	}
diff --git a/transport/internet/tcp/hub.go b/transport/internet/tcp/hub.go
index 347a8efb..5fd563dd 100644
--- a/transport/internet/tcp/hub.go
+++ b/transport/internet/tcp/hub.go
@@ -21,7 +21,6 @@ type Listener struct {
 	authConfig internet.ConnectionAuthenticator
 	config     *Config
 	addConn    internet.ConnHandler
-	locker     *internet.FileLocker // for unix domain socket
 }
 
 // ListenTCP creates a new Listener based on configurations.
@@ -48,10 +47,6 @@ func ListenTCP(ctx context.Context, address net.Address, port net.Port, streamSe
 			return nil, newError("failed to listen Unix Domain Socket on ", address).Base(err)
 		}
 		newError("listening Unix Domain Socket on ", address).WriteToLog(session.ExportIDToError(ctx))
-		locker := ctx.Value(address.Domain())
-		if locker != nil {
-			l.locker = locker.(*internet.FileLocker)
-		}
 	} else {
 		listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
 			IP:   address.IP(),
@@ -122,9 +117,6 @@ func (v *Listener) Addr() net.Addr {
 
 // Close implements internet.Listener.Close.
 func (v *Listener) Close() error {
-	if v.locker != nil {
-		v.locker.Release()
-	}
 	return v.listener.Close()
 }
 
diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go
index 38931531..14a8d77a 100644
--- a/transport/internet/tls/tls.go
+++ b/transport/internet/tls/tls.go
@@ -43,7 +43,7 @@ func (c *Conn) HandshakeAddress() net.Address {
 }
 
 // Client initiates a TLS client handshake on the given connection.
-func Client(c net.Conn, config *tls.Config) net.Conn {
+func Client(c net.Conn, config *tls.Config) *Conn {
 	tlsConn := tls.Client(c, config)
 	return &Conn{Conn: tlsConn}
 }
diff --git a/transport/internet/transportcommon/listener.go b/transport/internet/transportcommon/listener.go
index 0aae4251..265ba3f0 100644
--- a/transport/internet/transportcommon/listener.go
+++ b/transport/internet/transportcommon/listener.go
@@ -10,22 +10,10 @@ import (
 	"github.com/v2fly/v2ray-core/v5/transport/internet"
 )
 
-type combinedListener struct {
-	net.Listener
-	locker *internet.FileLocker
-}
-
-func (l *combinedListener) Close() error {
-	if l.locker != nil {
-		l.locker.Release()
-	}
-	return l.Listener.Close()
-}
-
 func ListenWithSecuritySettings(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig) (
 	net.Listener, error,
 ) {
-	var l combinedListener
+	var l net.Listener
 
 	transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment)
 	transportListener := transportEnvironment.Listener()
@@ -39,11 +27,7 @@ func ListenWithSecuritySettings(ctx context.Context, address net.Address, port n
 			return nil, newError("failed to listen unix domain socket on ", address).Base(err)
 		}
 		newError("listening unix domain socket on ", address).WriteToLog(session.ExportIDToError(ctx))
-		locker := ctx.Value(address.Domain())
-		if locker != nil {
-			l.locker = locker.(*internet.FileLocker)
-		}
-		l.Listener = listener
+		l = listener
 	} else { // tcp
 		listener, err := transportListener.Listen(ctx, &net.TCPAddr{
 			IP:   address.IP(),
@@ -53,11 +37,11 @@ func ListenWithSecuritySettings(ctx context.Context, address net.Address, port n
 			return nil, newError("failed to listen TCP on ", address, ":", port).Base(err)
 		}
 		newError("listening TCP on ", address, ":", port).WriteToLog(session.ExportIDToError(ctx))
-		l.Listener = listener
+		l = listener
 	}
 
 	if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {
 		newError("accepting PROXY protocol").AtWarning().WriteToLog(session.ExportIDToError(ctx))
 	}
-	return &l, nil
+	return l, nil
 }
diff --git a/transport/internet/udp/dispatcher.go b/transport/internet/udp/dispatcher.go
index c52021c6..ca7cb8cf 100644
--- a/transport/internet/udp/dispatcher.go
+++ b/transport/internet/udp/dispatcher.go
@@ -2,6 +2,7 @@ package udp
 
 import (
 	"context"
+	"io"
 
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common/buf"
@@ -12,3 +13,9 @@ type DispatcherI interface {
 	common.Closable
 	Dispatch(ctx context.Context, destination net.Destination, payload *buf.Buffer)
 }
+
+var DispatcherConnectionTerminationSignalReceiverMark = "DispatcherConnectionTerminationSignalReceiverMark"
+
+type DispatcherConnectionTerminationSignalReceiver interface {
+	io.Closer
+}
diff --git a/transport/internet/udp/dispatcher_packetaddr.go b/transport/internet/udp/dispatcher_packetaddr.go
index f59422aa..93844740 100644
--- a/transport/internet/udp/dispatcher_packetaddr.go
+++ b/transport/internet/udp/dispatcher_packetaddr.go
@@ -17,6 +17,9 @@ type PacketAddrDispatcher struct {
 }
 
 func (p PacketAddrDispatcher) Close() error {
+	if p.ctx.Value(DispatcherConnectionTerminationSignalReceiverMark) != nil {
+		p.ctx.Value(DispatcherConnectionTerminationSignalReceiverMark).(DispatcherConnectionTerminationSignalReceiver).Close()
+	}
 	return p.conn.Close()
 }
 
diff --git a/transport/internet/websocket/hub.go b/transport/internet/websocket/hub.go
index d3775334..666b5797 100644
--- a/transport/internet/websocket/hub.go
+++ b/transport/internet/websocket/hub.go
@@ -93,7 +93,6 @@ type Listener struct {
 	listener net.Listener
 	config   *Config
 	addConn  internet.ConnHandler
-	locker   *internet.FileLocker // for unix domain socket
 }
 
 func ListenWS(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {
@@ -119,10 +118,6 @@ func ListenWS(ctx context.Context, address net.Address, port net.Port, streamSet
 			return nil, newError("failed to listen unix domain socket(for WS) on ", address).Base(err)
 		}
 		newError("listening unix domain socket(for WS) on ", address).WriteToLog(session.ExportIDToError(ctx))
-		locker := ctx.Value(address.Domain())
-		if locker != nil {
-			l.locker = locker.(*internet.FileLocker)
-		}
 	} else { // tcp
 		listener, err = internet.ListenSystem(ctx, &net.TCPAddr{
 			IP:   address.IP(),
@@ -179,9 +174,6 @@ func (ln *Listener) Addr() net.Addr {
 
 // Close implements net.Listener.Close().
 func (ln *Listener) Close() error {
-	if ln.locker != nil {
-		ln.locker.Release()
-	}
 	return ln.listener.Close()
 }
 


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 983a9b17..827263a9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -28,7 +28,7 @@ jobs:
         os: [windows-latest, ubuntu-latest, macos-latest]
     steps:
       - name: Set up Go 1.x
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v4
         with:
           go-version: ^1.19
 
diff --git a/common/buf/multi_buffer_test.go b/common/buf/multi_buffer_test.go
index 6d835c82..c4c85c38 100644
--- a/common/buf/multi_buffer_test.go
+++ b/common/buf/multi_buffer_test.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"crypto/rand"
 	"io"
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -120,7 +119,7 @@ func TestMultiBufferReadAllToByte(t *testing.T) {
 		common.Must(err)
 		f.Close()
 
-		cnt, err := ioutil.ReadFile(dat)
+		cnt, err := os.ReadFile(dat)
 		common.Must(err)
 
 		if d := cmp.Diff(buf2, cnt); d != "" {
diff --git a/common/log/logger_test.go b/common/log/logger_test.go
index 3e430226..aa130ba9 100644
--- a/common/log/logger_test.go
+++ b/common/log/logger_test.go
@@ -1,7 +1,6 @@
 package log_test
 
 import (
-	"io/ioutil"
 	"os"
 	"strings"
 	"testing"
@@ -13,7 +12,7 @@ import (
 )
 
 func TestFileLogger(t *testing.T) {
-	f, err := ioutil.TempFile("", "vtest")
+	f, err := os.CreateTemp("", "vtest")
 	common.Must(err)
 	path := f.Name()
 	common.Must(f.Close())