浏览代码

Refactor inbound/outbound options struct

世界 1 年之前
父节点
当前提交
6ddcd3954d

+ 13 - 37
cmd/sing-box/cmd_merge.go

@@ -65,50 +65,26 @@ func merge(outputPath string) error {
 
 func mergePathResources(options *option.Options) error {
 	for index, inbound := range options.Inbounds {
-		switch inbound.Type {
-		case C.TypeHTTP:
-			inbound.HTTPOptions.TLS = mergeTLSInboundOptions(inbound.HTTPOptions.TLS)
-		case C.TypeMixed:
-			inbound.MixedOptions.TLS = mergeTLSInboundOptions(inbound.MixedOptions.TLS)
-		case C.TypeVMess:
-			inbound.VMessOptions.TLS = mergeTLSInboundOptions(inbound.VMessOptions.TLS)
-		case C.TypeTrojan:
-			inbound.TrojanOptions.TLS = mergeTLSInboundOptions(inbound.TrojanOptions.TLS)
-		case C.TypeNaive:
-			inbound.NaiveOptions.TLS = mergeTLSInboundOptions(inbound.NaiveOptions.TLS)
-		case C.TypeHysteria:
-			inbound.HysteriaOptions.TLS = mergeTLSInboundOptions(inbound.HysteriaOptions.TLS)
-		case C.TypeVLESS:
-			inbound.VLESSOptions.TLS = mergeTLSInboundOptions(inbound.VLESSOptions.TLS)
-		case C.TypeTUIC:
-			inbound.TUICOptions.TLS = mergeTLSInboundOptions(inbound.TUICOptions.TLS)
-		case C.TypeHysteria2:
-			inbound.Hysteria2Options.TLS = mergeTLSInboundOptions(inbound.Hysteria2Options.TLS)
-		default:
-			continue
+		rawOptions, err := inbound.RawOptions()
+		if err != nil {
+			return err
+		}
+		if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
+			tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
 		}
 		options.Inbounds[index] = inbound
 	}
 	for index, outbound := range options.Outbounds {
+		rawOptions, err := outbound.RawOptions()
+		if err != nil {
+			return err
+		}
 		switch outbound.Type {
-		case C.TypeHTTP:
-			outbound.HTTPOptions.TLS = mergeTLSOutboundOptions(outbound.HTTPOptions.TLS)
-		case C.TypeVMess:
-			outbound.VMessOptions.TLS = mergeTLSOutboundOptions(outbound.VMessOptions.TLS)
-		case C.TypeTrojan:
-			outbound.TrojanOptions.TLS = mergeTLSOutboundOptions(outbound.TrojanOptions.TLS)
-		case C.TypeHysteria:
-			outbound.HysteriaOptions.TLS = mergeTLSOutboundOptions(outbound.HysteriaOptions.TLS)
 		case C.TypeSSH:
 			outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
-		case C.TypeVLESS:
-			outbound.VLESSOptions.TLS = mergeTLSOutboundOptions(outbound.VLESSOptions.TLS)
-		case C.TypeTUIC:
-			outbound.TUICOptions.TLS = mergeTLSOutboundOptions(outbound.TUICOptions.TLS)
-		case C.TypeHysteria2:
-			outbound.Hysteria2Options.TLS = mergeTLSOutboundOptions(outbound.Hysteria2Options.TLS)
-		default:
-			continue
+		}
+		if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
+			tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
 		}
 		options.Outbounds[index] = outbound
 	}

+ 23 - 23
option/hysteria.go

@@ -2,17 +2,17 @@ package option
 
 type HysteriaInboundOptions struct {
 	ListenOptions
-	Up                  string             `json:"up,omitempty"`
-	UpMbps              int                `json:"up_mbps,omitempty"`
-	Down                string             `json:"down,omitempty"`
-	DownMbps            int                `json:"down_mbps,omitempty"`
-	Obfs                string             `json:"obfs,omitempty"`
-	Users               []HysteriaUser     `json:"users,omitempty"`
-	ReceiveWindowConn   uint64             `json:"recv_window_conn,omitempty"`
-	ReceiveWindowClient uint64             `json:"recv_window_client,omitempty"`
-	MaxConnClient       int                `json:"max_conn_client,omitempty"`
-	DisableMTUDiscovery bool               `json:"disable_mtu_discovery,omitempty"`
-	TLS                 *InboundTLSOptions `json:"tls,omitempty"`
+	Up                  string         `json:"up,omitempty"`
+	UpMbps              int            `json:"up_mbps,omitempty"`
+	Down                string         `json:"down,omitempty"`
+	DownMbps            int            `json:"down_mbps,omitempty"`
+	Obfs                string         `json:"obfs,omitempty"`
+	Users               []HysteriaUser `json:"users,omitempty"`
+	ReceiveWindowConn   uint64         `json:"recv_window_conn,omitempty"`
+	ReceiveWindowClient uint64         `json:"recv_window_client,omitempty"`
+	MaxConnClient       int            `json:"max_conn_client,omitempty"`
+	DisableMTUDiscovery bool           `json:"disable_mtu_discovery,omitempty"`
+	InboundTLSOptionsContainer
 }
 
 type HysteriaUser struct {
@@ -24,16 +24,16 @@ type HysteriaUser struct {
 type HysteriaOutboundOptions struct {
 	DialerOptions
 	ServerOptions
-	Up                  string              `json:"up,omitempty"`
-	UpMbps              int                 `json:"up_mbps,omitempty"`
-	Down                string              `json:"down,omitempty"`
-	DownMbps            int                 `json:"down_mbps,omitempty"`
-	Obfs                string              `json:"obfs,omitempty"`
-	Auth                []byte              `json:"auth,omitempty"`
-	AuthString          string              `json:"auth_str,omitempty"`
-	ReceiveWindowConn   uint64              `json:"recv_window_conn,omitempty"`
-	ReceiveWindow       uint64              `json:"recv_window,omitempty"`
-	DisableMTUDiscovery bool                `json:"disable_mtu_discovery,omitempty"`
-	Network             NetworkList         `json:"network,omitempty"`
-	TLS                 *OutboundTLSOptions `json:"tls,omitempty"`
+	Up                  string      `json:"up,omitempty"`
+	UpMbps              int         `json:"up_mbps,omitempty"`
+	Down                string      `json:"down,omitempty"`
+	DownMbps            int         `json:"down_mbps,omitempty"`
+	Obfs                string      `json:"obfs,omitempty"`
+	Auth                []byte      `json:"auth,omitempty"`
+	AuthString          string      `json:"auth_str,omitempty"`
+	ReceiveWindowConn   uint64      `json:"recv_window_conn,omitempty"`
+	ReceiveWindow       uint64      `json:"recv_window,omitempty"`
+	DisableMTUDiscovery bool        `json:"disable_mtu_discovery,omitempty"`
+	Network             NetworkList `json:"network,omitempty"`
+	OutboundTLSOptionsContainer
 }

+ 15 - 15
option/hysteria2.go

@@ -2,14 +2,14 @@ package option
 
 type Hysteria2InboundOptions struct {
 	ListenOptions
-	UpMbps                int                `json:"up_mbps,omitempty"`
-	DownMbps              int                `json:"down_mbps,omitempty"`
-	Obfs                  *Hysteria2Obfs     `json:"obfs,omitempty"`
-	Users                 []Hysteria2User    `json:"users,omitempty"`
-	IgnoreClientBandwidth bool               `json:"ignore_client_bandwidth,omitempty"`
-	TLS                   *InboundTLSOptions `json:"tls,omitempty"`
-	Masquerade            string             `json:"masquerade,omitempty"`
-	BrutalDebug           bool               `json:"brutal_debug,omitempty"`
+	UpMbps                int             `json:"up_mbps,omitempty"`
+	DownMbps              int             `json:"down_mbps,omitempty"`
+	Obfs                  *Hysteria2Obfs  `json:"obfs,omitempty"`
+	Users                 []Hysteria2User `json:"users,omitempty"`
+	IgnoreClientBandwidth bool            `json:"ignore_client_bandwidth,omitempty"`
+	InboundTLSOptionsContainer
+	Masquerade  string `json:"masquerade,omitempty"`
+	BrutalDebug bool   `json:"brutal_debug,omitempty"`
 }
 
 type Hysteria2Obfs struct {
@@ -25,11 +25,11 @@ type Hysteria2User struct {
 type Hysteria2OutboundOptions struct {
 	DialerOptions
 	ServerOptions
-	UpMbps      int                 `json:"up_mbps,omitempty"`
-	DownMbps    int                 `json:"down_mbps,omitempty"`
-	Obfs        *Hysteria2Obfs      `json:"obfs,omitempty"`
-	Password    string              `json:"password,omitempty"`
-	Network     NetworkList         `json:"network,omitempty"`
-	TLS         *OutboundTLSOptions `json:"tls,omitempty"`
-	BrutalDebug bool                `json:"brutal_debug,omitempty"`
+	UpMbps   int            `json:"up_mbps,omitempty"`
+	DownMbps int            `json:"down_mbps,omitempty"`
+	Obfs     *Hysteria2Obfs `json:"obfs,omitempty"`
+	Password string         `json:"password,omitempty"`
+	Network  NetworkList    `json:"network,omitempty"`
+	OutboundTLSOptionsContainer
+	BrutalDebug bool `json:"brutal_debug,omitempty"`
 }

+ 46 - 58
option/inbound.go

@@ -31,45 +31,55 @@ type _Inbound struct {
 
 type Inbound _Inbound
 
-func (h Inbound) MarshalJSON() ([]byte, error) {
-	var v any
+func (h *Inbound) RawOptions() (any, error) {
+	var rawOptionsPtr any
 	switch h.Type {
 	case C.TypeTun:
-		v = h.TunOptions
+		rawOptionsPtr = &h.TunOptions
 	case C.TypeRedirect:
-		v = h.RedirectOptions
+		rawOptionsPtr = &h.RedirectOptions
 	case C.TypeTProxy:
-		v = h.TProxyOptions
+		rawOptionsPtr = &h.TProxyOptions
 	case C.TypeDirect:
-		v = h.DirectOptions
+		rawOptionsPtr = &h.DirectOptions
 	case C.TypeSOCKS:
-		v = h.SocksOptions
+		rawOptionsPtr = &h.SocksOptions
 	case C.TypeHTTP:
-		v = h.HTTPOptions
+		rawOptionsPtr = &h.HTTPOptions
 	case C.TypeMixed:
-		v = h.MixedOptions
+		rawOptionsPtr = &h.MixedOptions
 	case C.TypeShadowsocks:
-		v = h.ShadowsocksOptions
+		rawOptionsPtr = &h.ShadowsocksOptions
 	case C.TypeVMess:
-		v = h.VMessOptions
+		rawOptionsPtr = &h.VMessOptions
 	case C.TypeTrojan:
-		v = h.TrojanOptions
+		rawOptionsPtr = &h.TrojanOptions
 	case C.TypeNaive:
-		v = h.NaiveOptions
+		rawOptionsPtr = &h.NaiveOptions
 	case C.TypeHysteria:
-		v = h.HysteriaOptions
+		rawOptionsPtr = &h.HysteriaOptions
 	case C.TypeShadowTLS:
-		v = h.ShadowTLSOptions
+		rawOptionsPtr = &h.ShadowTLSOptions
 	case C.TypeVLESS:
-		v = h.VLESSOptions
+		rawOptionsPtr = &h.VLESSOptions
 	case C.TypeTUIC:
-		v = h.TUICOptions
+		rawOptionsPtr = &h.TUICOptions
 	case C.TypeHysteria2:
-		v = h.Hysteria2Options
+		rawOptionsPtr = &h.Hysteria2Options
+	case "":
+		return nil, E.New("missing inbound type")
 	default:
 		return nil, E.New("unknown inbound type: ", h.Type)
 	}
-	return MarshallObjects((_Inbound)(h), v)
+	return rawOptionsPtr, nil
+}
+
+func (h Inbound) MarshalJSON() ([]byte, error) {
+	rawOptions, err := h.RawOptions()
+	if err != nil {
+		return nil, err
+	}
+	return MarshallObjects((_Inbound)(h), rawOptions)
 }
 
 func (h *Inbound) UnmarshalJSON(bytes []byte) error {
@@ -77,46 +87,11 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
 	if err != nil {
 		return err
 	}
-	var v any
-	switch h.Type {
-	case C.TypeTun:
-		v = &h.TunOptions
-	case C.TypeRedirect:
-		v = &h.RedirectOptions
-	case C.TypeTProxy:
-		v = &h.TProxyOptions
-	case C.TypeDirect:
-		v = &h.DirectOptions
-	case C.TypeSOCKS:
-		v = &h.SocksOptions
-	case C.TypeHTTP:
-		v = &h.HTTPOptions
-	case C.TypeMixed:
-		v = &h.MixedOptions
-	case C.TypeShadowsocks:
-		v = &h.ShadowsocksOptions
-	case C.TypeVMess:
-		v = &h.VMessOptions
-	case C.TypeTrojan:
-		v = &h.TrojanOptions
-	case C.TypeNaive:
-		v = &h.NaiveOptions
-	case C.TypeHysteria:
-		v = &h.HysteriaOptions
-	case C.TypeShadowTLS:
-		v = &h.ShadowTLSOptions
-	case C.TypeVLESS:
-		v = &h.VLESSOptions
-	case C.TypeTUIC:
-		v = &h.TUICOptions
-	case C.TypeHysteria2:
-		v = &h.Hysteria2Options
-	case "":
-		return E.New("missing inbound type")
-	default:
-		return E.New("unknown inbound type: ", h.Type)
+	rawOptions, err := h.RawOptions()
+	if err != nil {
+		return err
 	}
-	err = UnmarshallExcluded(bytes, (*_Inbound)(h), v)
+	err = UnmarshallExcluded(bytes, (*_Inbound)(h), rawOptions)
 	if err != nil {
 		return err
 	}
@@ -160,3 +135,16 @@ func (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error {
 	}
 	return json.Unmarshal(data, (*Duration)(c))
 }
+
+type ListenOptionsWrapper interface {
+	TakeListenOptions() ListenOptions
+	ReplaceListenOptions(options ListenOptions)
+}
+
+func (o *ListenOptions) TakeListenOptions() ListenOptions {
+	return *o
+}
+
+func (o *ListenOptions) ReplaceListenOptions(options ListenOptions) {
+	*o = options
+}

+ 3 - 3
option/naive.go

@@ -4,7 +4,7 @@ import "github.com/sagernet/sing/common/auth"
 
 type NaiveInboundOptions struct {
 	ListenOptions
-	Users   []auth.User        `json:"users,omitempty"`
-	Network NetworkList        `json:"network,omitempty"`
-	TLS     *InboundTLSOptions `json:"tls,omitempty"`
+	Users   []auth.User `json:"users,omitempty"`
+	Network NetworkList `json:"network,omitempty"`
+	InboundTLSOptionsContainer
 }

+ 61 - 64
option/outbound.go

@@ -31,49 +31,59 @@ type _Outbound struct {
 
 type Outbound _Outbound
 
-func (h Outbound) MarshalJSON() ([]byte, error) {
-	var v any
+func (h *Outbound) RawOptions() (any, error) {
+	var rawOptionsPtr any
 	switch h.Type {
 	case C.TypeDirect:
-		v = h.DirectOptions
+		rawOptionsPtr = &h.DirectOptions
 	case C.TypeBlock, C.TypeDNS:
-		v = nil
+		rawOptionsPtr = nil
 	case C.TypeSOCKS:
-		v = h.SocksOptions
+		rawOptionsPtr = &h.SocksOptions
 	case C.TypeHTTP:
-		v = h.HTTPOptions
+		rawOptionsPtr = &h.HTTPOptions
 	case C.TypeShadowsocks:
-		v = h.ShadowsocksOptions
+		rawOptionsPtr = &h.ShadowsocksOptions
 	case C.TypeVMess:
-		v = h.VMessOptions
+		rawOptionsPtr = &h.VMessOptions
 	case C.TypeTrojan:
-		v = h.TrojanOptions
+		rawOptionsPtr = &h.TrojanOptions
 	case C.TypeWireGuard:
-		v = h.WireGuardOptions
+		rawOptionsPtr = &h.WireGuardOptions
 	case C.TypeHysteria:
-		v = h.HysteriaOptions
+		rawOptionsPtr = &h.HysteriaOptions
 	case C.TypeTor:
-		v = h.TorOptions
+		rawOptionsPtr = &h.TorOptions
 	case C.TypeSSH:
-		v = h.SSHOptions
+		rawOptionsPtr = &h.SSHOptions
 	case C.TypeShadowTLS:
-		v = h.ShadowTLSOptions
+		rawOptionsPtr = &h.ShadowTLSOptions
 	case C.TypeShadowsocksR:
-		v = h.ShadowsocksROptions
+		rawOptionsPtr = &h.ShadowsocksROptions
 	case C.TypeVLESS:
-		v = h.VLESSOptions
+		rawOptionsPtr = &h.VLESSOptions
 	case C.TypeTUIC:
-		v = h.TUICOptions
+		rawOptionsPtr = &h.TUICOptions
 	case C.TypeHysteria2:
-		v = h.Hysteria2Options
+		rawOptionsPtr = &h.Hysteria2Options
 	case C.TypeSelector:
-		v = h.SelectorOptions
+		rawOptionsPtr = &h.SelectorOptions
 	case C.TypeURLTest:
-		v = h.URLTestOptions
+		rawOptionsPtr = &h.URLTestOptions
+	case "":
+		return nil, E.New("missing outbound type")
 	default:
 		return nil, E.New("unknown outbound type: ", h.Type)
 	}
-	return MarshallObjects((_Outbound)(h), v)
+	return rawOptionsPtr, nil
+}
+
+func (h *Outbound) MarshalJSON() ([]byte, error) {
+	rawOptions, err := h.RawOptions()
+	if err != nil {
+		return nil, err
+	}
+	return MarshallObjects((*_Outbound)(h), rawOptions)
 }
 
 func (h *Outbound) UnmarshalJSON(bytes []byte) error {
@@ -81,56 +91,22 @@ func (h *Outbound) UnmarshalJSON(bytes []byte) error {
 	if err != nil {
 		return err
 	}
-	var v any
-	switch h.Type {
-	case C.TypeDirect:
-		v = &h.DirectOptions
-	case C.TypeBlock, C.TypeDNS:
-		v = nil
-	case C.TypeSOCKS:
-		v = &h.SocksOptions
-	case C.TypeHTTP:
-		v = &h.HTTPOptions
-	case C.TypeShadowsocks:
-		v = &h.ShadowsocksOptions
-	case C.TypeVMess:
-		v = &h.VMessOptions
-	case C.TypeTrojan:
-		v = &h.TrojanOptions
-	case C.TypeWireGuard:
-		v = &h.WireGuardOptions
-	case C.TypeHysteria:
-		v = &h.HysteriaOptions
-	case C.TypeTor:
-		v = &h.TorOptions
-	case C.TypeSSH:
-		v = &h.SSHOptions
-	case C.TypeShadowTLS:
-		v = &h.ShadowTLSOptions
-	case C.TypeShadowsocksR:
-		v = &h.ShadowsocksROptions
-	case C.TypeVLESS:
-		v = &h.VLESSOptions
-	case C.TypeTUIC:
-		v = &h.TUICOptions
-	case C.TypeHysteria2:
-		v = &h.Hysteria2Options
-	case C.TypeSelector:
-		v = &h.SelectorOptions
-	case C.TypeURLTest:
-		v = &h.URLTestOptions
-	case "":
-		return E.New("missing outbound type")
-	default:
-		return E.New("unknown outbound type: ", h.Type)
+	rawOptions, err := h.RawOptions()
+	if err != nil {
+		return err
 	}
-	err = UnmarshallExcluded(bytes, (*_Outbound)(h), v)
+	err = UnmarshallExcluded(bytes, (*_Outbound)(h), rawOptions)
 	if err != nil {
 		return err
 	}
 	return nil
 }
 
+type DialerOptionsWrapper interface {
+	TakeDialerOptions() DialerOptions
+	ReplaceDialerOptions(options DialerOptions)
+}
+
 type DialerOptions struct {
 	Detour             string         `json:"detour,omitempty"`
 	BindInterface      string         `json:"bind_interface,omitempty"`
@@ -148,6 +124,19 @@ type DialerOptions struct {
 	FallbackDelay      Duration       `json:"fallback_delay,omitempty"`
 }
 
+func (o *DialerOptions) TakeDialerOptions() DialerOptions {
+	return *o
+}
+
+func (o *DialerOptions) ReplaceDialerOptions(options DialerOptions) {
+	*o = options
+}
+
+type ServerOptionsWrapper interface {
+	TakeServerOptions() ServerOptions
+	ReplaceServerOptions(options ServerOptions)
+}
+
 type ServerOptions struct {
 	Server     string `json:"server"`
 	ServerPort uint16 `json:"server_port"`
@@ -156,3 +145,11 @@ type ServerOptions struct {
 func (o ServerOptions) Build() M.Socksaddr {
 	return M.ParseSocksaddrHostPort(o.Server, o.ServerPort)
 }
+
+func (o *ServerOptions) TakeServerOptions() ServerOptions {
+	return *o
+}
+
+func (o *ServerOptions) ReplaceServerOptions(options ServerOptions) {
+	*o = options
+}

+ 3 - 3
option/shadowtls.go

@@ -23,7 +23,7 @@ type ShadowTLSHandshakeOptions struct {
 type ShadowTLSOutboundOptions struct {
 	DialerOptions
 	ServerOptions
-	Version  int                 `json:"version,omitempty"`
-	Password string              `json:"password,omitempty"`
-	TLS      *OutboundTLSOptions `json:"tls,omitempty"`
+	Version  int    `json:"version,omitempty"`
+	Password string `json:"password,omitempty"`
+	OutboundTLSOptionsContainer
 }

+ 8 - 8
option/simple.go

@@ -9,9 +9,9 @@ type SocksInboundOptions struct {
 
 type HTTPMixedInboundOptions struct {
 	ListenOptions
-	Users          []auth.User        `json:"users,omitempty"`
-	SetSystemProxy bool               `json:"set_system_proxy,omitempty"`
-	TLS            *InboundTLSOptions `json:"tls,omitempty"`
+	Users          []auth.User `json:"users,omitempty"`
+	SetSystemProxy bool        `json:"set_system_proxy,omitempty"`
+	InboundTLSOptionsContainer
 }
 
 type SocksOutboundOptions struct {
@@ -27,9 +27,9 @@ type SocksOutboundOptions struct {
 type HTTPOutboundOptions struct {
 	DialerOptions
 	ServerOptions
-	Username string              `json:"username,omitempty"`
-	Password string              `json:"password,omitempty"`
-	TLS      *OutboundTLSOptions `json:"tls,omitempty"`
-	Path     string              `json:"path,omitempty"`
-	Headers  HTTPHeader          `json:"headers,omitempty"`
+	Username string `json:"username,omitempty"`
+	Password string `json:"password,omitempty"`
+	OutboundTLSOptionsContainer
+	Path    string     `json:"path,omitempty"`
+	Headers HTTPHeader `json:"headers,omitempty"`
 }

+ 34 - 0
option/tls.go

@@ -17,6 +17,23 @@ type InboundTLSOptions struct {
 	Reality         *InboundRealityOptions `json:"reality,omitempty"`
 }
 
+type InboundTLSOptionsContainer struct {
+	TLS *InboundTLSOptions `json:"tls,omitempty"`
+}
+
+type InboundTLSOptionsWrapper interface {
+	TakeInboundTLSOptions() *InboundTLSOptions
+	ReplaceInboundTLSOptions(options *InboundTLSOptions)
+}
+
+func (o *InboundTLSOptionsContainer) TakeInboundTLSOptions() *InboundTLSOptions {
+	return o.TLS
+}
+
+func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTLSOptions) {
+	o.TLS = options
+}
+
 type OutboundTLSOptions struct {
 	Enabled         bool                    `json:"enabled,omitempty"`
 	DisableSNI      bool                    `json:"disable_sni,omitempty"`
@@ -33,6 +50,23 @@ type OutboundTLSOptions struct {
 	Reality         *OutboundRealityOptions `json:"reality,omitempty"`
 }
 
+type OutboundTLSOptionsContainer struct {
+	TLS *OutboundTLSOptions `json:"tls,omitempty"`
+}
+
+type OutboundTLSOptionsWrapper interface {
+	TakeOutboundTLSOptions() *OutboundTLSOptions
+	ReplaceOutboundTLSOptions(options *OutboundTLSOptions)
+}
+
+func (o *OutboundTLSOptionsContainer) TakeOutboundTLSOptions() *OutboundTLSOptions {
+	return o.TLS
+}
+
+func (o *OutboundTLSOptionsContainer) ReplaceOutboundTLSOptions(options *OutboundTLSOptions) {
+	o.TLS = options
+}
+
 type InboundRealityOptions struct {
 	Enabled           bool                           `json:"enabled,omitempty"`
 	Handshake         InboundRealityHandshakeOptions `json:"handshake,omitempty"`

+ 5 - 5
option/trojan.go

@@ -2,8 +2,8 @@ package option
 
 type TrojanInboundOptions struct {
 	ListenOptions
-	Users           []TrojanUser              `json:"users,omitempty"`
-	TLS             *InboundTLSOptions        `json:"tls,omitempty"`
+	Users []TrojanUser `json:"users,omitempty"`
+	InboundTLSOptionsContainer
 	Fallback        *ServerOptions            `json:"fallback,omitempty"`
 	FallbackForALPN map[string]*ServerOptions `json:"fallback_for_alpn,omitempty"`
 	Multiplex       *InboundMultiplexOptions  `json:"multiplex,omitempty"`
@@ -18,9 +18,9 @@ type TrojanUser struct {
 type TrojanOutboundOptions struct {
 	DialerOptions
 	ServerOptions
-	Password  string                    `json:"password"`
-	Network   NetworkList               `json:"network,omitempty"`
-	TLS       *OutboundTLSOptions       `json:"tls,omitempty"`
+	Password string      `json:"password"`
+	Network  NetworkList `json:"network,omitempty"`
+	OutboundTLSOptionsContainer
 	Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"`
 	Transport *V2RayTransportOptions    `json:"transport,omitempty"`
 }

+ 15 - 15
option/tuic.go

@@ -2,12 +2,12 @@ package option
 
 type TUICInboundOptions struct {
 	ListenOptions
-	Users             []TUICUser         `json:"users,omitempty"`
-	CongestionControl string             `json:"congestion_control,omitempty"`
-	AuthTimeout       Duration           `json:"auth_timeout,omitempty"`
-	ZeroRTTHandshake  bool               `json:"zero_rtt_handshake,omitempty"`
-	Heartbeat         Duration           `json:"heartbeat,omitempty"`
-	TLS               *InboundTLSOptions `json:"tls,omitempty"`
+	Users             []TUICUser `json:"users,omitempty"`
+	CongestionControl string     `json:"congestion_control,omitempty"`
+	AuthTimeout       Duration   `json:"auth_timeout,omitempty"`
+	ZeroRTTHandshake  bool       `json:"zero_rtt_handshake,omitempty"`
+	Heartbeat         Duration   `json:"heartbeat,omitempty"`
+	InboundTLSOptionsContainer
 }
 
 type TUICUser struct {
@@ -19,13 +19,13 @@ type TUICUser struct {
 type TUICOutboundOptions struct {
 	DialerOptions
 	ServerOptions
-	UUID              string              `json:"uuid,omitempty"`
-	Password          string              `json:"password,omitempty"`
-	CongestionControl string              `json:"congestion_control,omitempty"`
-	UDPRelayMode      string              `json:"udp_relay_mode,omitempty"`
-	UDPOverStream     bool                `json:"udp_over_stream,omitempty"`
-	ZeroRTTHandshake  bool                `json:"zero_rtt_handshake,omitempty"`
-	Heartbeat         Duration            `json:"heartbeat,omitempty"`
-	Network           NetworkList         `json:"network,omitempty"`
-	TLS               *OutboundTLSOptions `json:"tls,omitempty"`
+	UUID              string      `json:"uuid,omitempty"`
+	Password          string      `json:"password,omitempty"`
+	CongestionControl string      `json:"congestion_control,omitempty"`
+	UDPRelayMode      string      `json:"udp_relay_mode,omitempty"`
+	UDPOverStream     bool        `json:"udp_over_stream,omitempty"`
+	ZeroRTTHandshake  bool        `json:"zero_rtt_handshake,omitempty"`
+	Heartbeat         Duration    `json:"heartbeat,omitempty"`
+	Network           NetworkList `json:"network,omitempty"`
+	OutboundTLSOptionsContainer
 }

+ 6 - 6
option/vless.go

@@ -2,8 +2,8 @@ package option
 
 type VLESSInboundOptions struct {
 	ListenOptions
-	Users     []VLESSUser              `json:"users,omitempty"`
-	TLS       *InboundTLSOptions       `json:"tls,omitempty"`
+	Users []VLESSUser `json:"users,omitempty"`
+	InboundTLSOptionsContainer
 	Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
 	Transport *V2RayTransportOptions   `json:"transport,omitempty"`
 }
@@ -17,10 +17,10 @@ type VLESSUser struct {
 type VLESSOutboundOptions struct {
 	DialerOptions
 	ServerOptions
-	UUID           string                    `json:"uuid"`
-	Flow           string                    `json:"flow,omitempty"`
-	Network        NetworkList               `json:"network,omitempty"`
-	TLS            *OutboundTLSOptions       `json:"tls,omitempty"`
+	UUID    string      `json:"uuid"`
+	Flow    string      `json:"flow,omitempty"`
+	Network NetworkList `json:"network,omitempty"`
+	OutboundTLSOptionsContainer
 	Multiplex      *OutboundMultiplexOptions `json:"multiplex,omitempty"`
 	Transport      *V2RayTransportOptions    `json:"transport,omitempty"`
 	PacketEncoding *string                   `json:"packet_encoding,omitempty"`

+ 12 - 12
option/vmess.go

@@ -2,8 +2,8 @@ package option
 
 type VMessInboundOptions struct {
 	ListenOptions
-	Users     []VMessUser              `json:"users,omitempty"`
-	TLS       *InboundTLSOptions       `json:"tls,omitempty"`
+	Users []VMessUser `json:"users,omitempty"`
+	InboundTLSOptionsContainer
 	Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"`
 	Transport *V2RayTransportOptions   `json:"transport,omitempty"`
 }
@@ -17,14 +17,14 @@ type VMessUser struct {
 type VMessOutboundOptions struct {
 	DialerOptions
 	ServerOptions
-	UUID                string                    `json:"uuid"`
-	Security            string                    `json:"security"`
-	AlterId             int                       `json:"alter_id,omitempty"`
-	GlobalPadding       bool                      `json:"global_padding,omitempty"`
-	AuthenticatedLength bool                      `json:"authenticated_length,omitempty"`
-	Network             NetworkList               `json:"network,omitempty"`
-	TLS                 *OutboundTLSOptions       `json:"tls,omitempty"`
-	PacketEncoding      string                    `json:"packet_encoding,omitempty"`
-	Multiplex           *OutboundMultiplexOptions `json:"multiplex,omitempty"`
-	Transport           *V2RayTransportOptions    `json:"transport,omitempty"`
+	UUID                string      `json:"uuid"`
+	Security            string      `json:"security"`
+	AlterId             int         `json:"alter_id,omitempty"`
+	GlobalPadding       bool        `json:"global_padding,omitempty"`
+	AuthenticatedLength bool        `json:"authenticated_length,omitempty"`
+	Network             NetworkList `json:"network,omitempty"`
+	OutboundTLSOptionsContainer
+	PacketEncoding string                    `json:"packet_encoding,omitempty"`
+	Multiplex      *OutboundMultiplexOptions `json:"multiplex,omitempty"`
+	Transport      *V2RayTransportOptions    `json:"transport,omitempty"`
 }

+ 38 - 30
test/brutal_test.go

@@ -118,11 +118,13 @@ func TestBrutalTrojan(t *testing.T) {
 							DownMbps: 100,
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -150,10 +152,12 @@ func TestBrutalTrojan(t *testing.T) {
 							DownMbps: 100,
 						},
 					},
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},
@@ -275,19 +279,21 @@ func TestBrutalVLESS(t *testing.T) {
 							DownMbps: 100,
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
-						Reality: &option.InboundRealityOptions{
-							Enabled: true,
-							Handshake: option.InboundRealityHandshakeOptions{
-								ServerOptions: option.ServerOptions{
-									Server:     "google.com",
-									ServerPort: 443,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+							Reality: &option.InboundRealityOptions{
+								Enabled: true,
+								Handshake: option.InboundRealityHandshakeOptions{
+									ServerOptions: option.ServerOptions{
+										Server:     "google.com",
+										ServerPort: 443,
+									},
 								},
+								ShortID:    []string{"0123456789abcdef"},
+								PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
 							},
-							ShortID:    []string{"0123456789abcdef"},
-							PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
 						},
 					},
 				},
@@ -306,16 +312,18 @@ func TestBrutalVLESS(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					UUID: user.String(),
-					TLS: &option.OutboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
-						Reality: &option.OutboundRealityOptions{
-							Enabled:   true,
-							ShortID:   "0123456789abcdef",
-							PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
-						},
-						UTLS: &option.OutboundUTLSOptions{
-							Enabled: true,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+							Reality: &option.OutboundRealityOptions{
+								Enabled:   true,
+								ShortID:   "0123456789abcdef",
+								PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
+							},
+							UTLS: &option.OutboundUTLSOptions{
+								Enabled: true,
+							},
 						},
 					},
 					Multiplex: &option.OutboundMultiplexOptions{

+ 13 - 9
test/domain_inbound_test.go

@@ -38,11 +38,13 @@ func TestTUICDomainUDP(t *testing.T) {
 					Users: []option.TUICUser{{
 						UUID: uuid.Nil.String(),
 					}},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -60,10 +62,12 @@ func TestTUICDomainUDP(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					UUID: uuid.Nil.String(),
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},

+ 57 - 45
test/ech_test.go

@@ -40,14 +40,16 @@ func TestECH(t *testing.T) {
 							Password: "password",
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
-						ECH: &option.InboundECHOptions{
-							Enabled: true,
-							Key:     []string{echKey},
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+							ECH: &option.InboundECHOptions{
+								Enabled: true,
+								Key:     []string{echKey},
+							},
 						},
 					},
 				},
@@ -66,13 +68,15 @@ func TestECH(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					Password: "password",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						ECH: &option.OutboundECHOptions{
-							Enabled: true,
-							Config:  []string{echConfig},
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							ECH: &option.OutboundECHOptions{
+								Enabled: true,
+								Config:  []string{echConfig},
+							},
 						},
 					},
 				},
@@ -117,14 +121,16 @@ func TestECHQUIC(t *testing.T) {
 					Users: []option.TUICUser{{
 						UUID: uuid.Nil.String(),
 					}},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
-						ECH: &option.InboundECHOptions{
-							Enabled: true,
-							Key:     []string{echKey},
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+							ECH: &option.InboundECHOptions{
+								Enabled: true,
+								Key:     []string{echKey},
+							},
 						},
 					},
 				},
@@ -143,13 +149,15 @@ func TestECHQUIC(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					UUID: uuid.Nil.String(),
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						ECH: &option.OutboundECHOptions{
-							Enabled: true,
-							Config:  []string{echConfig},
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							ECH: &option.OutboundECHOptions{
+								Enabled: true,
+								Config:  []string{echConfig},
+							},
 						},
 					},
 				},
@@ -194,14 +202,16 @@ func TestECHHysteria2(t *testing.T) {
 					Users: []option.Hysteria2User{{
 						Password: "password",
 					}},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
-						ECH: &option.InboundECHOptions{
-							Enabled: true,
-							Key:     []string{echKey},
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+							ECH: &option.InboundECHOptions{
+								Enabled: true,
+								Key:     []string{echKey},
+							},
 						},
 					},
 				},
@@ -220,13 +230,15 @@ func TestECHHysteria2(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					Password: "password",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						ECH: &option.OutboundECHOptions{
-							Enabled: true,
-							Config:  []string{echConfig},
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							ECH: &option.OutboundECHOptions{
+								Enabled: true,
+								Config:  []string{echConfig},
+							},
 						},
 					},
 				},

+ 29 - 29
test/go.mod

@@ -10,24 +10,24 @@ require (
 	github.com/docker/docker v24.0.7+incompatible
 	github.com/docker/go-connections v0.4.0
 	github.com/gofrs/uuid/v5 v5.0.0
-	github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
-	github.com/sagernet/sing v0.2.18-0.20231119032432-6a556bfa50cc
+	github.com/sagernet/quic-go v0.40.0
+	github.com/sagernet/sing v0.2.20-0.20231212123824-8836b6754226
 	github.com/sagernet/sing-dns v0.1.11
-	github.com/sagernet/sing-quic v0.1.4
-	github.com/sagernet/sing-shadowsocks v0.2.5
-	github.com/sagernet/sing-shadowsocks2 v0.1.5
+	github.com/sagernet/sing-quic v0.1.6-0.20231207143711-eb3cbf9ed054
+	github.com/sagernet/sing-shadowsocks v0.2.6
+	github.com/sagernet/sing-shadowsocks2 v0.1.6-0.20231207143709-50439739601a
 	github.com/spyzhov/ajson v0.9.0
 	github.com/stretchr/testify v1.8.4
 	go.uber.org/goleak v1.3.0
-	golang.org/x/net v0.18.0
+	golang.org/x/net v0.19.0
 )
 
 require (
 	berty.tech/go-libtor v1.0.385 // indirect
 	github.com/Microsoft/go-winio v0.6.1 // indirect
 	github.com/ajg/form v1.5.1 // indirect
-	github.com/andybalholm/brotli v1.0.5 // indirect
-	github.com/caddyserver/certmagic v0.19.2 // indirect
+	github.com/andybalholm/brotli v1.0.6 // indirect
+	github.com/caddyserver/certmagic v0.20.0 // indirect
 	github.com/cloudflare/circl v1.3.6 // indirect
 	github.com/cretz/bine v0.2.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
@@ -35,6 +35,7 @@ require (
 	github.com/docker/distribution v2.8.3+incompatible // indirect
 	github.com/docker/go-units v0.5.0 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
+	github.com/gaukas/godicttls v0.0.4 // indirect
 	github.com/go-chi/chi/v5 v5.0.10 // indirect
 	github.com/go-chi/cors v1.2.1 // indirect
 	github.com/go-chi/render v1.0.3 // indirect
@@ -45,11 +46,11 @@ require (
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/google/btree v1.1.2 // indirect
-	github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
+	github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
 	github.com/hashicorp/yamux v0.1.1 // indirect
-	github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c // indirect
+	github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect
 	github.com/josharian/native v1.1.0 // indirect
-	github.com/klauspost/compress v1.15.15 // indirect
+	github.com/klauspost/compress v1.17.4 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
 	github.com/libdns/alidns v1.0.3 // indirect
 	github.com/libdns/cloudflare v0.1.0 // indirect
@@ -59,7 +60,7 @@ require (
 	github.com/miekg/dns v1.1.57 // indirect
 	github.com/moby/term v0.5.0 // indirect
 	github.com/morikuni/aec v1.0.0 // indirect
-	github.com/onsi/ginkgo/v2 v2.9.5 // indirect
+	github.com/onsi/ginkgo/v2 v2.9.7 // indirect
 	github.com/ooni/go-libtor v1.1.8 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.2 // indirect
@@ -68,36 +69,35 @@ require (
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/quic-go/qpack v0.4.0 // indirect
-	github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
+	github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
 	github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
-	github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a // indirect
-	github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
-	github.com/sagernet/gvisor v0.0.0-20231119034329-07cfb6aaf930 // indirect
+	github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect
+	github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e // indirect
 	github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
 	github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
-	github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07 // indirect
+	github.com/sagernet/sing-mux v0.1.6-0.20231208180947-9053c29513a2 // indirect
 	github.com/sagernet/sing-shadowtls v0.1.4 // indirect
-	github.com/sagernet/sing-tun v0.1.21-0.20231119035513-f6ea97c5af71 // indirect
+	github.com/sagernet/sing-tun v0.1.24-0.20231212060935-6a1419aeae11 // indirect
 	github.com/sagernet/sing-vmess v0.1.8 // indirect
-	github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
-	github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
-	github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect
-	github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect
-	github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed // indirect
+	github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
+	github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect
+	github.com/sagernet/utls v1.5.4 // indirect
+	github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e // indirect
+	github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
 	github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
 	github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
 	github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.uber.org/zap v1.26.0 // indirect
-	go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
-	golang.org/x/crypto v0.15.0 // indirect
-	golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
+	go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
+	golang.org/x/crypto v0.16.0 // indirect
+	golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
 	golang.org/x/mod v0.14.0 // indirect
-	golang.org/x/sys v0.14.0 // indirect
+	golang.org/x/sys v0.15.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
-	golang.org/x/time v0.4.0 // indirect
-	golang.org/x/tools v0.15.0 // indirect
+	golang.org/x/time v0.5.0 // indirect
+	golang.org/x/tools v0.16.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
 	google.golang.org/grpc v1.59.0 // indirect
 	google.golang.org/protobuf v1.31.0 // indirect

+ 61 - 67
test/go.sum

@@ -5,13 +5,10 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
 github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
 github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
-github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
-github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
-github.com/caddyserver/certmagic v0.19.2 h1:HZd1AKLx4592MalEGQS39DKs2ZOAJCEM/xYPMQ2/ui0=
-github.com/caddyserver/certmagic v0.19.2/go.mod h1:fsL01NomQ6N+kE2j37ZCnig2MFosG+MIO4ztnmG/zz8=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-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/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
+github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
+github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
 github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
 github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
@@ -32,6 +29,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
+github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
 github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
 github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
@@ -58,20 +57,19 @@ 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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
+github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
 github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
 github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c h1:PgxFEySCI41sH0mB7/2XswdXbUykQsRUGod8Rn+NubM=
-github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
+github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
+github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI=
 github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
 github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
-github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
+github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
+github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
 github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
 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=
@@ -94,9 +92,9 @@ github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
-github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
-github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
+github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
+github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
+github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
 github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w=
 github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -113,52 +111,49 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
 github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
-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/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
+github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=
 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
-github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a h1:wZHruBxZCsQLXHAozWpnJBL3wJ/XufDpz0qKtgpSnA4=
-github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a/go.mod h1:dNV1ZP9y3qx5ltULeKaQZTZWTLHflgW5DES+Ses7cMI=
-github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
-github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
-github.com/sagernet/gvisor v0.0.0-20231119034329-07cfb6aaf930 h1:dSPgjIw0CT6ISLeEh8Q20dZMBMFCcEceo23+LncRcNQ=
-github.com/sagernet/gvisor v0.0.0-20231119034329-07cfb6aaf930/go.mod h1:JpKHkOYgh4wLwrX2BhH3ZIvCvazCkTnPeEcmigZJfHY=
+github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY=
+github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k=
+github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e h1:DOkjByVeAR56dkszjnMZke4wr7yM/1xHaJF3G9olkEE=
+github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw=
 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
-github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460 h1:dAe4OIJAtE0nHOzTHhAReQteh3+sa63rvXbuIpbeOTY=
-github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460/go.mod h1:uJGpmJCOcMQqMlHKc3P1Vz6uygmpz4bPeVIoOhdVQnM=
+github.com/sagernet/quic-go v0.40.0 h1:DvQNPb72lzvNQDe9tcUyHTw8eRv6PLtM2mNYmdlzUMo=
+github.com/sagernet/quic-go v0.40.0/go.mod h1:VqtdhlbkeeG5Okhb3eDMb/9o0EoglReHunNT9ukrJAI=
 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
-github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
-github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
-github.com/sagernet/sing v0.2.18-0.20231119032432-6a556bfa50cc h1:dmU0chO0QrBpARo8sqyOc+mvPLW+qux4ca16kb2WIc8=
-github.com/sagernet/sing v0.2.18-0.20231119032432-6a556bfa50cc/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
+github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
+github.com/sagernet/sing v0.2.20-0.20231212123824-8836b6754226 h1:rcII71ho6F/7Nyx7n2kESLcnvNMdcU4i8ZUGF2Fi7yA=
+github.com/sagernet/sing v0.2.20-0.20231212123824-8836b6754226/go.mod h1:Ce5LNojQOgOiWhiD8pPD6E9H7e2KgtOe3Zxx4Ou5u80=
 github.com/sagernet/sing-dns v0.1.11 h1:PPrMCVVrAeR3f5X23I+cmvacXJ+kzuyAsBiWyUKhGSE=
 github.com/sagernet/sing-dns v0.1.11/go.mod h1:zJ/YjnYB61SYE+ubMcMqVdpaSvsyQ2iShQGO3vuLvvE=
-github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07 h1:ncKb5tVOsCQgCsv6UpsA0jinbNb5OQ5GMPJlyQP3EHM=
-github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07/go.mod h1:u/MZf32xPG8jEKe3t+xUV67EBnKtDtCaPhsJQOQGUYU=
-github.com/sagernet/sing-quic v0.1.4 h1:F5KRGXMXKQEmP8VrzVollf9HWcRqggcuG9nRCL+5IJ8=
-github.com/sagernet/sing-quic v0.1.4/go.mod h1:aXHVP+osF3w5wJzoWZbJSrX3ceJiU9QMd0KPnKV6C/o=
-github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
-github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
-github.com/sagernet/sing-shadowsocks2 v0.1.5 h1:JDeAJ4ZWlYZ7F6qEVdDKPhQEangxKw/JtmU+i/YfCYE=
-github.com/sagernet/sing-shadowsocks2 v0.1.5/go.mod h1:KF65y8lI5PGHyMgRZGYXYsH9ilgRc/yr+NYbSNGuBm4=
+github.com/sagernet/sing-mux v0.1.6-0.20231208180947-9053c29513a2 h1:rRlYQPbMKmzKX+43XC04gEQvxc45/AxfteRWfcl2/rw=
+github.com/sagernet/sing-mux v0.1.6-0.20231208180947-9053c29513a2/go.mod h1:IdSrwwqBeJTrjLZJRFXE+F8mYXNI/rPAjzlgTFuEVmo=
+github.com/sagernet/sing-quic v0.1.6-0.20231207143711-eb3cbf9ed054 h1:Ed7FskwQcep5oQ+QahgVK0F6jPPSV8Nqwjr9MwGatMU=
+github.com/sagernet/sing-quic v0.1.6-0.20231207143711-eb3cbf9ed054/go.mod h1:u758WWv3G1OITG365CYblL0NfAruFL1PpLD9DUVTv1o=
+github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
+github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
+github.com/sagernet/sing-shadowsocks2 v0.1.6-0.20231207143709-50439739601a h1:uYIKfpE1/EJpa+1Bja7b006VixeRuVduOpeuesMk2lU=
+github.com/sagernet/sing-shadowsocks2 v0.1.6-0.20231207143709-50439739601a/go.mod h1:pjeylQ4ApvpEH7B4PUBrdyJf4xmQkg8BaIzT5fI2fR0=
 github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
 github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
-github.com/sagernet/sing-tun v0.1.21-0.20231119035513-f6ea97c5af71 h1:WQi0TwhjbSNFFbxybIgAUSjVvo7uWSsLD28ldoM2avY=
-github.com/sagernet/sing-tun v0.1.21-0.20231119035513-f6ea97c5af71/go.mod h1:hyzA4gDWbeg2SXklqPDswBKa//QcjlZqKw9aPcNdQ9A=
+github.com/sagernet/sing-tun v0.1.24-0.20231212060935-6a1419aeae11 h1:crTOVPJGOGWOW+Q2a0FQiiS/G2+W6uCLKtOofFMisQc=
+github.com/sagernet/sing-tun v0.1.24-0.20231212060935-6a1419aeae11/go.mod h1:DgXPnBqtqWrZj37Mun/W61dW0Q56eLqTZYhcuNLaCtY=
 github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
 github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
-github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
-github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
-github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q=
-github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M=
-github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
-github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
-github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho=
-github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk=
-github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed h1:90a510OeE9siSJoYsI8nSjPmA+u5ROMDts/ZkdNsuXY=
-github.com/sagernet/ws v0.0.0-20231030053741-7d481eb31bed/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
+github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
+github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
+github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI=
+github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY=
+github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co=
+github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s=
+github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo=
+github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE=
+github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
+github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
 github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
 github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
 github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0=
@@ -187,17 +182,17 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
 go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
 go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
-go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
-go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
+go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
+go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
-golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
-golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
-golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
-golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
+golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
+golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
+golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
@@ -208,8 +203,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
-golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -217,33 +212,32 @@ golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
 golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
-golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
-golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
+golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
+golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
 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=

+ 26 - 18
test/hysteria2_test.go

@@ -52,11 +52,13 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) {
 					Users: []option.Hysteria2User{{
 						Password: "password",
 					}},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -77,10 +79,12 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) {
 					DownMbps: 100,
 					Obfs:     obfs,
 					Password: "password",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},
@@ -118,11 +122,13 @@ func TestHysteria2Inbound(t *testing.T) {
 					Users: []option.Hysteria2User{{
 						Password: "password",
 					}},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -177,10 +183,12 @@ func TestHysteria2Outbound(t *testing.T) {
 						Password: "cry_me_a_r1ver",
 					},
 					Password: "password",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},

+ 26 - 18
test/hysteria_test.go

@@ -35,11 +35,13 @@ func TestHysteriaSelf(t *testing.T) {
 						AuthString: "password",
 					}},
 					Obfs: "fuck me till the daylight",
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -60,10 +62,12 @@ func TestHysteriaSelf(t *testing.T) {
 					DownMbps:   100,
 					AuthString: "password",
 					Obfs:       "fuck me till the daylight",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},
@@ -99,11 +103,13 @@ func TestHysteriaInbound(t *testing.T) {
 						AuthString: "password",
 					}},
 					Obfs: "fuck me till the daylight",
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -157,10 +163,12 @@ func TestHysteriaOutbound(t *testing.T) {
 					DownMbps:   100,
 					AuthString: "password",
 					Obfs:       "fuck me till the daylight",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},

+ 14 - 10
test/naive_test.go

@@ -74,11 +74,13 @@ func TestNaiveInbound(t *testing.T) {
 						},
 					},
 					Network: network.NetworkTCP,
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -116,11 +118,13 @@ func TestNaiveHTTP3Inbound(t *testing.T) {
 						},
 					},
 					Network: network.NetworkUDP,
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},

+ 88 - 72
test/reality_test.go

@@ -39,19 +39,21 @@ func TestVLESSVisionReality(t *testing.T) {
 							Flow: vless.FlowVision,
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
-						Reality: &option.InboundRealityOptions{
-							Enabled: true,
-							Handshake: option.InboundRealityHandshakeOptions{
-								ServerOptions: option.ServerOptions{
-									Server:     "google.com",
-									ServerPort: 443,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+							Reality: &option.InboundRealityOptions{
+								Enabled: true,
+								Handshake: option.InboundRealityHandshakeOptions{
+									ServerOptions: option.ServerOptions{
+										Server:     "google.com",
+										ServerPort: 443,
+									},
 								},
+								ShortID:    []string{"0123456789abcdef"},
+								PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
 							},
-							ShortID:    []string{"0123456789abcdef"},
-							PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
 						},
 					},
 				},
@@ -70,11 +72,13 @@ func TestVLESSVisionReality(t *testing.T) {
 							Password: userUUID.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -92,10 +96,12 @@ func TestVLESSVisionReality(t *testing.T) {
 						ServerPort: otherPort,
 					},
 					Password: userUUID.String(),
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 					DialerOptions: option.DialerOptions{
 						Detour: "vless-out",
@@ -112,16 +118,18 @@ func TestVLESSVisionReality(t *testing.T) {
 					},
 					UUID: userUUID.String(),
 					Flow: vless.FlowVision,
-					TLS: &option.OutboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
-						Reality: &option.OutboundRealityOptions{
-							Enabled:   true,
-							ShortID:   "0123456789abcdef",
-							PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
-						},
-						UTLS: &option.OutboundUTLSOptions{
-							Enabled: true,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+							Reality: &option.OutboundRealityOptions{
+								Enabled:   true,
+								ShortID:   "0123456789abcdef",
+								PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
+							},
+							UTLS: &option.OutboundUTLSOptions{
+								Enabled: true,
+							},
 						},
 					},
 				},
@@ -169,19 +177,21 @@ func TestVLESSVisionRealityPlain(t *testing.T) {
 							Flow: vless.FlowVision,
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
-						Reality: &option.InboundRealityOptions{
-							Enabled: true,
-							Handshake: option.InboundRealityHandshakeOptions{
-								ServerOptions: option.ServerOptions{
-									Server:     "google.com",
-									ServerPort: 443,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+							Reality: &option.InboundRealityOptions{
+								Enabled: true,
+								Handshake: option.InboundRealityHandshakeOptions{
+									ServerOptions: option.ServerOptions{
+										Server:     "google.com",
+										ServerPort: 443,
+									},
 								},
+								ShortID:    []string{"0123456789abcdef"},
+								PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
 							},
-							ShortID:    []string{"0123456789abcdef"},
-							PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
 						},
 					},
 				},
@@ -201,16 +211,18 @@ func TestVLESSVisionRealityPlain(t *testing.T) {
 					},
 					UUID: userUUID.String(),
 					Flow: vless.FlowVision,
-					TLS: &option.OutboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
-						Reality: &option.OutboundRealityOptions{
-							Enabled:   true,
-							ShortID:   "0123456789abcdef",
-							PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
-						},
-						UTLS: &option.OutboundUTLSOptions{
-							Enabled: true,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+							Reality: &option.OutboundRealityOptions{
+								Enabled:   true,
+								ShortID:   "0123456789abcdef",
+								PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
+							},
+							UTLS: &option.OutboundUTLSOptions{
+								Enabled: true,
+							},
 						},
 					},
 				},
@@ -275,19 +287,21 @@ func testVLESSRealityTransport(t *testing.T, transport *option.V2RayTransportOpt
 							UUID: userUUID.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
-						Reality: &option.InboundRealityOptions{
-							Enabled: true,
-							Handshake: option.InboundRealityHandshakeOptions{
-								ServerOptions: option.ServerOptions{
-									Server:     "google.com",
-									ServerPort: 443,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+							Reality: &option.InboundRealityOptions{
+								Enabled: true,
+								Handshake: option.InboundRealityHandshakeOptions{
+									ServerOptions: option.ServerOptions{
+										Server:     "google.com",
+										ServerPort: 443,
+									},
 								},
+								ShortID:    []string{"0123456789abcdef"},
+								PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
 							},
-							ShortID:    []string{"0123456789abcdef"},
-							PrivateKey: "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
 						},
 					},
 					Transport: transport,
@@ -307,16 +321,18 @@ func testVLESSRealityTransport(t *testing.T, transport *option.V2RayTransportOpt
 						ServerPort: serverPort,
 					},
 					UUID: userUUID.String(),
-					TLS: &option.OutboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
-						Reality: &option.OutboundRealityOptions{
-							Enabled:   true,
-							ShortID:   "0123456789abcdef",
-							PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
-						},
-						UTLS: &option.OutboundUTLSOptions{
-							Enabled: true,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+							Reality: &option.OutboundRealityOptions{
+								Enabled:   true,
+								ShortID:   "0123456789abcdef",
+								PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
+							},
+							UTLS: &option.OutboundUTLSOptions{
+								Enabled: true,
+							},
 						},
 					},
 					Transport: transport,

+ 12 - 8
test/shadowtls_test.go

@@ -99,11 +99,13 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool)
 						Server:     "127.0.0.1",
 						ServerPort: serverPort,
 					},
-					TLS: &option.OutboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
-						UTLS: &option.OutboundUTLSOptions{
-							Enabled: utlsEanbled,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+							UTLS: &option.OutboundUTLSOptions{
+								Enabled: utlsEanbled,
+							},
 						},
 					},
 					Version:  version,
@@ -301,9 +303,11 @@ func TestShadowTLSOutbound(t *testing.T) {
 						Server:     "127.0.0.1",
 						ServerPort: serverPort,
 					},
-					TLS: &option.OutboundTLSOptions{
-						Enabled:    true,
-						ServerName: "google.com",
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:    true,
+							ServerName: "google.com",
+						},
 					},
 					Version:  3,
 					Password: "hello",

+ 16 - 12
test/tls_test.go

@@ -35,11 +35,13 @@ func TestUTLS(t *testing.T) {
 							Password: "password",
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -57,13 +59,15 @@ func TestUTLS(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					Password: "password",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						UTLS: &option.OutboundUTLSOptions{
-							Enabled:     true,
-							Fingerprint: "chrome",
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							UTLS: &option.OutboundUTLSOptions{
+								Enabled:     true,
+								Fingerprint: "chrome",
+							},
 						},
 					},
 				},

+ 19 - 13
test/trojan_test.go

@@ -40,10 +40,12 @@ func TestTrojanOutbound(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					Password: "password",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},
@@ -79,11 +81,13 @@ func TestTrojanSelf(t *testing.T) {
 							Password: "password",
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -101,10 +105,12 @@ func TestTrojanSelf(t *testing.T) {
 						ServerPort: serverPort,
 					},
 					Password: "password",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},

+ 26 - 18
test/tuic_test.go

@@ -51,11 +51,13 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) {
 						UUID: uuid.Nil.String(),
 					}},
 					ZeroRTTHandshake: zeroRTTHandshake,
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -75,10 +77,12 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) {
 					UUID:             uuid.Nil.String(),
 					UDPRelayMode:     udpRelayMode,
 					ZeroRTTHandshake: zeroRTTHandshake,
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},
@@ -112,11 +116,13 @@ func TestTUICInbound(t *testing.T) {
 						UUID:     "FE35D05B-8803-45C4-BAE6-723AD2CD5D3D",
 						Password: "tuic",
 					}},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -166,10 +172,12 @@ func TestTUICOutbound(t *testing.T) {
 					},
 					UUID:     "FE35D05B-8803-45C4-BAE6-723AD2CD5D3D",
 					Password: "tuic",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},

+ 13 - 9
test/v2ray_grpc_test.go

@@ -41,11 +41,13 @@ func testV2RayGRPCInbound(t *testing.T, forceLite bool) {
 							UUID: userId.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 					Transport: &option.V2RayTransportOptions{
 						Type: C.V2RayTransportTypeGRPC,
@@ -147,10 +149,12 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) {
 					},
 					UUID:     userId.String(),
 					Security: "zero",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 					Transport: &option.V2RayTransportOptions{
 						Type: C.V2RayTransportTypeGRPC,

+ 39 - 27
test/v2ray_transport_test.go

@@ -68,11 +68,13 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
 							UUID: user.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 					Transport: server,
 				},
@@ -92,10 +94,12 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
 					},
 					UUID:     user.String(),
 					Security: "zero",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 					Transport: client,
 				},
@@ -144,11 +148,13 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
 							Password: user.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 					Transport: server,
 				},
@@ -167,10 +173,12 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
 						ServerPort: serverPort,
 					},
 					Password: user.String(),
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 					Transport: client,
 				},
@@ -222,11 +230,13 @@ func TestVMessQUICSelf(t *testing.T) {
 							UUID: user.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 					Transport: transport,
 				},
@@ -246,10 +256,12 @@ func TestVMessQUICSelf(t *testing.T) {
 					},
 					UUID:     user.String(),
 					Security: "zero",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 					Transport: transport,
 				},

+ 13 - 9
test/v2ray_ws_test.go

@@ -75,11 +75,13 @@ func testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeade
 							UUID: userId.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 					Transport: &option.V2RayTransportOptions{
 						Type: C.V2RayTransportTypeWebsocket,
@@ -179,10 +181,12 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead
 					},
 					UUID:     userId.String(),
 					Security: "zero",
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 					Transport: &option.V2RayTransportOptions{
 						Type: C.V2RayTransportTypeWebsocket,

+ 78 - 54
test/vless_test.go

@@ -130,11 +130,13 @@ func testVLESSXrayOutbound(t *testing.T, packetEncoding string, flow string) {
 							Password: userID.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -148,10 +150,12 @@ func testVLESSXrayOutbound(t *testing.T, packetEncoding string, flow string) {
 						ServerPort: otherPort,
 					},
 					Password: userID.String(),
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 					DialerOptions: option.DialerOptions{
 						Detour: "vless",
@@ -169,10 +173,12 @@ func testVLESSXrayOutbound(t *testing.T, packetEncoding string, flow string) {
 					UUID:           userID.String(),
 					Flow:           flow,
 					PacketEncoding: &packetEncoding,
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},
@@ -238,11 +244,13 @@ func testVLESSSelf(t *testing.T, flow string) {
 							Flow: flow,
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -261,10 +269,12 @@ func testVLESSSelf(t *testing.T, flow string) {
 					},
 					UUID: userUUID.String(),
 					Flow: flow,
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},
@@ -313,11 +323,13 @@ func testVLESSSelfTLS(t *testing.T, flow string) {
 							Flow: flow,
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -335,11 +347,13 @@ func testVLESSSelfTLS(t *testing.T, flow string) {
 							Password: userUUID.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -357,10 +371,12 @@ func testVLESSSelfTLS(t *testing.T, flow string) {
 						ServerPort: otherPort,
 					},
 					Password: userUUID.String(),
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 					DialerOptions: option.DialerOptions{
 						Detour: "vless-out",
@@ -377,10 +393,12 @@ func testVLESSSelfTLS(t *testing.T, flow string) {
 					},
 					UUID: userUUID.String(),
 					Flow: flow,
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 				},
 			},
@@ -424,11 +442,13 @@ func testVLESSXrayInbound(t *testing.T, flow string) {
 							Flow: flow,
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -446,11 +466,13 @@ func testVLESSXrayInbound(t *testing.T, flow string) {
 							Password: userId.String(),
 						},
 					},
-					TLS: &option.InboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
-						KeyPath:         keyPem,
+					InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+						TLS: &option.InboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+							KeyPath:         keyPem,
+						},
 					},
 				},
 			},
@@ -480,10 +502,12 @@ func testVLESSXrayInbound(t *testing.T, flow string) {
 						ServerPort: otherPort,
 					},
 					Password: userId.String(),
-					TLS: &option.OutboundTLSOptions{
-						Enabled:         true,
-						ServerName:      "example.org",
-						CertificatePath: certPem,
+					OutboundTLSOptionsContainer: option.OutboundTLSOptionsContainer{
+						TLS: &option.OutboundTLSOptions{
+							Enabled:         true,
+							ServerName:      "example.org",
+							CertificatePath: certPem,
+						},
 					},
 					DialerOptions: option.DialerOptions{
 						Detour: "vless-out",

+ 32 - 0
test/wrapper_test.go

@@ -0,0 +1,32 @@
+package main
+
+import (
+	"testing"
+
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/option"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestOptionsWrapper(t *testing.T) {
+	inbound := option.Inbound{
+		Type: C.TypeHTTP,
+		HTTPOptions: option.HTTPMixedInboundOptions{
+			InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{
+				TLS: &option.InboundTLSOptions{
+					Enabled: true,
+				},
+			},
+		},
+	}
+	rawOptions, err := inbound.RawOptions()
+	require.NoError(t, err)
+	tlsOptionsWrapper, loaded := rawOptions.(option.InboundTLSOptionsWrapper)
+	require.True(t, loaded, "find inbound tls options")
+	tlsOptions := tlsOptionsWrapper.TakeInboundTLSOptions()
+	require.NotNil(t, tlsOptions, "find inbound tls options")
+	tlsOptions.Enabled = false
+	tlsOptionsWrapper.ReplaceInboundTLSOptions(tlsOptions)
+	require.False(t, inbound.HTTPOptions.TLS.Enabled, "replace tls enabled")
+}