diff --git a/common/dialer/default.go b/common/dialer/default.go
index b8a0d5f4..fcc5e60e 100644
--- a/common/dialer/default.go
+++ b/common/dialer/default.go
@@ -3,6 +3,7 @@ package dialer
 import (
 	"context"
 	"net"
+	"net/netip"
 	"time"
 
 	"github.com/sagernet/sing-box/adapter"
@@ -102,7 +103,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
 		udpAddr4   string
 	)
 	if options.Inet4BindAddress != nil {
-		bindAddr := options.Inet4BindAddress.Build()
+		bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified())
 		dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
 		udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
 		udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
@@ -113,7 +114,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
 		udpAddr6   string
 	)
 	if options.Inet6BindAddress != nil {
-		bindAddr := options.Inet6BindAddress.Build()
+		bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified())
 		dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
 		udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
 		udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
diff --git a/common/listener/listener.go b/common/listener/listener.go
index b42b0434..289f15c5 100644
--- a/common/listener/listener.go
+++ b/common/listener/listener.go
@@ -3,6 +3,7 @@ package listener
 import (
 	"context"
 	"net"
+	"net/netip"
 	"sync/atomic"
 
 	"github.com/sagernet/sing-box/adapter"
@@ -92,7 +93,7 @@ func (l *Listener) Start() error {
 	if l.setSystemProxy {
 		listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
 		var listenAddrString string
-		listenAddr := l.listenOptions.Listen.Build()
+		listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified())
 		if listenAddr.IsUnspecified() {
 			listenAddrString = "127.0.0.1"
 		} else {
diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go
index 02cef3f0..646d4017 100644
--- a/common/listener/listener_tcp.go
+++ b/common/listener/listener_tcp.go
@@ -2,6 +2,7 @@ package listener
 
 import (
 	"net"
+	"net/netip"
 	"time"
 
 	"github.com/sagernet/sing-box/adapter"
@@ -16,7 +17,7 @@ import (
 
 func (l *Listener) ListenTCP() (net.Listener, error) {
 	var err error
-	bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
+	bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
 	var tcpListener net.Listener
 	var listenConfig net.ListenConfig
 	if l.listenOptions.TCPKeepAlive >= 0 {
diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go
index d871c672..9be23213 100644
--- a/common/listener/listener_udp.go
+++ b/common/listener/listener_udp.go
@@ -2,6 +2,7 @@ package listener
 
 import (
 	"net"
+	"net/netip"
 	"os"
 	"time"
 
@@ -13,7 +14,7 @@ import (
 )
 
 func (l *Listener) ListenUDP() (net.PacketConn, error) {
-	bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
+	bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
 	var lc net.ListenConfig
 	var udpFragment bool
 	if l.listenOptions.UDPFragment != nil {
diff --git a/experimental/deprecated/manager.go b/experimental/deprecated/manager.go
index d7cae179..d12acf48 100644
--- a/experimental/deprecated/manager.go
+++ b/experimental/deprecated/manager.go
@@ -2,6 +2,7 @@ package deprecated
 
 import (
 	"context"
+
 	"github.com/sagernet/sing/service"
 )
 
diff --git a/option/dns.go b/option/dns.go
index be947583..32c1ac2e 100644
--- a/option/dns.go
+++ b/option/dns.go
@@ -1,6 +1,10 @@
 package option
 
-import "net/netip"
+import (
+	"net/netip"
+
+	"github.com/sagernet/sing/common/json/badoption"
+)
 
 type DNSOptions struct {
 	Servers        []DNSServerOptions `json:"servers,omitempty"`
@@ -12,22 +16,22 @@ type DNSOptions struct {
 }
 
 type DNSServerOptions struct {
-	Tag                  string         `json:"tag,omitempty"`
-	Address              string         `json:"address"`
-	AddressResolver      string         `json:"address_resolver,omitempty"`
-	AddressStrategy      DomainStrategy `json:"address_strategy,omitempty"`
-	AddressFallbackDelay Duration       `json:"address_fallback_delay,omitempty"`
-	Strategy             DomainStrategy `json:"strategy,omitempty"`
-	Detour               string         `json:"detour,omitempty"`
-	ClientSubnet         *AddrPrefix    `json:"client_subnet,omitempty"`
+	Tag                  string                `json:"tag,omitempty"`
+	Address              string                `json:"address"`
+	AddressResolver      string                `json:"address_resolver,omitempty"`
+	AddressStrategy      DomainStrategy        `json:"address_strategy,omitempty"`
+	AddressFallbackDelay badoption.Duration    `json:"address_fallback_delay,omitempty"`
+	Strategy             DomainStrategy        `json:"strategy,omitempty"`
+	Detour               string                `json:"detour,omitempty"`
+	ClientSubnet         *badoption.Prefixable `json:"client_subnet,omitempty"`
 }
 
 type DNSClientOptions struct {
-	Strategy         DomainStrategy `json:"strategy,omitempty"`
-	DisableCache     bool           `json:"disable_cache,omitempty"`
-	DisableExpire    bool           `json:"disable_expire,omitempty"`
-	IndependentCache bool           `json:"independent_cache,omitempty"`
-	ClientSubnet     *AddrPrefix    `json:"client_subnet,omitempty"`
+	Strategy         DomainStrategy        `json:"strategy,omitempty"`
+	DisableCache     bool                  `json:"disable_cache,omitempty"`
+	DisableExpire    bool                  `json:"disable_expire,omitempty"`
+	IndependentCache bool                  `json:"independent_cache,omitempty"`
+	ClientSubnet     *badoption.Prefixable `json:"client_subnet,omitempty"`
 }
 
 type DNSFakeIPOptions struct {
diff --git a/option/experimental.go b/option/experimental.go
index 6ab66385..bf0df9e7 100644
--- a/option/experimental.go
+++ b/option/experimental.go
@@ -1,5 +1,7 @@
 package option
 
+import "github.com/sagernet/sing/common/json/badoption"
+
 type ExperimentalOptions struct {
 	CacheFile *CacheFileOptions `json:"cache_file,omitempty"`
 	ClashAPI  *ClashAPIOptions  `json:"clash_api,omitempty"`
@@ -8,24 +10,24 @@ type ExperimentalOptions struct {
 }
 
 type CacheFileOptions struct {
-	Enabled     bool     `json:"enabled,omitempty"`
-	Path        string   `json:"path,omitempty"`
-	CacheID     string   `json:"cache_id,omitempty"`
-	StoreFakeIP bool     `json:"store_fakeip,omitempty"`
-	StoreRDRC   bool     `json:"store_rdrc,omitempty"`
-	RDRCTimeout Duration `json:"rdrc_timeout,omitempty"`
+	Enabled     bool               `json:"enabled,omitempty"`
+	Path        string             `json:"path,omitempty"`
+	CacheID     string             `json:"cache_id,omitempty"`
+	StoreFakeIP bool               `json:"store_fakeip,omitempty"`
+	StoreRDRC   bool               `json:"store_rdrc,omitempty"`
+	RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"`
 }
 
 type ClashAPIOptions struct {
-	ExternalController               string           `json:"external_controller,omitempty"`
-	ExternalUI                       string           `json:"external_ui,omitempty"`
-	ExternalUIDownloadURL            string           `json:"external_ui_download_url,omitempty"`
-	ExternalUIDownloadDetour         string           `json:"external_ui_download_detour,omitempty"`
-	Secret                           string           `json:"secret,omitempty"`
-	DefaultMode                      string           `json:"default_mode,omitempty"`
-	ModeList                         []string         `json:"-"`
-	AccessControlAllowOrigin         Listable[string] `json:"access_control_allow_origin,omitempty"`
-	AccessControlAllowPrivateNetwork bool             `json:"access_control_allow_private_network,omitempty"`
+	ExternalController               string                     `json:"external_controller,omitempty"`
+	ExternalUI                       string                     `json:"external_ui,omitempty"`
+	ExternalUIDownloadURL            string                     `json:"external_ui_download_url,omitempty"`
+	ExternalUIDownloadDetour         string                     `json:"external_ui_download_detour,omitempty"`
+	Secret                           string                     `json:"secret,omitempty"`
+	DefaultMode                      string                     `json:"default_mode,omitempty"`
+	ModeList                         []string                   `json:"-"`
+	AccessControlAllowOrigin         badoption.Listable[string] `json:"access_control_allow_origin,omitempty"`
+	AccessControlAllowPrivateNetwork bool                       `json:"access_control_allow_private_network,omitempty"`
 
 	// Deprecated: migrated to global cache file
 	CacheFile string `json:"cache_file,omitempty"`
diff --git a/option/group.go b/option/group.go
index 72a0f637..02b3a5ec 100644
--- a/option/group.go
+++ b/option/group.go
@@ -1,5 +1,7 @@
 package option
 
+import "github.com/sagernet/sing/common/json/badoption"
+
 type SelectorOutboundOptions struct {
 	Outbounds                 []string `json:"outbounds"`
 	Default                   string   `json:"default,omitempty"`
@@ -7,10 +9,10 @@ type SelectorOutboundOptions struct {
 }
 
 type URLTestOutboundOptions struct {
-	Outbounds                 []string `json:"outbounds"`
-	URL                       string   `json:"url,omitempty"`
-	Interval                  Duration `json:"interval,omitempty"`
-	Tolerance                 uint16   `json:"tolerance,omitempty"`
-	IdleTimeout               Duration `json:"idle_timeout,omitempty"`
-	InterruptExistConnections bool     `json:"interrupt_exist_connections,omitempty"`
+	Outbounds                 []string           `json:"outbounds"`
+	URL                       string             `json:"url,omitempty"`
+	Interval                  badoption.Duration `json:"interval,omitempty"`
+	Tolerance                 uint16             `json:"tolerance,omitempty"`
+	IdleTimeout               badoption.Duration `json:"idle_timeout,omitempty"`
+	InterruptExistConnections bool               `json:"interrupt_exist_connections,omitempty"`
 }
diff --git a/option/inbound.go b/option/inbound.go
index 651d0284..a67719fa 100644
--- a/option/inbound.go
+++ b/option/inbound.go
@@ -7,6 +7,7 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/json"
 	"github.com/sagernet/sing/common/json/badjson"
+	"github.com/sagernet/sing/common/json/badoption"
 	"github.com/sagernet/sing/service"
 )
 
@@ -49,24 +50,24 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro
 
 // Deprecated: Use rule action instead
 type InboundOptions struct {
-	SniffEnabled              bool           `json:"sniff,omitempty"`
-	SniffOverrideDestination  bool           `json:"sniff_override_destination,omitempty"`
-	SniffTimeout              Duration       `json:"sniff_timeout,omitempty"`
-	DomainStrategy            DomainStrategy `json:"domain_strategy,omitempty"`
-	UDPDisableDomainUnmapping bool           `json:"udp_disable_domain_unmapping,omitempty"`
-	Detour                    string         `json:"detour,omitempty"`
+	SniffEnabled              bool               `json:"sniff,omitempty"`
+	SniffOverrideDestination  bool               `json:"sniff_override_destination,omitempty"`
+	SniffTimeout              badoption.Duration `json:"sniff_timeout,omitempty"`
+	DomainStrategy            DomainStrategy     `json:"domain_strategy,omitempty"`
+	UDPDisableDomainUnmapping bool               `json:"udp_disable_domain_unmapping,omitempty"`
+	Detour                    string             `json:"detour,omitempty"`
 }
 
 type ListenOptions struct {
-	Listen               *ListenAddress   `json:"listen,omitempty"`
-	ListenPort           uint16           `json:"listen_port,omitempty"`
-	TCPKeepAlive         Duration         `json:"tcp_keep_alive,omitempty"`
-	TCPKeepAliveInterval Duration         `json:"tcp_keep_alive_interval,omitempty"`
-	TCPFastOpen          bool             `json:"tcp_fast_open,omitempty"`
-	TCPMultiPath         bool             `json:"tcp_multi_path,omitempty"`
-	UDPFragment          *bool            `json:"udp_fragment,omitempty"`
-	UDPFragmentDefault   bool             `json:"-"`
-	UDPTimeout           UDPTimeoutCompat `json:"udp_timeout,omitempty"`
+	Listen               *badoption.Addr    `json:"listen,omitempty"`
+	ListenPort           uint16             `json:"listen_port,omitempty"`
+	TCPKeepAlive         badoption.Duration `json:"tcp_keep_alive,omitempty"`
+	TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"`
+	TCPFastOpen          bool               `json:"tcp_fast_open,omitempty"`
+	TCPMultiPath         bool               `json:"tcp_multi_path,omitempty"`
+	UDPFragment          *bool              `json:"udp_fragment,omitempty"`
+	UDPFragmentDefault   bool               `json:"-"`
+	UDPTimeout           UDPTimeoutCompat   `json:"udp_timeout,omitempty"`
 
 	// Deprecated: removed
 	ProxyProtocol bool `json:"proxy_protocol,omitempty"`
@@ -75,7 +76,7 @@ type ListenOptions struct {
 	InboundOptions
 }
 
-type UDPTimeoutCompat Duration
+type UDPTimeoutCompat badoption.Duration
 
 func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) {
 	return json.Marshal((time.Duration)(c).String())
@@ -88,7 +89,7 @@ func (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error {
 		*c = UDPTimeoutCompat(time.Second * time.Duration(valueNumber))
 		return nil
 	}
-	return json.Unmarshal(data, (*Duration)(c))
+	return json.Unmarshal(data, (*badoption.Duration)(c))
 }
 
 type ListenOptionsWrapper interface {
diff --git a/option/ntp.go b/option/ntp.go
index 0bd2489a..d441d95e 100644
--- a/option/ntp.go
+++ b/option/ntp.go
@@ -1,9 +1,11 @@
 package option
 
+import "github.com/sagernet/sing/common/json/badoption"
+
 type NTPOptions struct {
-	Enabled       bool     `json:"enabled,omitempty"`
-	Interval      Duration `json:"interval,omitempty"`
-	WriteToSystem bool     `json:"write_to_system,omitempty"`
+	Enabled       bool               `json:"enabled,omitempty"`
+	Interval      badoption.Duration `json:"interval,omitempty"`
+	WriteToSystem bool               `json:"write_to_system,omitempty"`
 	ServerOptions
 	DialerOptions
 }
diff --git a/option/outbound.go b/option/outbound.go
index 1dddb354..0e2d5874 100644
--- a/option/outbound.go
+++ b/option/outbound.go
@@ -8,6 +8,7 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/json"
 	"github.com/sagernet/sing/common/json/badjson"
+	"github.com/sagernet/sing/common/json/badoption"
 	M "github.com/sagernet/sing/common/metadata"
 	"github.com/sagernet/sing/service"
 )
@@ -64,21 +65,21 @@ type DialerOptionsWrapper interface {
 }
 
 type DialerOptions struct {
-	Detour              string         `json:"detour,omitempty"`
-	BindInterface       string         `json:"bind_interface,omitempty"`
-	Inet4BindAddress    *ListenAddress `json:"inet4_bind_address,omitempty"`
-	Inet6BindAddress    *ListenAddress `json:"inet6_bind_address,omitempty"`
-	ProtectPath         string         `json:"protect_path,omitempty"`
-	RoutingMark         uint32         `json:"routing_mark,omitempty"`
-	ReuseAddr           bool           `json:"reuse_addr,omitempty"`
-	ConnectTimeout      Duration       `json:"connect_timeout,omitempty"`
-	TCPFastOpen         bool           `json:"tcp_fast_open,omitempty"`
-	TCPMultiPath        bool           `json:"tcp_multi_path,omitempty"`
-	UDPFragment         *bool          `json:"udp_fragment,omitempty"`
-	UDPFragmentDefault  bool           `json:"-"`
-	DomainStrategy      DomainStrategy `json:"domain_strategy,omitempty"`
-	FallbackDelay       Duration       `json:"fallback_delay,omitempty"`
-	IsWireGuardListener bool           `json:"-"`
+	Detour              string             `json:"detour,omitempty"`
+	BindInterface       string             `json:"bind_interface,omitempty"`
+	Inet4BindAddress    *badoption.Addr    `json:"inet4_bind_address,omitempty"`
+	Inet6BindAddress    *badoption.Addr    `json:"inet6_bind_address,omitempty"`
+	ProtectPath         string             `json:"protect_path,omitempty"`
+	RoutingMark         uint32             `json:"routing_mark,omitempty"`
+	ReuseAddr           bool               `json:"reuse_addr,omitempty"`
+	ConnectTimeout      badoption.Duration `json:"connect_timeout,omitempty"`
+	TCPFastOpen         bool               `json:"tcp_fast_open,omitempty"`
+	TCPMultiPath        bool               `json:"tcp_multi_path,omitempty"`
+	UDPFragment         *bool              `json:"udp_fragment,omitempty"`
+	UDPFragmentDefault  bool               `json:"-"`
+	DomainStrategy      DomainStrategy     `json:"domain_strategy,omitempty"`
+	FallbackDelay       badoption.Duration `json:"fallback_delay,omitempty"`
+	IsWireGuardListener bool               `json:"-"`
 }
 
 func (o *DialerOptions) TakeDialerOptions() DialerOptions {
diff --git a/option/platform.go b/option/platform.go
index a43cbf23..e4ecd6fa 100644
--- a/option/platform.go
+++ b/option/platform.go
@@ -3,6 +3,7 @@ package option
 import (
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/json"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 type OnDemandOptions struct {
@@ -12,10 +13,10 @@ type OnDemandOptions struct {
 
 type OnDemandRule struct {
 	Action                *OnDemandRuleAction        `json:"action,omitempty"`
-	DNSSearchDomainMatch  Listable[string]           `json:"dns_search_domain_match,omitempty"`
-	DNSServerAddressMatch Listable[string]           `json:"dns_server_address_match,omitempty"`
+	DNSSearchDomainMatch  badoption.Listable[string] `json:"dns_search_domain_match,omitempty"`
+	DNSServerAddressMatch badoption.Listable[string] `json:"dns_server_address_match,omitempty"`
 	InterfaceTypeMatch    *OnDemandRuleInterfaceType `json:"interface_type_match,omitempty"`
-	SSIDMatch             Listable[string]           `json:"ssid_match,omitempty"`
+	SSIDMatch             badoption.Listable[string] `json:"ssid_match,omitempty"`
 	ProbeURL              string                     `json:"probe_url,omitempty"`
 }
 
diff --git a/option/rule.go b/option/rule.go
index 952afa61..d5ff9925 100644
--- a/option/rule.go
+++ b/option/rule.go
@@ -8,6 +8,7 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/json"
 	"github.com/sagernet/sing/common/json/badjson"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 type _Rule struct {
@@ -66,39 +67,39 @@ func (r Rule) IsValid() bool {
 }
 
 type RawDefaultRule struct {
-	Inbound                  Listable[string] `json:"inbound,omitempty"`
-	IPVersion                int              `json:"ip_version,omitempty"`
-	Network                  Listable[string] `json:"network,omitempty"`
-	AuthUser                 Listable[string] `json:"auth_user,omitempty"`
-	Protocol                 Listable[string] `json:"protocol,omitempty"`
-	Client                   Listable[string] `json:"client,omitempty"`
-	Domain                   Listable[string] `json:"domain,omitempty"`
-	DomainSuffix             Listable[string] `json:"domain_suffix,omitempty"`
-	DomainKeyword            Listable[string] `json:"domain_keyword,omitempty"`
-	DomainRegex              Listable[string] `json:"domain_regex,omitempty"`
-	Geosite                  Listable[string] `json:"geosite,omitempty"`
-	SourceGeoIP              Listable[string] `json:"source_geoip,omitempty"`
-	GeoIP                    Listable[string] `json:"geoip,omitempty"`
-	SourceIPCIDR             Listable[string] `json:"source_ip_cidr,omitempty"`
-	SourceIPIsPrivate        bool             `json:"source_ip_is_private,omitempty"`
-	IPCIDR                   Listable[string] `json:"ip_cidr,omitempty"`
-	IPIsPrivate              bool             `json:"ip_is_private,omitempty"`
-	SourcePort               Listable[uint16] `json:"source_port,omitempty"`
-	SourcePortRange          Listable[string] `json:"source_port_range,omitempty"`
-	Port                     Listable[uint16] `json:"port,omitempty"`
-	PortRange                Listable[string] `json:"port_range,omitempty"`
-	ProcessName              Listable[string] `json:"process_name,omitempty"`
-	ProcessPath              Listable[string] `json:"process_path,omitempty"`
-	ProcessPathRegex         Listable[string] `json:"process_path_regex,omitempty"`
-	PackageName              Listable[string] `json:"package_name,omitempty"`
-	User                     Listable[string] `json:"user,omitempty"`
-	UserID                   Listable[int32]  `json:"user_id,omitempty"`
-	ClashMode                string           `json:"clash_mode,omitempty"`
-	WIFISSID                 Listable[string] `json:"wifi_ssid,omitempty"`
-	WIFIBSSID                Listable[string] `json:"wifi_bssid,omitempty"`
-	RuleSet                  Listable[string] `json:"rule_set,omitempty"`
-	RuleSetIPCIDRMatchSource bool             `json:"rule_set_ip_cidr_match_source,omitempty"`
-	Invert                   bool             `json:"invert,omitempty"`
+	Inbound                  badoption.Listable[string] `json:"inbound,omitempty"`
+	IPVersion                int                        `json:"ip_version,omitempty"`
+	Network                  badoption.Listable[string] `json:"network,omitempty"`
+	AuthUser                 badoption.Listable[string] `json:"auth_user,omitempty"`
+	Protocol                 badoption.Listable[string] `json:"protocol,omitempty"`
+	Client                   badoption.Listable[string] `json:"client,omitempty"`
+	Domain                   badoption.Listable[string] `json:"domain,omitempty"`
+	DomainSuffix             badoption.Listable[string] `json:"domain_suffix,omitempty"`
+	DomainKeyword            badoption.Listable[string] `json:"domain_keyword,omitempty"`
+	DomainRegex              badoption.Listable[string] `json:"domain_regex,omitempty"`
+	Geosite                  badoption.Listable[string] `json:"geosite,omitempty"`
+	SourceGeoIP              badoption.Listable[string] `json:"source_geoip,omitempty"`
+	GeoIP                    badoption.Listable[string] `json:"geoip,omitempty"`
+	SourceIPCIDR             badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
+	SourceIPIsPrivate        bool                       `json:"source_ip_is_private,omitempty"`
+	IPCIDR                   badoption.Listable[string] `json:"ip_cidr,omitempty"`
+	IPIsPrivate              bool                       `json:"ip_is_private,omitempty"`
+	SourcePort               badoption.Listable[uint16] `json:"source_port,omitempty"`
+	SourcePortRange          badoption.Listable[string] `json:"source_port_range,omitempty"`
+	Port                     badoption.Listable[uint16] `json:"port,omitempty"`
+	PortRange                badoption.Listable[string] `json:"port_range,omitempty"`
+	ProcessName              badoption.Listable[string] `json:"process_name,omitempty"`
+	ProcessPath              badoption.Listable[string] `json:"process_path,omitempty"`
+	ProcessPathRegex         badoption.Listable[string] `json:"process_path_regex,omitempty"`
+	PackageName              badoption.Listable[string] `json:"package_name,omitempty"`
+	User                     badoption.Listable[string] `json:"user,omitempty"`
+	UserID                   badoption.Listable[int32]  `json:"user_id,omitempty"`
+	ClashMode                string                     `json:"clash_mode,omitempty"`
+	WIFISSID                 badoption.Listable[string] `json:"wifi_ssid,omitempty"`
+	WIFIBSSID                badoption.Listable[string] `json:"wifi_bssid,omitempty"`
+	RuleSet                  badoption.Listable[string] `json:"rule_set,omitempty"`
+	RuleSetIPCIDRMatchSource bool                       `json:"rule_set_ip_cidr_match_source,omitempty"`
+	Invert                   bool                       `json:"invert,omitempty"`
 
 	// Deprecated: renamed to rule_set_ip_cidr_match_source
 	Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
diff --git a/option/rule_action.go b/option/rule_action.go
index a7244e17..7a76391c 100644
--- a/option/rule_action.go
+++ b/option/rule_action.go
@@ -3,6 +3,7 @@ package option
 import (
 	"context"
 	"fmt"
+	"net/netip"
 	"time"
 
 	C "github.com/sagernet/sing-box/constant"
@@ -11,6 +12,7 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/json"
 	"github.com/sagernet/sing/common/json/badjson"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 type _RuleAction struct {
@@ -177,7 +179,7 @@ type _DNSRouteActionOptions struct {
 	// Deprecated: Use DNSRouteOptionsActionOptions instead.
 	RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
 	// Deprecated: Use DNSRouteOptionsActionOptions instead.
-	ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
+	ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
 }
 
 type DNSRouteActionOptions _DNSRouteActionOptions
@@ -197,9 +199,9 @@ func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data [
 }
 
 type _DNSRouteOptionsActionOptions struct {
-	DisableCache bool        `json:"disable_cache,omitempty"`
-	RewriteTTL   *uint32     `json:"rewrite_ttl,omitempty"`
-	ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
+	DisableCache bool                  `json:"disable_cache,omitempty"`
+	RewriteTTL   *uint32               `json:"rewrite_ttl,omitempty"`
+	ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
 }
 
 type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions
@@ -225,10 +227,10 @@ func (d DirectActionOptions) Descriptions() []string {
 		descriptions = append(descriptions, "bind_interface="+d.BindInterface)
 	}
 	if d.Inet4BindAddress != nil {
-		descriptions = append(descriptions, "inet4_bind_address="+d.Inet4BindAddress.Build().String())
+		descriptions = append(descriptions, "inet4_bind_address="+d.Inet4BindAddress.Build(netip.IPv4Unspecified()).String())
 	}
 	if d.Inet6BindAddress != nil {
-		descriptions = append(descriptions, "inet6_bind_address="+d.Inet6BindAddress.Build().String())
+		descriptions = append(descriptions, "inet6_bind_address="+d.Inet6BindAddress.Build(netip.IPv6Unspecified()).String())
 	}
 	if d.RoutingMark != 0 {
 		descriptions = append(descriptions, "routing_mark="+fmt.Sprintf("0x%x", d.RoutingMark))
@@ -294,8 +296,8 @@ func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error {
 }
 
 type RouteActionSniff struct {
-	Sniffer Listable[string] `json:"sniffer,omitempty"`
-	Timeout Duration         `json:"timeout,omitempty"`
+	Sniffer badoption.Listable[string] `json:"sniffer,omitempty"`
+	Timeout badoption.Duration         `json:"timeout,omitempty"`
 }
 
 type RouteActionResolve struct {
diff --git a/option/rule_dns.go b/option/rule_dns.go
index 894e95d3..e758488b 100644
--- a/option/rule_dns.go
+++ b/option/rule_dns.go
@@ -9,6 +9,7 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/json"
 	"github.com/sagernet/sing/common/json/badjson"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 type _DNSRule struct {
@@ -67,41 +68,41 @@ func (r DNSRule) IsValid() bool {
 }
 
 type RawDefaultDNSRule struct {
-	Inbound                  Listable[string]       `json:"inbound,omitempty"`
-	IPVersion                int                    `json:"ip_version,omitempty"`
-	QueryType                Listable[DNSQueryType] `json:"query_type,omitempty"`
-	Network                  Listable[string]       `json:"network,omitempty"`
-	AuthUser                 Listable[string]       `json:"auth_user,omitempty"`
-	Protocol                 Listable[string]       `json:"protocol,omitempty"`
-	Domain                   Listable[string]       `json:"domain,omitempty"`
-	DomainSuffix             Listable[string]       `json:"domain_suffix,omitempty"`
-	DomainKeyword            Listable[string]       `json:"domain_keyword,omitempty"`
-	DomainRegex              Listable[string]       `json:"domain_regex,omitempty"`
-	Geosite                  Listable[string]       `json:"geosite,omitempty"`
-	SourceGeoIP              Listable[string]       `json:"source_geoip,omitempty"`
-	GeoIP                    Listable[string]       `json:"geoip,omitempty"`
-	IPCIDR                   Listable[string]       `json:"ip_cidr,omitempty"`
-	IPIsPrivate              bool                   `json:"ip_is_private,omitempty"`
-	SourceIPCIDR             Listable[string]       `json:"source_ip_cidr,omitempty"`
-	SourceIPIsPrivate        bool                   `json:"source_ip_is_private,omitempty"`
-	SourcePort               Listable[uint16]       `json:"source_port,omitempty"`
-	SourcePortRange          Listable[string]       `json:"source_port_range,omitempty"`
-	Port                     Listable[uint16]       `json:"port,omitempty"`
-	PortRange                Listable[string]       `json:"port_range,omitempty"`
-	ProcessName              Listable[string]       `json:"process_name,omitempty"`
-	ProcessPath              Listable[string]       `json:"process_path,omitempty"`
-	ProcessPathRegex         Listable[string]       `json:"process_path_regex,omitempty"`
-	PackageName              Listable[string]       `json:"package_name,omitempty"`
-	User                     Listable[string]       `json:"user,omitempty"`
-	UserID                   Listable[int32]        `json:"user_id,omitempty"`
-	Outbound                 Listable[string]       `json:"outbound,omitempty"`
-	ClashMode                string                 `json:"clash_mode,omitempty"`
-	WIFISSID                 Listable[string]       `json:"wifi_ssid,omitempty"`
-	WIFIBSSID                Listable[string]       `json:"wifi_bssid,omitempty"`
-	RuleSet                  Listable[string]       `json:"rule_set,omitempty"`
-	RuleSetIPCIDRMatchSource bool                   `json:"rule_set_ip_cidr_match_source,omitempty"`
-	RuleSetIPCIDRAcceptEmpty bool                   `json:"rule_set_ip_cidr_accept_empty,omitempty"`
-	Invert                   bool                   `json:"invert,omitempty"`
+	Inbound                  badoption.Listable[string]       `json:"inbound,omitempty"`
+	IPVersion                int                              `json:"ip_version,omitempty"`
+	QueryType                badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
+	Network                  badoption.Listable[string]       `json:"network,omitempty"`
+	AuthUser                 badoption.Listable[string]       `json:"auth_user,omitempty"`
+	Protocol                 badoption.Listable[string]       `json:"protocol,omitempty"`
+	Domain                   badoption.Listable[string]       `json:"domain,omitempty"`
+	DomainSuffix             badoption.Listable[string]       `json:"domain_suffix,omitempty"`
+	DomainKeyword            badoption.Listable[string]       `json:"domain_keyword,omitempty"`
+	DomainRegex              badoption.Listable[string]       `json:"domain_regex,omitempty"`
+	Geosite                  badoption.Listable[string]       `json:"geosite,omitempty"`
+	SourceGeoIP              badoption.Listable[string]       `json:"source_geoip,omitempty"`
+	GeoIP                    badoption.Listable[string]       `json:"geoip,omitempty"`
+	IPCIDR                   badoption.Listable[string]       `json:"ip_cidr,omitempty"`
+	IPIsPrivate              bool                             `json:"ip_is_private,omitempty"`
+	SourceIPCIDR             badoption.Listable[string]       `json:"source_ip_cidr,omitempty"`
+	SourceIPIsPrivate        bool                             `json:"source_ip_is_private,omitempty"`
+	SourcePort               badoption.Listable[uint16]       `json:"source_port,omitempty"`
+	SourcePortRange          badoption.Listable[string]       `json:"source_port_range,omitempty"`
+	Port                     badoption.Listable[uint16]       `json:"port,omitempty"`
+	PortRange                badoption.Listable[string]       `json:"port_range,omitempty"`
+	ProcessName              badoption.Listable[string]       `json:"process_name,omitempty"`
+	ProcessPath              badoption.Listable[string]       `json:"process_path,omitempty"`
+	ProcessPathRegex         badoption.Listable[string]       `json:"process_path_regex,omitempty"`
+	PackageName              badoption.Listable[string]       `json:"package_name,omitempty"`
+	User                     badoption.Listable[string]       `json:"user,omitempty"`
+	UserID                   badoption.Listable[int32]        `json:"user_id,omitempty"`
+	Outbound                 badoption.Listable[string]       `json:"outbound,omitempty"`
+	ClashMode                string                           `json:"clash_mode,omitempty"`
+	WIFISSID                 badoption.Listable[string]       `json:"wifi_ssid,omitempty"`
+	WIFIBSSID                badoption.Listable[string]       `json:"wifi_bssid,omitempty"`
+	RuleSet                  badoption.Listable[string]       `json:"rule_set,omitempty"`
+	RuleSetIPCIDRMatchSource bool                             `json:"rule_set_ip_cidr_match_source,omitempty"`
+	RuleSetIPCIDRAcceptEmpty bool                             `json:"rule_set_ip_cidr_accept_empty,omitempty"`
+	Invert                   bool                             `json:"invert,omitempty"`
 
 	// Deprecated: renamed to rule_set_ip_cidr_match_source
 	Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
diff --git a/option/rule_set.go b/option/rule_set.go
index 358821de..f0d96bb6 100644
--- a/option/rule_set.go
+++ b/option/rule_set.go
@@ -10,6 +10,7 @@ import (
 	F "github.com/sagernet/sing/common/format"
 	"github.com/sagernet/sing/common/json"
 	"github.com/sagernet/sing/common/json/badjson"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"go4.org/netipx"
 )
@@ -84,9 +85,9 @@ type LocalRuleSet struct {
 }
 
 type RemoteRuleSet struct {
-	URL            string   `json:"url"`
-	DownloadDetour string   `json:"download_detour,omitempty"`
-	UpdateInterval Duration `json:"update_interval,omitempty"`
+	URL            string             `json:"url"`
+	DownloadDetour string             `json:"download_detour,omitempty"`
+	UpdateInterval badoption.Duration `json:"update_interval,omitempty"`
 }
 
 type _HeadlessRule struct {
@@ -145,32 +146,32 @@ func (r HeadlessRule) IsValid() bool {
 }
 
 type DefaultHeadlessRule struct {
-	QueryType        Listable[DNSQueryType] `json:"query_type,omitempty"`
-	Network          Listable[string]       `json:"network,omitempty"`
-	Domain           Listable[string]       `json:"domain,omitempty"`
-	DomainSuffix     Listable[string]       `json:"domain_suffix,omitempty"`
-	DomainKeyword    Listable[string]       `json:"domain_keyword,omitempty"`
-	DomainRegex      Listable[string]       `json:"domain_regex,omitempty"`
-	SourceIPCIDR     Listable[string]       `json:"source_ip_cidr,omitempty"`
-	IPCIDR           Listable[string]       `json:"ip_cidr,omitempty"`
-	SourcePort       Listable[uint16]       `json:"source_port,omitempty"`
-	SourcePortRange  Listable[string]       `json:"source_port_range,omitempty"`
-	Port             Listable[uint16]       `json:"port,omitempty"`
-	PortRange        Listable[string]       `json:"port_range,omitempty"`
-	ProcessName      Listable[string]       `json:"process_name,omitempty"`
-	ProcessPath      Listable[string]       `json:"process_path,omitempty"`
-	ProcessPathRegex Listable[string]       `json:"process_path_regex,omitempty"`
-	PackageName      Listable[string]       `json:"package_name,omitempty"`
-	WIFISSID         Listable[string]       `json:"wifi_ssid,omitempty"`
-	WIFIBSSID        Listable[string]       `json:"wifi_bssid,omitempty"`
-	Invert           bool                   `json:"invert,omitempty"`
+	QueryType        badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
+	Network          badoption.Listable[string]       `json:"network,omitempty"`
+	Domain           badoption.Listable[string]       `json:"domain,omitempty"`
+	DomainSuffix     badoption.Listable[string]       `json:"domain_suffix,omitempty"`
+	DomainKeyword    badoption.Listable[string]       `json:"domain_keyword,omitempty"`
+	DomainRegex      badoption.Listable[string]       `json:"domain_regex,omitempty"`
+	SourceIPCIDR     badoption.Listable[string]       `json:"source_ip_cidr,omitempty"`
+	IPCIDR           badoption.Listable[string]       `json:"ip_cidr,omitempty"`
+	SourcePort       badoption.Listable[uint16]       `json:"source_port,omitempty"`
+	SourcePortRange  badoption.Listable[string]       `json:"source_port_range,omitempty"`
+	Port             badoption.Listable[uint16]       `json:"port,omitempty"`
+	PortRange        badoption.Listable[string]       `json:"port_range,omitempty"`
+	ProcessName      badoption.Listable[string]       `json:"process_name,omitempty"`
+	ProcessPath      badoption.Listable[string]       `json:"process_path,omitempty"`
+	ProcessPathRegex badoption.Listable[string]       `json:"process_path_regex,omitempty"`
+	PackageName      badoption.Listable[string]       `json:"package_name,omitempty"`
+	WIFISSID         badoption.Listable[string]       `json:"wifi_ssid,omitempty"`
+	WIFIBSSID        badoption.Listable[string]       `json:"wifi_bssid,omitempty"`
+	Invert           bool                             `json:"invert,omitempty"`
 
 	DomainMatcher *domain.Matcher `json:"-"`
 	SourceIPSet   *netipx.IPSet   `json:"-"`
 	IPSet         *netipx.IPSet   `json:"-"`
 
-	AdGuardDomain        Listable[string]       `json:"-"`
-	AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"`
+	AdGuardDomain        badoption.Listable[string] `json:"-"`
+	AdGuardDomainMatcher *domain.AdGuardMatcher     `json:"-"`
 }
 
 func (r DefaultHeadlessRule) IsValid() bool {
diff --git a/option/simple.go b/option/simple.go
index 78171ce4..5fda30ef 100644
--- a/option/simple.go
+++ b/option/simple.go
@@ -1,6 +1,9 @@
 package option
 
-import "github.com/sagernet/sing/common/auth"
+import (
+	"github.com/sagernet/sing/common/auth"
+	"github.com/sagernet/sing/common/json/badoption"
+)
 
 type SocksInboundOptions struct {
 	ListenOptions
@@ -30,6 +33,6 @@ type HTTPOutboundOptions struct {
 	Username string `json:"username,omitempty"`
 	Password string `json:"password,omitempty"`
 	OutboundTLSOptionsContainer
-	Path    string     `json:"path,omitempty"`
-	Headers HTTPHeader `json:"headers,omitempty"`
+	Path    string               `json:"path,omitempty"`
+	Headers badoption.HTTPHeader `json:"headers,omitempty"`
 }
diff --git a/option/ssh.go b/option/ssh.go
index d0bfbf74..1c6ca6bb 100644
--- a/option/ssh.go
+++ b/option/ssh.go
@@ -1,14 +1,16 @@
 package option
 
+import "github.com/sagernet/sing/common/json/badoption"
+
 type SSHOutboundOptions struct {
 	DialerOptions
 	ServerOptions
-	User                 string           `json:"user,omitempty"`
-	Password             string           `json:"password,omitempty"`
-	PrivateKey           Listable[string] `json:"private_key,omitempty"`
-	PrivateKeyPath       string           `json:"private_key_path,omitempty"`
-	PrivateKeyPassphrase string           `json:"private_key_passphrase,omitempty"`
-	HostKey              Listable[string] `json:"host_key,omitempty"`
-	HostKeyAlgorithms    Listable[string] `json:"host_key_algorithms,omitempty"`
-	ClientVersion        string           `json:"client_version,omitempty"`
+	User                 string                     `json:"user,omitempty"`
+	Password             string                     `json:"password,omitempty"`
+	PrivateKey           badoption.Listable[string] `json:"private_key,omitempty"`
+	PrivateKeyPath       string                     `json:"private_key_path,omitempty"`
+	PrivateKeyPassphrase string                     `json:"private_key_passphrase,omitempty"`
+	HostKey              badoption.Listable[string] `json:"host_key,omitempty"`
+	HostKeyAlgorithms    badoption.Listable[string] `json:"host_key_algorithms,omitempty"`
+	ClientVersion        string                     `json:"client_version,omitempty"`
 }
diff --git a/option/time_unit.go b/option/time_unit.go
deleted file mode 100644
index 5e531dad..00000000
--- a/option/time_unit.go
+++ /dev/null
@@ -1,226 +0,0 @@
-package option
-
-import (
-	"errors"
-	"time"
-)
-
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-const durationDay = 24 * time.Hour
-
-var unitMap = map[string]uint64{
-	"ns": uint64(time.Nanosecond),
-	"us": uint64(time.Microsecond),
-	"µs": uint64(time.Microsecond), // U+00B5 = micro symbol
-	"μs": uint64(time.Microsecond), // U+03BC = Greek letter mu
-	"ms": uint64(time.Millisecond),
-	"s":  uint64(time.Second),
-	"m":  uint64(time.Minute),
-	"h":  uint64(time.Hour),
-	"d":  uint64(durationDay),
-}
-
-// ParseDuration parses a duration string.
-// A duration string is a possibly signed sequence of
-// decimal numbers, each with optional fraction and a unit suffix,
-// such as "300ms", "-1.5h" or "2h45m".
-// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
-func ParseDuration(s string) (Duration, error) {
-	// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
-	orig := s
-	var d uint64
-	neg := false
-
-	// Consume [-+]?
-	if s != "" {
-		c := s[0]
-		if c == '-' || c == '+' {
-			neg = c == '-'
-			s = s[1:]
-		}
-	}
-	// Special case: if all that is left is "0", this is zero.
-	if s == "0" {
-		return 0, nil
-	}
-	if s == "" {
-		return 0, errors.New("time: invalid duration " + quote(orig))
-	}
-	for s != "" {
-		var (
-			v, f  uint64      // integers before, after decimal point
-			scale float64 = 1 // value = v + f/scale
-		)
-
-		var err error
-
-		// The next character must be [0-9.]
-		if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
-			return 0, errors.New("time: invalid duration " + quote(orig))
-		}
-		// Consume [0-9]*
-		pl := len(s)
-		v, s, err = leadingInt(s)
-		if err != nil {
-			return 0, errors.New("time: invalid duration " + quote(orig))
-		}
-		pre := pl != len(s) // whether we consumed anything before a period
-
-		// Consume (\.[0-9]*)?
-		post := false
-		if s != "" && s[0] == '.' {
-			s = s[1:]
-			pl := len(s)
-			f, scale, s = leadingFraction(s)
-			post = pl != len(s)
-		}
-		if !pre && !post {
-			// no digits (e.g. ".s" or "-.s")
-			return 0, errors.New("time: invalid duration " + quote(orig))
-		}
-
-		// Consume unit.
-		i := 0
-		for ; i < len(s); i++ {
-			c := s[i]
-			if c == '.' || '0' <= c && c <= '9' {
-				break
-			}
-		}
-		if i == 0 {
-			return 0, errors.New("time: missing unit in duration " + quote(orig))
-		}
-		u := s[:i]
-		s = s[i:]
-		unit, ok := unitMap[u]
-		if !ok {
-			return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig))
-		}
-		if v > 1<<63/unit {
-			// overflow
-			return 0, errors.New("time: invalid duration " + quote(orig))
-		}
-		v *= unit
-		if f > 0 {
-			// float64 is needed to be nanosecond accurate for fractions of hours.
-			// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
-			v += uint64(float64(f) * (float64(unit) / scale))
-			if v > 1<<63 {
-				// overflow
-				return 0, errors.New("time: invalid duration " + quote(orig))
-			}
-		}
-		d += v
-		if d > 1<<63 {
-			return 0, errors.New("time: invalid duration " + quote(orig))
-		}
-	}
-	if neg {
-		return -Duration(d), nil
-	}
-	if d > 1<<63-1 {
-		return 0, errors.New("time: invalid duration " + quote(orig))
-	}
-	return Duration(d), nil
-}
-
-var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
-
-// leadingInt consumes the leading [0-9]* from s.
-func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, err error) {
-	i := 0
-	for ; i < len(s); i++ {
-		c := s[i]
-		if c < '0' || c > '9' {
-			break
-		}
-		if x > 1<<63/10 {
-			// overflow
-			return 0, rem, errLeadingInt
-		}
-		x = x*10 + uint64(c) - '0'
-		if x > 1<<63 {
-			// overflow
-			return 0, rem, errLeadingInt
-		}
-	}
-	return x, s[i:], nil
-}
-
-// leadingFraction consumes the leading [0-9]* from s.
-// It is used only for fractions, so does not return an error on overflow,
-// it just stops accumulating precision.
-func leadingFraction(s string) (x uint64, scale float64, rem string) {
-	i := 0
-	scale = 1
-	overflow := false
-	for ; i < len(s); i++ {
-		c := s[i]
-		if c < '0' || c > '9' {
-			break
-		}
-		if overflow {
-			continue
-		}
-		if x > (1<<63-1)/10 {
-			// It's possible for overflow to give a positive number, so take care.
-			overflow = true
-			continue
-		}
-		y := x*10 + uint64(c) - '0'
-		if y > 1<<63 {
-			overflow = true
-			continue
-		}
-		x = y
-		scale *= 10
-	}
-	return x, scale, s[i:]
-}
-
-// These are borrowed from unicode/utf8 and strconv and replicate behavior in
-// that package, since we can't take a dependency on either.
-const (
-	lowerhex  = "0123456789abcdef"
-	runeSelf  = 0x80
-	runeError = '\uFFFD'
-)
-
-func quote(s string) string {
-	buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes
-	buf[0] = '"'
-	for i, c := range s {
-		if c >= runeSelf || c < ' ' {
-			// This means you are asking us to parse a time.Duration or
-			// time.Location with unprintable or non-ASCII characters in it.
-			// We don't expect to hit this case very often. We could try to
-			// reproduce strconv.Quote's behavior with full fidelity but
-			// given how rarely we expect to hit these edge cases, speed and
-			// conciseness are better.
-			var width int
-			if c == runeError {
-				width = 1
-				if i+2 < len(s) && s[i:i+3] == string(runeError) {
-					width = 3
-				}
-			} else {
-				width = len(string(c))
-			}
-			for j := 0; j < width; j++ {
-				buf = append(buf, `\x`...)
-				buf = append(buf, lowerhex[s[i+j]>>4])
-				buf = append(buf, lowerhex[s[i+j]&0xF])
-			}
-		} else {
-			if c == '"' || c == '\\' {
-				buf = append(buf, '\\')
-			}
-			buf = append(buf, string(c)...)
-		}
-	}
-	buf = append(buf, '"')
-	return string(buf)
-}
diff --git a/option/tls.go b/option/tls.go
index 3680afe0..72aaaef1 100644
--- a/option/tls.go
+++ b/option/tls.go
@@ -1,20 +1,22 @@
 package option
 
+import "github.com/sagernet/sing/common/json/badoption"
+
 type InboundTLSOptions struct {
-	Enabled         bool                   `json:"enabled,omitempty"`
-	ServerName      string                 `json:"server_name,omitempty"`
-	Insecure        bool                   `json:"insecure,omitempty"`
-	ALPN            Listable[string]       `json:"alpn,omitempty"`
-	MinVersion      string                 `json:"min_version,omitempty"`
-	MaxVersion      string                 `json:"max_version,omitempty"`
-	CipherSuites    Listable[string]       `json:"cipher_suites,omitempty"`
-	Certificate     Listable[string]       `json:"certificate,omitempty"`
-	CertificatePath string                 `json:"certificate_path,omitempty"`
-	Key             Listable[string]       `json:"key,omitempty"`
-	KeyPath         string                 `json:"key_path,omitempty"`
-	ACME            *InboundACMEOptions    `json:"acme,omitempty"`
-	ECH             *InboundECHOptions     `json:"ech,omitempty"`
-	Reality         *InboundRealityOptions `json:"reality,omitempty"`
+	Enabled         bool                       `json:"enabled,omitempty"`
+	ServerName      string                     `json:"server_name,omitempty"`
+	Insecure        bool                       `json:"insecure,omitempty"`
+	ALPN            badoption.Listable[string] `json:"alpn,omitempty"`
+	MinVersion      string                     `json:"min_version,omitempty"`
+	MaxVersion      string                     `json:"max_version,omitempty"`
+	CipherSuites    badoption.Listable[string] `json:"cipher_suites,omitempty"`
+	Certificate     badoption.Listable[string] `json:"certificate,omitempty"`
+	CertificatePath string                     `json:"certificate_path,omitempty"`
+	Key             badoption.Listable[string] `json:"key,omitempty"`
+	KeyPath         string                     `json:"key_path,omitempty"`
+	ACME            *InboundACMEOptions        `json:"acme,omitempty"`
+	ECH             *InboundECHOptions         `json:"ech,omitempty"`
+	Reality         *InboundRealityOptions     `json:"reality,omitempty"`
 }
 
 type InboundTLSOptionsContainer struct {
@@ -35,19 +37,19 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL
 }
 
 type OutboundTLSOptions struct {
-	Enabled         bool                    `json:"enabled,omitempty"`
-	DisableSNI      bool                    `json:"disable_sni,omitempty"`
-	ServerName      string                  `json:"server_name,omitempty"`
-	Insecure        bool                    `json:"insecure,omitempty"`
-	ALPN            Listable[string]        `json:"alpn,omitempty"`
-	MinVersion      string                  `json:"min_version,omitempty"`
-	MaxVersion      string                  `json:"max_version,omitempty"`
-	CipherSuites    Listable[string]        `json:"cipher_suites,omitempty"`
-	Certificate     Listable[string]        `json:"certificate,omitempty"`
-	CertificatePath string                  `json:"certificate_path,omitempty"`
-	ECH             *OutboundECHOptions     `json:"ech,omitempty"`
-	UTLS            *OutboundUTLSOptions    `json:"utls,omitempty"`
-	Reality         *OutboundRealityOptions `json:"reality,omitempty"`
+	Enabled         bool                       `json:"enabled,omitempty"`
+	DisableSNI      bool                       `json:"disable_sni,omitempty"`
+	ServerName      string                     `json:"server_name,omitempty"`
+	Insecure        bool                       `json:"insecure,omitempty"`
+	ALPN            badoption.Listable[string] `json:"alpn,omitempty"`
+	MinVersion      string                     `json:"min_version,omitempty"`
+	MaxVersion      string                     `json:"max_version,omitempty"`
+	CipherSuites    badoption.Listable[string] `json:"cipher_suites,omitempty"`
+	Certificate     badoption.Listable[string] `json:"certificate,omitempty"`
+	CertificatePath string                     `json:"certificate_path,omitempty"`
+	ECH             *OutboundECHOptions        `json:"ech,omitempty"`
+	UTLS            *OutboundUTLSOptions       `json:"utls,omitempty"`
+	Reality         *OutboundRealityOptions    `json:"reality,omitempty"`
 }
 
 type OutboundTLSOptionsContainer struct {
@@ -71,8 +73,8 @@ type InboundRealityOptions struct {
 	Enabled           bool                           `json:"enabled,omitempty"`
 	Handshake         InboundRealityHandshakeOptions `json:"handshake,omitempty"`
 	PrivateKey        string                         `json:"private_key,omitempty"`
-	ShortID           Listable[string]               `json:"short_id,omitempty"`
-	MaxTimeDifference Duration                       `json:"max_time_difference,omitempty"`
+	ShortID           badoption.Listable[string]     `json:"short_id,omitempty"`
+	MaxTimeDifference badoption.Duration             `json:"max_time_difference,omitempty"`
 }
 
 type InboundRealityHandshakeOptions struct {
@@ -81,19 +83,19 @@ type InboundRealityHandshakeOptions struct {
 }
 
 type InboundECHOptions struct {
-	Enabled                     bool             `json:"enabled,omitempty"`
-	PQSignatureSchemesEnabled   bool             `json:"pq_signature_schemes_enabled,omitempty"`
-	DynamicRecordSizingDisabled bool             `json:"dynamic_record_sizing_disabled,omitempty"`
-	Key                         Listable[string] `json:"key,omitempty"`
-	KeyPath                     string           `json:"key_path,omitempty"`
+	Enabled                     bool                       `json:"enabled,omitempty"`
+	PQSignatureSchemesEnabled   bool                       `json:"pq_signature_schemes_enabled,omitempty"`
+	DynamicRecordSizingDisabled bool                       `json:"dynamic_record_sizing_disabled,omitempty"`
+	Key                         badoption.Listable[string] `json:"key,omitempty"`
+	KeyPath                     string                     `json:"key_path,omitempty"`
 }
 
 type OutboundECHOptions struct {
-	Enabled                     bool             `json:"enabled,omitempty"`
-	PQSignatureSchemesEnabled   bool             `json:"pq_signature_schemes_enabled,omitempty"`
-	DynamicRecordSizingDisabled bool             `json:"dynamic_record_sizing_disabled,omitempty"`
-	Config                      Listable[string] `json:"config,omitempty"`
-	ConfigPath                  string           `json:"config_path,omitempty"`
+	Enabled                     bool                       `json:"enabled,omitempty"`
+	PQSignatureSchemesEnabled   bool                       `json:"pq_signature_schemes_enabled,omitempty"`
+	DynamicRecordSizingDisabled bool                       `json:"dynamic_record_sizing_disabled,omitempty"`
+	Config                      badoption.Listable[string] `json:"config,omitempty"`
+	ConfigPath                  string                     `json:"config_path,omitempty"`
 }
 
 type OutboundUTLSOptions struct {
diff --git a/option/tls_acme.go b/option/tls_acme.go
index 9c2e081f..50270607 100644
--- a/option/tls_acme.go
+++ b/option/tls_acme.go
@@ -5,10 +5,11 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/json"
 	"github.com/sagernet/sing/common/json/badjson"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 type InboundACMEOptions struct {
-	Domain                  Listable[string]            `json:"domain,omitempty"`
+	Domain                  badoption.Listable[string]  `json:"domain,omitempty"`
 	DataDirectory           string                      `json:"data_directory,omitempty"`
 	DefaultServerName       string                      `json:"default_server_name,omitempty"`
 	Email                   string                      `json:"email,omitempty"`
diff --git a/option/tuic.go b/option/tuic.go
index 736d5a66..a9b739ec 100644
--- a/option/tuic.go
+++ b/option/tuic.go
@@ -1,12 +1,14 @@
 package option
 
+import "github.com/sagernet/sing/common/json/badoption"
+
 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"`
+	Users             []TUICUser         `json:"users,omitempty"`
+	CongestionControl string             `json:"congestion_control,omitempty"`
+	AuthTimeout       badoption.Duration `json:"auth_timeout,omitempty"`
+	ZeroRTTHandshake  bool               `json:"zero_rtt_handshake,omitempty"`
+	Heartbeat         badoption.Duration `json:"heartbeat,omitempty"`
 	InboundTLSOptionsContainer
 }
 
@@ -19,13 +21,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"`
+	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         badoption.Duration `json:"heartbeat,omitempty"`
+	Network           NetworkList        `json:"network,omitempty"`
 	OutboundTLSOptionsContainer
 }
diff --git a/option/tun.go b/option/tun.go
index dbb1bfea..a7a0f6bc 100644
--- a/option/tun.go
+++ b/option/tun.go
@@ -7,51 +7,52 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	F "github.com/sagernet/sing/common/format"
 	"github.com/sagernet/sing/common/json"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 type TunInboundOptions struct {
-	InterfaceName          string                 `json:"interface_name,omitempty"`
-	MTU                    uint32                 `json:"mtu,omitempty"`
-	GSO                    bool                   `json:"gso,omitempty"`
-	Address                Listable[netip.Prefix] `json:"address,omitempty"`
-	AutoRoute              bool                   `json:"auto_route,omitempty"`
-	IPRoute2TableIndex     int                    `json:"iproute2_table_index,omitempty"`
-	IPRoute2RuleIndex      int                    `json:"iproute2_rule_index,omitempty"`
-	AutoRedirect           bool                   `json:"auto_redirect,omitempty"`
-	AutoRedirectInputMark  FwMark                 `json:"auto_redirect_input_mark,omitempty"`
-	AutoRedirectOutputMark FwMark                 `json:"auto_redirect_output_mark,omitempty"`
-	StrictRoute            bool                   `json:"strict_route,omitempty"`
-	RouteAddress           Listable[netip.Prefix] `json:"route_address,omitempty"`
-	RouteAddressSet        Listable[string]       `json:"route_address_set,omitempty"`
-	RouteExcludeAddress    Listable[netip.Prefix] `json:"route_exclude_address,omitempty"`
-	RouteExcludeAddressSet Listable[string]       `json:"route_exclude_address_set,omitempty"`
-	IncludeInterface       Listable[string]       `json:"include_interface,omitempty"`
-	ExcludeInterface       Listable[string]       `json:"exclude_interface,omitempty"`
-	IncludeUID             Listable[uint32]       `json:"include_uid,omitempty"`
-	IncludeUIDRange        Listable[string]       `json:"include_uid_range,omitempty"`
-	ExcludeUID             Listable[uint32]       `json:"exclude_uid,omitempty"`
-	ExcludeUIDRange        Listable[string]       `json:"exclude_uid_range,omitempty"`
-	IncludeAndroidUser     Listable[int]          `json:"include_android_user,omitempty"`
-	IncludePackage         Listable[string]       `json:"include_package,omitempty"`
-	ExcludePackage         Listable[string]       `json:"exclude_package,omitempty"`
-	EndpointIndependentNat bool                   `json:"endpoint_independent_nat,omitempty"`
-	UDPTimeout             UDPTimeoutCompat       `json:"udp_timeout,omitempty"`
-	Stack                  string                 `json:"stack,omitempty"`
-	Platform               *TunPlatformOptions    `json:"platform,omitempty"`
+	InterfaceName          string                           `json:"interface_name,omitempty"`
+	MTU                    uint32                           `json:"mtu,omitempty"`
+	GSO                    bool                             `json:"gso,omitempty"`
+	Address                badoption.Listable[netip.Prefix] `json:"address,omitempty"`
+	AutoRoute              bool                             `json:"auto_route,omitempty"`
+	IPRoute2TableIndex     int                              `json:"iproute2_table_index,omitempty"`
+	IPRoute2RuleIndex      int                              `json:"iproute2_rule_index,omitempty"`
+	AutoRedirect           bool                             `json:"auto_redirect,omitempty"`
+	AutoRedirectInputMark  FwMark                           `json:"auto_redirect_input_mark,omitempty"`
+	AutoRedirectOutputMark FwMark                           `json:"auto_redirect_output_mark,omitempty"`
+	StrictRoute            bool                             `json:"strict_route,omitempty"`
+	RouteAddress           badoption.Listable[netip.Prefix] `json:"route_address,omitempty"`
+	RouteAddressSet        badoption.Listable[string]       `json:"route_address_set,omitempty"`
+	RouteExcludeAddress    badoption.Listable[netip.Prefix] `json:"route_exclude_address,omitempty"`
+	RouteExcludeAddressSet badoption.Listable[string]       `json:"route_exclude_address_set,omitempty"`
+	IncludeInterface       badoption.Listable[string]       `json:"include_interface,omitempty"`
+	ExcludeInterface       badoption.Listable[string]       `json:"exclude_interface,omitempty"`
+	IncludeUID             badoption.Listable[uint32]       `json:"include_uid,omitempty"`
+	IncludeUIDRange        badoption.Listable[string]       `json:"include_uid_range,omitempty"`
+	ExcludeUID             badoption.Listable[uint32]       `json:"exclude_uid,omitempty"`
+	ExcludeUIDRange        badoption.Listable[string]       `json:"exclude_uid_range,omitempty"`
+	IncludeAndroidUser     badoption.Listable[int]          `json:"include_android_user,omitempty"`
+	IncludePackage         badoption.Listable[string]       `json:"include_package,omitempty"`
+	ExcludePackage         badoption.Listable[string]       `json:"exclude_package,omitempty"`
+	EndpointIndependentNat bool                             `json:"endpoint_independent_nat,omitempty"`
+	UDPTimeout             UDPTimeoutCompat                 `json:"udp_timeout,omitempty"`
+	Stack                  string                           `json:"stack,omitempty"`
+	Platform               *TunPlatformOptions              `json:"platform,omitempty"`
 	InboundOptions
 
 	// Deprecated: merged to Address
-	Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
+	Inet4Address badoption.Listable[netip.Prefix] `json:"inet4_address,omitempty"`
 	// Deprecated: merged to Address
-	Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
+	Inet6Address badoption.Listable[netip.Prefix] `json:"inet6_address,omitempty"`
 	// Deprecated: merged to RouteAddress
-	Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
+	Inet4RouteAddress badoption.Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
 	// Deprecated: merged to RouteAddress
-	Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
+	Inet6RouteAddress badoption.Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
 	// Deprecated: merged to RouteExcludeAddress
-	Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"`
+	Inet4RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"`
 	// Deprecated: merged to RouteExcludeAddress
-	Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
+	Inet6RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
 }
 
 type FwMark uint32
diff --git a/option/tun_platform.go b/option/tun_platform.go
index a0a54eed..b42f6894 100644
--- a/option/tun_platform.go
+++ b/option/tun_platform.go
@@ -1,5 +1,7 @@
 package option
 
+import "github.com/sagernet/sing/common/json/badoption"
+
 type TunPlatformOptions struct {
 	HTTPProxy *HTTPProxyOptions `json:"http_proxy,omitempty"`
 }
@@ -7,6 +9,6 @@ type TunPlatformOptions struct {
 type HTTPProxyOptions struct {
 	Enabled bool `json:"enabled,omitempty"`
 	ServerOptions
-	BypassDomain Listable[string] `json:"bypass_domain,omitempty"`
-	MatchDomain  Listable[string] `json:"match_domain,omitempty"`
+	BypassDomain badoption.Listable[string] `json:"bypass_domain,omitempty"`
+	MatchDomain  badoption.Listable[string] `json:"match_domain,omitempty"`
 }
diff --git a/option/types.go b/option/types.go
index bb648549..04e3f10e 100644
--- a/option/types.go
+++ b/option/types.go
@@ -1,10 +1,7 @@
 package option
 
 import (
-	"net/http"
-	"net/netip"
 	"strings"
-	"time"
 
 	"github.com/sagernet/sing-dns"
 	E "github.com/sagernet/sing/common/exceptions"
@@ -15,79 +12,6 @@ import (
 	mDNS "github.com/miekg/dns"
 )
 
-type ListenAddress netip.Addr
-
-func NewListenAddress(addr netip.Addr) *ListenAddress {
-	address := ListenAddress(addr)
-	return &address
-}
-
-func (a ListenAddress) MarshalJSON() ([]byte, error) {
-	addr := netip.Addr(a)
-	if !addr.IsValid() {
-		return nil, nil
-	}
-	return json.Marshal(addr.String())
-}
-
-func (a *ListenAddress) UnmarshalJSON(content []byte) error {
-	var value string
-	err := json.Unmarshal(content, &value)
-	if err != nil {
-		return err
-	}
-	addr, err := netip.ParseAddr(value)
-	if err != nil {
-		return err
-	}
-	*a = ListenAddress(addr)
-	return nil
-}
-
-func (a *ListenAddress) Build() netip.Addr {
-	if a == nil {
-		return netip.AddrFrom4([4]byte{127, 0, 0, 1})
-	}
-	return (netip.Addr)(*a)
-}
-
-type AddrPrefix netip.Prefix
-
-func (a AddrPrefix) MarshalJSON() ([]byte, error) {
-	prefix := netip.Prefix(a)
-	if prefix.Bits() == prefix.Addr().BitLen() {
-		return json.Marshal(prefix.Addr().String())
-	} else {
-		return json.Marshal(prefix.String())
-	}
-}
-
-func (a *AddrPrefix) UnmarshalJSON(content []byte) error {
-	var value string
-	err := json.Unmarshal(content, &value)
-	if err != nil {
-		return err
-	}
-	prefix, prefixErr := netip.ParsePrefix(value)
-	if prefixErr == nil {
-		*a = AddrPrefix(prefix)
-		return nil
-	}
-	addr, addrErr := netip.ParseAddr(value)
-	if addrErr == nil {
-		*a = AddrPrefix(netip.PrefixFrom(addr, addr.BitLen()))
-		return nil
-	}
-	return prefixErr
-}
-
-func (a *AddrPrefix) Build() netip.Prefix {
-	if a == nil {
-		return netip.Prefix{}
-	}
-	return netip.Prefix(*a)
-}
-
 type NetworkList string
 
 func (v *NetworkList) UnmarshalJSON(content []byte) error {
@@ -120,30 +44,6 @@ func (v NetworkList) Build() []string {
 	return strings.Split(string(v), "\n")
 }
 
-type Listable[T any] []T
-
-func (l Listable[T]) MarshalJSON() ([]byte, error) {
-	arrayList := []T(l)
-	if len(arrayList) == 1 {
-		return json.Marshal(arrayList[0])
-	}
-	return json.Marshal(arrayList)
-}
-
-func (l *Listable[T]) UnmarshalJSON(content []byte) error {
-	err := json.UnmarshalDisallowUnknownFields(content, (*[]T)(l))
-	if err == nil {
-		return nil
-	}
-	var singleItem T
-	newError := json.UnmarshalDisallowUnknownFields(content, &singleItem)
-	if newError != nil {
-		return E.Errors(err, newError)
-	}
-	*l = []T{singleItem}
-	return nil
-}
-
 type DomainStrategy dns.DomainStrategy
 
 func (s DomainStrategy) String() string {
@@ -206,26 +106,6 @@ func (s *DomainStrategy) UnmarshalJSON(bytes []byte) error {
 	return nil
 }
 
-type Duration time.Duration
-
-func (d Duration) MarshalJSON() ([]byte, error) {
-	return json.Marshal((time.Duration)(d).String())
-}
-
-func (d *Duration) UnmarshalJSON(bytes []byte) error {
-	var value string
-	err := json.Unmarshal(bytes, &value)
-	if err != nil {
-		return err
-	}
-	duration, err := ParseDuration(value)
-	if err != nil {
-		return err
-	}
-	*d = Duration(duration)
-	return nil
-}
-
 type DNSQueryType uint16
 
 func (t DNSQueryType) String() string {
@@ -270,15 +150,3 @@ func DNSQueryTypeToString(queryType uint16) string {
 	}
 	return F.ToString(queryType)
 }
-
-type HTTPHeader map[string]Listable[string]
-
-func (h HTTPHeader) Build() http.Header {
-	header := make(http.Header)
-	for name, values := range h {
-		for _, value := range values {
-			header.Add(name, value)
-		}
-	}
-	return header
-}
diff --git a/option/v2ray_transport.go b/option/v2ray_transport.go
index f87b175d..68c23858 100644
--- a/option/v2ray_transport.go
+++ b/option/v2ray_transport.go
@@ -5,6 +5,7 @@ import (
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/json"
 	"github.com/sagernet/sing/common/json/badjson"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 type _V2RayTransportOptions struct {
@@ -67,33 +68,33 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
 }
 
 type V2RayHTTPOptions struct {
-	Host        Listable[string] `json:"host,omitempty"`
-	Path        string           `json:"path,omitempty"`
-	Method      string           `json:"method,omitempty"`
-	Headers     HTTPHeader       `json:"headers,omitempty"`
-	IdleTimeout Duration         `json:"idle_timeout,omitempty"`
-	PingTimeout Duration         `json:"ping_timeout,omitempty"`
+	Host        badoption.Listable[string] `json:"host,omitempty"`
+	Path        string                     `json:"path,omitempty"`
+	Method      string                     `json:"method,omitempty"`
+	Headers     badoption.HTTPHeader       `json:"headers,omitempty"`
+	IdleTimeout badoption.Duration         `json:"idle_timeout,omitempty"`
+	PingTimeout badoption.Duration         `json:"ping_timeout,omitempty"`
 }
 
 type V2RayWebsocketOptions struct {
-	Path                string     `json:"path,omitempty"`
-	Headers             HTTPHeader `json:"headers,omitempty"`
-	MaxEarlyData        uint32     `json:"max_early_data,omitempty"`
-	EarlyDataHeaderName string     `json:"early_data_header_name,omitempty"`
+	Path                string               `json:"path,omitempty"`
+	Headers             badoption.HTTPHeader `json:"headers,omitempty"`
+	MaxEarlyData        uint32               `json:"max_early_data,omitempty"`
+	EarlyDataHeaderName string               `json:"early_data_header_name,omitempty"`
 }
 
 type V2RayQUICOptions struct{}
 
 type V2RayGRPCOptions struct {
-	ServiceName         string   `json:"service_name,omitempty"`
-	IdleTimeout         Duration `json:"idle_timeout,omitempty"`
-	PingTimeout         Duration `json:"ping_timeout,omitempty"`
-	PermitWithoutStream bool     `json:"permit_without_stream,omitempty"`
-	ForceLite           bool     `json:"-"` // for test
+	ServiceName         string             `json:"service_name,omitempty"`
+	IdleTimeout         badoption.Duration `json:"idle_timeout,omitempty"`
+	PingTimeout         badoption.Duration `json:"ping_timeout,omitempty"`
+	PermitWithoutStream bool               `json:"permit_without_stream,omitempty"`
+	ForceLite           bool               `json:"-"` // for test
 }
 
 type V2RayHTTPUpgradeOptions struct {
-	Host    string     `json:"host,omitempty"`
-	Path    string     `json:"path,omitempty"`
-	Headers HTTPHeader `json:"headers,omitempty"`
+	Host    string               `json:"host,omitempty"`
+	Path    string               `json:"path,omitempty"`
+	Headers badoption.HTTPHeader `json:"headers,omitempty"`
 }
diff --git a/option/wireguard.go b/option/wireguard.go
index 65bfad20..ebdf159f 100644
--- a/option/wireguard.go
+++ b/option/wireguard.go
@@ -1,15 +1,19 @@
 package option
 
-import "net/netip"
+import (
+	"net/netip"
+
+	"github.com/sagernet/sing/common/json/badoption"
+)
 
 type WireGuardOutboundOptions struct {
 	DialerOptions
-	SystemInterface bool                   `json:"system_interface,omitempty"`
-	GSO             bool                   `json:"gso,omitempty"`
-	InterfaceName   string                 `json:"interface_name,omitempty"`
-	LocalAddress    Listable[netip.Prefix] `json:"local_address"`
-	PrivateKey      string                 `json:"private_key"`
-	Peers           []WireGuardPeer        `json:"peers,omitempty"`
+	SystemInterface bool                             `json:"system_interface,omitempty"`
+	GSO             bool                             `json:"gso,omitempty"`
+	InterfaceName   string                           `json:"interface_name,omitempty"`
+	LocalAddress    badoption.Listable[netip.Prefix] `json:"local_address"`
+	PrivateKey      string                           `json:"private_key"`
+	Peers           []WireGuardPeer                  `json:"peers,omitempty"`
 	ServerOptions
 	PeerPublicKey string      `json:"peer_public_key"`
 	PreSharedKey  string      `json:"pre_shared_key,omitempty"`
@@ -21,8 +25,8 @@ type WireGuardOutboundOptions struct {
 
 type WireGuardPeer struct {
 	ServerOptions
-	PublicKey    string           `json:"public_key,omitempty"`
-	PreSharedKey string           `json:"pre_shared_key,omitempty"`
-	AllowedIPs   Listable[string] `json:"allowed_ips,omitempty"`
-	Reserved     []uint8          `json:"reserved,omitempty"`
+	PublicKey    string                     `json:"public_key,omitempty"`
+	PreSharedKey string                     `json:"pre_shared_key,omitempty"`
+	AllowedIPs   badoption.Listable[string] `json:"allowed_ips,omitempty"`
+	Reserved     []uint8                    `json:"reserved,omitempty"`
 }
diff --git a/protocol/tun/inbound.go b/protocol/tun/inbound.go
index 4be30d61..f2476223 100644
--- a/protocol/tun/inbound.go
+++ b/protocol/tun/inbound.go
@@ -21,6 +21,7 @@ import (
 	"github.com/sagernet/sing-tun"
 	"github.com/sagernet/sing/common"
 	E "github.com/sagernet/sing/common/exceptions"
+	"github.com/sagernet/sing/common/json/badoption"
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
 	"github.com/sagernet/sing/common/ranges"
@@ -257,7 +258,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
 	return inbound, nil
 }
 
-func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] {
+func uidToRange(uidList badoption.Listable[uint32]) []ranges.Range[uint32] {
 	return common.Map(uidList, func(uid uint32) ranges.Range[uint32] {
 		return ranges.NewSingle(uid)
 	})
diff --git a/route/router.go b/route/router.go
index ce480749..9a57328a 100644
--- a/route/router.go
+++ b/route/router.go
@@ -239,9 +239,9 @@ func NewRouter(
 			}
 			var clientSubnet netip.Prefix
 			if server.ClientSubnet != nil {
-				clientSubnet = server.ClientSubnet.Build()
+				clientSubnet = netip.Prefix(common.PtrValueOrDefault(server.ClientSubnet))
 			} else if dnsOptions.ClientSubnet != nil {
-				clientSubnet = dnsOptions.ClientSubnet.Build()
+				clientSubnet = netip.Prefix(common.PtrValueOrDefault(dnsOptions.ClientSubnet))
 			}
 			if serverProtocol == "" {
 				serverProtocol = "transport"
diff --git a/route/rule/rule_action.go b/route/rule/rule_action.go
index f85940d6..ad1743c5 100644
--- a/route/rule/rule_action.go
+++ b/route/rule/rule_action.go
@@ -84,13 +84,13 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
 			Server:       action.RouteOptions.Server,
 			DisableCache: action.RouteOptions.DisableCache,
 			RewriteTTL:   action.RouteOptions.RewriteTTL,
-			ClientSubnet: action.RouteOptions.ClientSubnet.Build(),
+			ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
 		}
 	case C.RuleActionTypeRouteOptions:
 		return &RuleActionDNSRouteOptions{
 			DisableCache: action.RouteOptionsOptions.DisableCache,
 			RewriteTTL:   action.RouteOptionsOptions.RewriteTTL,
-			ClientSubnet: action.RouteOptionsOptions.ClientSubnet.Build(),
+			ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
 		}
 	case C.RuleActionTypeReject:
 		return &RuleActionReject{
diff --git a/test/brutal_test.go b/test/brutal_test.go
index ce1d2c2a..cea0ad07 100644
--- a/test/brutal_test.go
+++ b/test/brutal_test.go
@@ -7,6 +7,8 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/gofrs/uuid/v5"
 )
@@ -15,13 +17,13 @@ func TestBrutalShadowsocks(t *testing.T) {
 	method := shadowaead_2022.List[0]
 	password := mkBase64(t, 16)
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -30,7 +32,7 @@ func TestBrutalShadowsocks(t *testing.T) {
 				Type: C.TypeShadowsocks,
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Method:   method,
@@ -100,13 +102,13 @@ func TestBrutalTrojan(t *testing.T) {
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	password := mkBase64(t, 16)
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -115,7 +117,7 @@ func TestBrutalTrojan(t *testing.T) {
 				Type: C.TypeTrojan,
 				TrojanOptions: option.TrojanInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.TrojanUser{{Password: password}},
@@ -197,13 +199,13 @@ func TestBrutalTrojan(t *testing.T) {
 func TestBrutalVMess(t *testing.T) {
 	user, _ := uuid.NewV4()
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -212,7 +214,7 @@ func TestBrutalVMess(t *testing.T) {
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{{UUID: user.String()}},
@@ -279,13 +281,13 @@ func TestBrutalVMess(t *testing.T) {
 func TestBrutalVLESS(t *testing.T) {
 	user, _ := uuid.NewV4()
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -294,7 +296,7 @@ func TestBrutalVLESS(t *testing.T) {
 				Type: C.TypeVLESS,
 				VLESSOptions: option.VLESSInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VLESSUser{{UUID: user.String()}},
diff --git a/test/direct_test.go b/test/direct_test.go
index c4fd8c5e..7e7740e5 100644
--- a/test/direct_test.go
+++ b/test/direct_test.go
@@ -6,18 +6,20 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 // Since this is a feature one-off added by outsiders, I won't address these anymore.
 func _TestProxyProtocol(t *testing.T) {
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -26,7 +28,7 @@ func _TestProxyProtocol(t *testing.T) {
 				Type: C.TypeDirect,
 				DirectOptions: option.DirectInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:        option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:        common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort:    serverPort,
 						ProxyProtocol: true,
 					},
diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go
index c82b0d29..032b5f59 100644
--- a/test/domain_inbound_test.go
+++ b/test/domain_inbound_test.go
@@ -7,6 +7,8 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-dns"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/gofrs/uuid/v5"
 )
@@ -14,13 +16,13 @@ import (
 func TestTUICDomainUDP(t *testing.T) {
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -29,7 +31,7 @@ func TestTUICDomainUDP(t *testing.T) {
 				Type: C.TypeTUIC,
 				TUICOptions: option.TUICInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 						InboundOptions: option.InboundOptions{
 							DomainStrategy: option.DomainStrategy(dns.DomainStrategyUseIPv6),
diff --git a/test/ech_test.go b/test/ech_test.go
index eeac1acb..5dac5a0a 100644
--- a/test/ech_test.go
+++ b/test/ech_test.go
@@ -8,6 +8,7 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/gofrs/uuid/v5"
 )
@@ -16,13 +17,13 @@ func TestECH(t *testing.T) {
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false))
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -31,7 +32,7 @@ func TestECH(t *testing.T) {
 				Type: C.TypeTrojan,
 				TrojanOptions: option.TrojanInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.TrojanUser{
@@ -109,13 +110,13 @@ func TestECHQUIC(t *testing.T) {
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false))
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -124,7 +125,7 @@ func TestECHQUIC(t *testing.T) {
 				Type: C.TypeTUIC,
 				TUICOptions: option.TUICInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.TUICUser{{
@@ -199,13 +200,13 @@ func TestECHHysteria2(t *testing.T) {
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false))
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -214,7 +215,7 @@ func TestECHHysteria2(t *testing.T) {
 				Type: C.TypeHysteria2,
 				Hysteria2Options: option.Hysteria2InboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.Hysteria2User{{
diff --git a/test/go.mod b/test/go.mod
index f9eefa16..72d593ff 100644
--- a/test/go.mod
+++ b/test/go.mod
@@ -12,10 +12,10 @@ require (
 	github.com/docker/docker v27.3.1+incompatible
 	github.com/docker/go-connections v0.5.0
 	github.com/gofrs/uuid/v5 v5.3.0
-	github.com/sagernet/quic-go v0.48.0-beta.1
-	github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb
-	github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce
-	github.com/sagernet/sing-quic v0.3.0-rc.1
+	github.com/sagernet/quic-go v0.48.1-beta.1
+	github.com/sagernet/sing v0.5.1-0.20241107131656-6e1285b5d82f
+	github.com/sagernet/sing-dns v0.3.1-0.20241105104342-1914f319ddab
+	github.com/sagernet/sing-quic v0.3.0-rc.2
 	github.com/sagernet/sing-shadowsocks v0.2.7
 	github.com/sagernet/sing-shadowsocks2 v0.2.0
 	github.com/spyzhov/ajson v0.9.4
@@ -25,13 +25,11 @@ require (
 )
 
 require (
-	berty.tech/go-libtor v1.0.385 // indirect
 	github.com/Microsoft/go-winio v0.6.1 // indirect
 	github.com/andybalholm/brotli v1.0.6 // indirect
 	github.com/caddyserver/certmagic v0.20.0 // indirect
 	github.com/cloudflare/circl v1.3.7 // indirect
 	github.com/containerd/log v0.1.0 // indirect
-	github.com/cretz/bine v0.2.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/distribution/reference v0.5.0 // indirect
 	github.com/docker/go-units v0.5.0 // indirect
@@ -42,8 +40,6 @@ require (
 	github.com/go-logr/stdr v1.2.2 // indirect
 	github.com/go-ole/go-ole v1.3.0 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
-	github.com/gobwas/httphead v0.1.0 // indirect
-	github.com/gobwas/pool v0.2.1 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/google/btree v1.1.3 // indirect
 	github.com/google/go-cmp v0.6.0 // indirect
@@ -65,7 +61,6 @@ require (
 	github.com/moby/term v0.5.0 // indirect
 	github.com/morikuni/aec v1.0.0 // 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
 	github.com/oschwald/maxminddb-golang v1.12.0 // indirect
@@ -81,13 +76,10 @@ require (
 	github.com/sagernet/nftables v0.3.0-beta.4 // indirect
 	github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
 	github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec // indirect
-	github.com/sagernet/sing-shadowtls v0.1.4 // indirect
-	github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d // indirect
+	github.com/sagernet/sing-tun v0.4.0-rc.5.0.20241107062822-5a91eb99c90f // indirect
 	github.com/sagernet/sing-vmess v0.1.12 // indirect
 	github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
 	github.com/sagernet/utls v1.6.7 // indirect
-	github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect
-	github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
 	github.com/vishvananda/netns v0.0.4 // indirect
 	github.com/zeebo/blake3 v0.2.3 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
diff --git a/test/go.sum b/test/go.sum
index e851ebd3..7cb24e5f 100644
--- a/test/go.sum
+++ b/test/go.sum
@@ -1,5 +1,3 @@
-berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
-berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
 github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
 github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
@@ -14,9 +12,6 @@ github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vc
 github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
 github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
 github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
-github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
-github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
-github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -43,10 +38,6 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
 github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
-github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
-github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
-github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
-github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
 github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -107,8 +98,6 @@ 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/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
-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=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
@@ -135,45 +124,37 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
 github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
 github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
-github.com/sagernet/quic-go v0.48.0-beta.1 h1:86hQZrmuoARI3BpDRkQaP0iAVpywA4YsRrzJPYuPKWg=
-github.com/sagernet/quic-go v0.48.0-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
+github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AEpT94Z0vwo=
+github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
 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.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
-github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb h1:3IhGq2UmcbQfAcuqyE8RYKFapqEEa3eItS/MrZr+5l8=
-github.com/sagernet/sing v0.5.0-rc.4.0.20241022031908-cd17884118cb/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
-github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce h1:OfpxE5qnXMyU/9LtNgX4M7bGP11lJx4s+KZ3Sijb0HE=
-github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241021154031-a59e0fbba3ce/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M=
+github.com/sagernet/sing v0.5.1-0.20241107131656-6e1285b5d82f h1:A6+OeV5P1mok0eEEbLh4PidymZ6VZnTZ2uHwfapXgdU=
+github.com/sagernet/sing v0.5.1-0.20241107131656-6e1285b5d82f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
+github.com/sagernet/sing-dns v0.3.1-0.20241105104342-1914f319ddab h1:djP4EY/KM5T62xscormLflVi7eDlHv6p7md1FHMSArE=
+github.com/sagernet/sing-dns v0.3.1-0.20241105104342-1914f319ddab/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M=
 github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec h1:6Fd/VsEsw9qIjaGi1IBTZSb4b4v5JYtNcoiBtGsQC48=
 github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec/go.mod h1:RSwqqHwbtTOX3vs6ms8vMtBGH/0ZNyLm/uwt6TlmR84=
-github.com/sagernet/sing-quic v0.3.0-rc.1 h1:SlzL1yfEAKJyRduub8vzOVtbyTLAX7RZEEBZxO5utts=
-github.com/sagernet/sing-quic v0.3.0-rc.1/go.mod h1:uX+aUHA0fgIN6U3WViseDpSdTQriViZ7qz0Wbsf1mNQ=
+github.com/sagernet/sing-quic v0.3.0-rc.2 h1:7vcC4bdS1GBJzHZhfmJiH0CfzQ4mYLUW51Z2RNHcGwc=
+github.com/sagernet/sing-quic v0.3.0-rc.2/go.mod h1:3UOq51WVqzra7eCgod7t4hqnTaOiZzFUci9avMrtOqs=
 github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
 github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
 github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
 github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
-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.4.0-rc.4.0.20241021153919-9ae45181180d h1:zWcIQM3eAKJGzy7zhqkO7zm7ZI890OdR4vSwx2mevS0=
-github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241021153919-9ae45181180d/go.mod h1:Xhv+Mz2nE7HZTwResni8EtTa7AMJz/S6uQLT5lV23M8=
+github.com/sagernet/sing-tun v0.4.0-rc.5.0.20241107062822-5a91eb99c90f h1:gQwTgN/E4oHe3VlseD3/RhPs866cWcTsPG4dP6a8f8o=
+github.com/sagernet/sing-tun v0.4.0-rc.5.0.20241107062822-5a91eb99c90f/go.mod h1:Ehs5mZ3T8tTgV3H1Tx4Va5ixvyKjTAUPJ3G11dq7B/g=
 github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg=
 github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I=
 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/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
 github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
-github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs=
-github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc=
-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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/spyzhov/ajson v0.9.4 h1:MVibcTCgO7DY4IlskdqIlCmDOsUOZ9P7oKj8ifdcf84=
 github.com/spyzhov/ajson v0.9.4/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
@@ -211,10 +192,8 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
 golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
@@ -227,8 +206,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
 golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -237,23 +214,15 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
 golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 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-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.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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
 golang.org/x/sys v0.26.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.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
-golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
 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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
 golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
diff --git a/test/http_test.go b/test/http_test.go
index 7e724005..dbf8b3bc 100644
--- a/test/http_test.go
+++ b/test/http_test.go
@@ -6,17 +6,19 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func TestHTTPSelf(t *testing.T) {
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -25,7 +27,7 @@ func TestHTTPSelf(t *testing.T) {
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 				},
diff --git a/test/hysteria2_test.go b/test/hysteria2_test.go
index 665da552..1e031f1c 100644
--- a/test/hysteria2_test.go
+++ b/test/hysteria2_test.go
@@ -7,6 +7,8 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-quic/hysteria2"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func TestHysteria2Self(t *testing.T) {
@@ -28,13 +30,13 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) {
 		}
 	}
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -43,7 +45,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) {
 				Type: C.TypeHysteria2,
 				Hysteria2Options: option.Hysteria2InboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					UpMbps:   100,
@@ -115,12 +117,12 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) {
 func TestHysteria2Inbound(t *testing.T) {
 	caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeHysteria2,
 				Hysteria2Options: option.Hysteria2InboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Obfs: &option.Hysteria2Obfs{
@@ -167,12 +169,12 @@ func TestHysteria2Outbound(t *testing.T) {
 		},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
diff --git a/test/hysteria_test.go b/test/hysteria_test.go
index dce00390..e9467e18 100644
--- a/test/hysteria_test.go
+++ b/test/hysteria_test.go
@@ -6,18 +6,20 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func TestHysteriaSelf(t *testing.T) {
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -26,7 +28,7 @@ func TestHysteriaSelf(t *testing.T) {
 				Type: C.TypeHysteria,
 				HysteriaOptions: option.HysteriaInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					UpMbps:   100,
@@ -98,12 +100,12 @@ func TestHysteriaSelf(t *testing.T) {
 func TestHysteriaInbound(t *testing.T) {
 	caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeHysteria,
 				HysteriaOptions: option.HysteriaInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					UpMbps:   100,
@@ -149,12 +151,12 @@ func TestHysteriaOutbound(t *testing.T) {
 		},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go
index c26c81a7..4ae82bc9 100644
--- a/test/inbound_detour_test.go
+++ b/test/inbound_detour_test.go
@@ -7,19 +7,21 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func TestChainedInbound(t *testing.T) {
 	method := shadowaead_2022.List[0]
 	password := mkBase64(t, 16)
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -28,9 +30,11 @@ func TestChainedInbound(t *testing.T) {
 				Type: C.TypeShadowsocks,
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
-						Detour:     "detour",
+						InboundOptions: option.InboundOptions{
+							Detour: "detour",
+						},
 					},
 					Method:   method,
 					Password: password,
@@ -41,7 +45,7 @@ func TestChainedInbound(t *testing.T) {
 				Tag:  "detour",
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: otherPort,
 					},
 					Method:   method,
diff --git a/test/mux_cool_test.go b/test/mux_cool_test.go
index e72f244f..2dceeb54 100644
--- a/test/mux_cool_test.go
+++ b/test/mux_cool_test.go
@@ -7,6 +7,8 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/spyzhov/ajson"
 	"github.com/stretchr/testify/require"
@@ -37,12 +39,12 @@ func TestMuxCoolServer(t *testing.T) {
 	})
 
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
@@ -81,12 +83,12 @@ func TestMuxCoolClient(t *testing.T) {
 	})
 
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -112,13 +114,13 @@ func TestMuxCoolClient(t *testing.T) {
 func TestMuxCoolSelf(t *testing.T) {
 	user := newUUID()
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -127,7 +129,7 @@ func TestMuxCoolSelf(t *testing.T) {
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
diff --git a/test/mux_test.go b/test/mux_test.go
index 335def2e..27c6b914 100644
--- a/test/mux_test.go
+++ b/test/mux_test.go
@@ -7,6 +7,8 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/gofrs/uuid/v5"
 )
@@ -55,13 +57,13 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) {
 	method := shadowaead_2022.List[0]
 	password := mkBase64(t, 16)
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -70,7 +72,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) {
 				Type: C.TypeShadowsocks,
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Method:   method,
@@ -125,13 +127,13 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) {
 func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) {
 	user, _ := uuid.NewV4()
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -140,7 +142,7 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) {
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
diff --git a/test/naive_test.go b/test/naive_test.go
index fe3e7dce..d3cb395e 100644
--- a/test/naive_test.go
+++ b/test/naive_test.go
@@ -6,19 +6,21 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/auth"
+	"github.com/sagernet/sing/common/json/badoption"
 	"github.com/sagernet/sing/common/network"
 )
 
 func TestNaiveInboundWithNginx(t *testing.T) {
 	caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeNaive,
 				NaiveOptions: option.NaiveInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: otherPort,
 					},
 					Users: []auth.User{
@@ -59,12 +61,12 @@ func TestNaiveInboundWithNginx(t *testing.T) {
 func TestNaiveInbound(t *testing.T) {
 	caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeNaive,
 				NaiveOptions: option.NaiveInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []auth.User{
@@ -103,12 +105,12 @@ func TestNaiveInbound(t *testing.T) {
 func TestNaiveHTTP3Inbound(t *testing.T) {
 	caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeNaive,
 				NaiveOptions: option.NaiveInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []auth.User{
diff --git a/test/shadowsocks_legacy_test.go b/test/shadowsocks_legacy_test.go
index ae6f38e4..bbdbbde8 100644
--- a/test/shadowsocks_legacy_test.go
+++ b/test/shadowsocks_legacy_test.go
@@ -7,7 +7,9 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-shadowsocks2/shadowstream"
+	"github.com/sagernet/sing/common"
 	F "github.com/sagernet/sing/common/format"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func TestShadowsocksLegacy(t *testing.T) {
@@ -24,12 +26,12 @@ func testShadowsocksLegacy(t *testing.T, method string) {
 		},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go
index 0f7af765..c83734e4 100644
--- a/test/shadowsocks_test.go
+++ b/test/shadowsocks_test.go
@@ -9,7 +9,9 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
+	"github.com/sagernet/sing/common"
 	F "github.com/sagernet/sing/common/format"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/stretchr/testify/require"
 )
@@ -99,12 +101,12 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass
 		Cmd:        []string{"-s", F.ToString("127.0.0.1:", serverPort), "-b", F.ToString("0.0.0.0:", clientPort), "-m", method, "-k", password, "-U"},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeShadowsocks,
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Method:   method,
@@ -124,12 +126,12 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas
 		Cmd:        []string{"-s", F.ToString("0.0.0.0:", serverPort), "-m", method, "-k", password, "-U"},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -154,13 +156,13 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas
 
 func testShadowsocksSelf(t *testing.T, method string, password string) {
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -169,7 +171,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) {
 				Type: C.TypeShadowsocks,
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Method:   method,
@@ -221,13 +223,13 @@ func TestShadowsocksUoT(t *testing.T) {
 	method := shadowaead_2022.List[0]
 	password := mkBase64(t, 16)
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -236,7 +238,7 @@ func TestShadowsocksUoT(t *testing.T) {
 				Type: C.TypeShadowsocks,
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Method:   method,
@@ -289,13 +291,13 @@ func TestShadowsocksUoT(t *testing.T) {
 
 func testShadowsocks2022EIH(t *testing.T, method string, password string) {
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -304,7 +306,7 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) {
 				Type: C.TypeShadowsocks,
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Method:   method,
diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go
index 71e8d9fa..026c6f55 100644
--- a/test/shadowtls_test.go
+++ b/test/shadowtls_test.go
@@ -10,7 +10,9 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
+	"github.com/sagernet/sing/common"
 	F "github.com/sagernet/sing/common/format"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/stretchr/testify/require"
 )
@@ -37,12 +39,12 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool)
 	method := shadowaead_2022.List[0]
 	ssPassword := mkBase64(t, 16)
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -52,9 +54,12 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool)
 				Tag:  "in",
 				ShadowTLSOptions: option.ShadowTLSInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
-						Detour:     "detour",
+
+						InboundOptions: option.InboundOptions{
+							Detour: "detour",
+						},
 					},
 					Handshake: option.ShadowTLSHandshakeOptions{
 						ServerOptions: option.ServerOptions{
@@ -72,7 +77,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool)
 				Tag:  "detour",
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: otherPort,
 					},
 					Method:   method,
@@ -142,12 +147,12 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool)
 
 func TestShadowTLSFallback(t *testing.T) {
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeShadowTLS,
 				ShadowTLSOptions: option.ShadowTLSInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Handshake: option.ShadowTLSHandshakeOptions{
@@ -189,13 +194,13 @@ func TestShadowTLSInbound(t *testing.T) {
 		Cmd:        []string{"--v3", "--threads", "1", "client", "--listen", "0.0.0.0:" + F.ToString(otherPort), "--server", "127.0.0.1:" + F.ToString(serverPort), "--sni", "google.com", "--password", password},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -204,9 +209,11 @@ func TestShadowTLSInbound(t *testing.T) {
 				Type: C.TypeShadowTLS,
 				ShadowTLSOptions: option.ShadowTLSInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
-						Detour:     "detour",
+						InboundOptions: option.InboundOptions{
+							Detour: "detour",
+						},
 					},
 					Handshake: option.ShadowTLSHandshakeOptions{
 						ServerOptions: option.ServerOptions{
@@ -225,7 +232,7 @@ func TestShadowTLSInbound(t *testing.T) {
 				Tag:  "detour",
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen: option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen: common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 					},
 					Method:   method,
 					Password: password,
@@ -283,12 +290,12 @@ func TestShadowTLSOutbound(t *testing.T) {
 		Env:        []string{"RUST_LOG=trace"},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -298,7 +305,7 @@ func TestShadowTLSOutbound(t *testing.T) {
 				Tag:  "detour",
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: otherPort,
 					},
 					Method:   method,
diff --git a/test/ss_plugin_test.go b/test/ss_plugin_test.go
index 3f837b4e..b74c1356 100644
--- a/test/ss_plugin_test.go
+++ b/test/ss_plugin_test.go
@@ -6,6 +6,8 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func TestShadowsocksObfs(t *testing.T) {
@@ -33,12 +35,12 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string)
 		},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
diff --git a/test/tfo_test.go b/test/tfo_test.go
index 458a936d..2587bc2c 100644
--- a/test/tfo_test.go
+++ b/test/tfo_test.go
@@ -7,19 +7,21 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-shadowsocks/shadowaead"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func TestTCPSlowOpen(t *testing.T) {
 	method := shadowaead.List[0]
 	password := mkBase64(t, 16)
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -28,7 +30,7 @@ func TestTCPSlowOpen(t *testing.T) {
 				Type: C.TypeShadowsocks,
 				ShadowsocksOptions: option.ShadowsocksInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:      option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:      common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort:  serverPort,
 						TCPFastOpen: true,
 					},
diff --git a/test/tls_test.go b/test/tls_test.go
index b42d924f..fb0c7a1b 100644
--- a/test/tls_test.go
+++ b/test/tls_test.go
@@ -6,18 +6,20 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func TestUTLS(t *testing.T) {
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -26,7 +28,7 @@ func TestUTLS(t *testing.T) {
 				Type: C.TypeTrojan,
 				TrojanOptions: option.TrojanInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.TrojanUser{
diff --git a/test/trojan_test.go b/test/trojan_test.go
index 1a206c66..08e3b4d8 100644
--- a/test/trojan_test.go
+++ b/test/trojan_test.go
@@ -6,6 +6,8 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func TestTrojanOutbound(t *testing.T) {
@@ -20,12 +22,12 @@ func TestTrojanOutbound(t *testing.T) {
 		},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -57,13 +59,13 @@ func TestTrojanOutbound(t *testing.T) {
 func TestTrojanSelf(t *testing.T) {
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -72,7 +74,7 @@ func TestTrojanSelf(t *testing.T) {
 				Type: C.TypeTrojan,
 				TrojanOptions: option.TrojanInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.TrojanUser{
@@ -140,13 +142,13 @@ func TestTrojanSelf(t *testing.T) {
 
 func TestTrojanPlainSelf(t *testing.T) {
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -155,7 +157,7 @@ func TestTrojanPlainSelf(t *testing.T) {
 				Type: C.TypeTrojan,
 				TrojanOptions: option.TrojanInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.TrojanUser{
diff --git a/test/tuic_test.go b/test/tuic_test.go
index 41fb7599..0c5f277d 100644
--- a/test/tuic_test.go
+++ b/test/tuic_test.go
@@ -6,6 +6,8 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/gofrs/uuid/v5"
 )
@@ -29,13 +31,13 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) {
 		udpRelayMode = "quic"
 	}
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -44,7 +46,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) {
 				Type: C.TypeTUIC,
 				TUICOptions: option.TUICInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.TUICUser{{
@@ -113,12 +115,12 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) {
 func TestTUICInbound(t *testing.T) {
 	caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeTUIC,
 				TUICOptions: option.TUICInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.TUICUser{{
@@ -160,12 +162,12 @@ func TestTUICOutbound(t *testing.T) {
 		},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
diff --git a/test/v2ray_api_test.go b/test/v2ray_api_test.go
index cd7ae2c4..72e97b3f 100644
--- a/test/v2ray_api_test.go
+++ b/test/v2ray_api_test.go
@@ -8,19 +8,21 @@ import (
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/experimental/v2rayapi"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/stretchr/testify/require"
 )
 
 func TestV2RayAPI(t *testing.T) {
 	i := startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
diff --git a/test/v2ray_grpc_test.go b/test/v2ray_grpc_test.go
index 5cf87543..30cb4bd5 100644
--- a/test/v2ray_grpc_test.go
+++ b/test/v2ray_grpc_test.go
@@ -7,6 +7,8 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/gofrs/uuid/v5"
 	"github.com/spyzhov/ajson"
@@ -27,12 +29,12 @@ func testV2RayGRPCInbound(t *testing.T, forceLite bool) {
 	require.NoError(t, err)
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
@@ -126,13 +128,13 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) {
 		},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
diff --git a/test/v2ray_transport_test.go b/test/v2ray_transport_test.go
index 27074e78..bc1a3157 100644
--- a/test/v2ray_transport_test.go
+++ b/test/v2ray_transport_test.go
@@ -6,6 +6,8 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/gofrs/uuid/v5"
 	"github.com/stretchr/testify/require"
@@ -44,13 +46,13 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
 	require.NoError(t, err)
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -59,7 +61,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
@@ -133,13 +135,13 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
 	require.NoError(t, err)
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -148,7 +150,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
 				Type: C.TypeTrojan,
 				TrojanOptions: option.TrojanInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.TrojanUser{
@@ -224,13 +226,13 @@ func TestVMessQUICSelf(t *testing.T) {
 	require.NoError(t, err)
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -239,7 +241,7 @@ func TestVMessQUICSelf(t *testing.T) {
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
@@ -312,13 +314,13 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO
 	user, err := uuid.DefaultGenerator.NewV4()
 	require.NoError(t, err)
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -327,7 +329,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
diff --git a/test/v2ray_ws_test.go b/test/v2ray_ws_test.go
index de8d4bdc..35f7a7c5 100644
--- a/test/v2ray_ws_test.go
+++ b/test/v2ray_ws_test.go
@@ -7,6 +7,8 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/gofrs/uuid/v5"
 	"github.com/spyzhov/ajson"
@@ -61,12 +63,12 @@ func testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeade
 	require.NoError(t, err)
 	_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
@@ -158,13 +160,13 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead
 		},
 	})
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
diff --git a/test/vmess_test.go b/test/vmess_test.go
index 9f81d9a0..e0fd6549 100644
--- a/test/vmess_test.go
+++ b/test/vmess_test.go
@@ -7,6 +7,8 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 
 	"github.com/gofrs/uuid/v5"
 	"github.com/spyzhov/ajson"
@@ -181,12 +183,12 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authe
 	})
 
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
@@ -229,12 +231,12 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo
 	})
 
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -263,13 +265,13 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo
 func testVMessSelf(t *testing.T, security string, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) {
 	user := newUUID()
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				Tag:  "mixed-in",
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
@@ -278,7 +280,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo
 				Type: C.TypeVMess,
 				VMessOptions: option.VMessInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: serverPort,
 					},
 					Users: []option.VMessUser{
diff --git a/test/wireguard_test.go b/test/wireguard_test.go
index 70c0e5a5..a30d520e 100644
--- a/test/wireguard_test.go
+++ b/test/wireguard_test.go
@@ -7,6 +7,8 @@ import (
 
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/json/badoption"
 )
 
 func _TestWireGuard(t *testing.T) {
@@ -21,12 +23,12 @@ func _TestWireGuard(t *testing.T) {
 	})
 	time.Sleep(5 * time.Second)
 	startInstance(t, option.Options{
-		Inbounds: []option.LegacyInbound{
+		LegacyInbounds: []option.LegacyInbound{
 			{
 				Type: C.TypeMixed,
 				MixedOptions: option.HTTPMixedInboundOptions{
 					ListenOptions: option.ListenOptions{
-						Listen:     option.NewListenAddress(netip.IPv4Unspecified()),
+						Listen:     common.Ptr(badoption.Addr(netip.IPv4Unspecified())),
 						ListenPort: clientPort,
 					},
 				},
diff --git a/transport/sip003/v2ray.go b/transport/sip003/v2ray.go
index c142180b..d7b752f6 100644
--- a/transport/sip003/v2ray.go
+++ b/transport/sip003/v2ray.go
@@ -12,6 +12,7 @@ import (
 	"github.com/sagernet/sing-box/transport/v2ray"
 	"github.com/sagernet/sing-vmess"
 	E "github.com/sagernet/sing/common/exceptions"
+	"github.com/sagernet/sing/common/json/badoption"
 	M "github.com/sagernet/sing/common/metadata"
 	N "github.com/sagernet/sing/common/network"
 )
@@ -67,7 +68,7 @@ func newV2RayPlugin(ctx context.Context, pluginOpts Args, router adapter.Router,
 		transportOptions = option.V2RayTransportOptions{
 			Type: C.V2RayTransportTypeWebsocket,
 			WebsocketOptions: option.V2RayWebsocketOptions{
-				Headers: map[string]option.Listable[string]{
+				Headers: map[string]badoption.Listable[string]{
 					"Host": []string{host},
 				},
 				Path: path,