Migrate bad options to library

This commit is contained in:
世界 2024-11-18 18:55:34 +08:00
parent 08dcbf3a8e
commit 0cc88fd0c8
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
31 changed files with 356 additions and 671 deletions

View file

@ -3,6 +3,7 @@ package dialer
import ( import (
"context" "context"
"net" "net"
"net/netip"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -102,7 +103,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
udpAddr4 string udpAddr4 string
) )
if options.Inet4BindAddress != nil { if options.Inet4BindAddress != nil {
bindAddr := options.Inet4BindAddress.Build() bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified())
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String() udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
@ -113,7 +114,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
udpAddr6 string udpAddr6 string
) )
if options.Inet6BindAddress != nil { if options.Inet6BindAddress != nil {
bindAddr := options.Inet6BindAddress.Build() bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified())
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String() udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()

View file

@ -3,6 +3,7 @@ package listener
import ( import (
"context" "context"
"net" "net"
"net/netip"
"sync/atomic" "sync/atomic"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -92,7 +93,7 @@ func (l *Listener) Start() error {
if l.setSystemProxy { if l.setSystemProxy {
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
var listenAddrString string var listenAddrString string
listenAddr := l.listenOptions.Listen.Build() listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified())
if listenAddr.IsUnspecified() { if listenAddr.IsUnspecified() {
listenAddrString = "127.0.0.1" listenAddrString = "127.0.0.1"
} else { } else {

View file

@ -2,6 +2,7 @@ package listener
import ( import (
"net" "net"
"net/netip"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
@ -16,7 +17,7 @@ import (
func (l *Listener) ListenTCP() (net.Listener, error) { func (l *Listener) ListenTCP() (net.Listener, error) {
var err 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 tcpListener net.Listener
var listenConfig net.ListenConfig var listenConfig net.ListenConfig
if l.listenOptions.TCPKeepAlive >= 0 { if l.listenOptions.TCPKeepAlive >= 0 {

View file

@ -2,6 +2,7 @@ package listener
import ( import (
"net" "net"
"net/netip"
"os" "os"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
@ -12,7 +13,7 @@ import (
) )
func (l *Listener) ListenUDP() (net.PacketConn, error) { 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 lc net.ListenConfig
var udpFragment bool var udpFragment bool
if l.listenOptions.UDPFragment != nil { if l.listenOptions.UDPFragment != nil {

View file

@ -2,6 +2,7 @@ package deprecated
import ( import (
"context" "context"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
) )

View file

@ -1,6 +1,10 @@
package option package option
import "net/netip" import (
"net/netip"
"github.com/sagernet/sing/common/json/badoption"
)
type DNSOptions struct { type DNSOptions struct {
Servers []DNSServerOptions `json:"servers,omitempty"` Servers []DNSServerOptions `json:"servers,omitempty"`
@ -16,10 +20,10 @@ type DNSServerOptions struct {
Address string `json:"address"` Address string `json:"address"`
AddressResolver string `json:"address_resolver,omitempty"` AddressResolver string `json:"address_resolver,omitempty"`
AddressStrategy DomainStrategy `json:"address_strategy,omitempty"` AddressStrategy DomainStrategy `json:"address_strategy,omitempty"`
AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"` AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"`
Strategy DomainStrategy `json:"strategy,omitempty"` Strategy DomainStrategy `json:"strategy,omitempty"`
Detour string `json:"detour,omitempty"` Detour string `json:"detour,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
} }
type DNSClientOptions struct { type DNSClientOptions struct {
@ -27,7 +31,7 @@ type DNSClientOptions struct {
DisableCache bool `json:"disable_cache,omitempty"` DisableCache bool `json:"disable_cache,omitempty"`
DisableExpire bool `json:"disable_expire,omitempty"` DisableExpire bool `json:"disable_expire,omitempty"`
IndependentCache bool `json:"independent_cache,omitempty"` IndependentCache bool `json:"independent_cache,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
} }
type DNSFakeIPOptions struct { type DNSFakeIPOptions struct {

View file

@ -1,5 +1,7 @@
package option package option
import "github.com/sagernet/sing/common/json/badoption"
type ExperimentalOptions struct { type ExperimentalOptions struct {
CacheFile *CacheFileOptions `json:"cache_file,omitempty"` CacheFile *CacheFileOptions `json:"cache_file,omitempty"`
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"` ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
@ -13,7 +15,7 @@ type CacheFileOptions struct {
CacheID string `json:"cache_id,omitempty"` CacheID string `json:"cache_id,omitempty"`
StoreFakeIP bool `json:"store_fakeip,omitempty"` StoreFakeIP bool `json:"store_fakeip,omitempty"`
StoreRDRC bool `json:"store_rdrc,omitempty"` StoreRDRC bool `json:"store_rdrc,omitempty"`
RDRCTimeout Duration `json:"rdrc_timeout,omitempty"` RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"`
} }
type ClashAPIOptions struct { type ClashAPIOptions struct {
@ -24,7 +26,7 @@ type ClashAPIOptions struct {
Secret string `json:"secret,omitempty"` Secret string `json:"secret,omitempty"`
DefaultMode string `json:"default_mode,omitempty"` DefaultMode string `json:"default_mode,omitempty"`
ModeList []string `json:"-"` ModeList []string `json:"-"`
AccessControlAllowOrigin Listable[string] `json:"access_control_allow_origin,omitempty"` AccessControlAllowOrigin badoption.Listable[string] `json:"access_control_allow_origin,omitempty"`
AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"` AccessControlAllowPrivateNetwork bool `json:"access_control_allow_private_network,omitempty"`
// Deprecated: migrated to global cache file // Deprecated: migrated to global cache file

View file

@ -1,5 +1,7 @@
package option package option
import "github.com/sagernet/sing/common/json/badoption"
type SelectorOutboundOptions struct { type SelectorOutboundOptions struct {
Outbounds []string `json:"outbounds"` Outbounds []string `json:"outbounds"`
Default string `json:"default,omitempty"` Default string `json:"default,omitempty"`
@ -9,8 +11,8 @@ type SelectorOutboundOptions struct {
type URLTestOutboundOptions struct { type URLTestOutboundOptions struct {
Outbounds []string `json:"outbounds"` Outbounds []string `json:"outbounds"`
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
Interval Duration `json:"interval,omitempty"` Interval badoption.Duration `json:"interval,omitempty"`
Tolerance uint16 `json:"tolerance,omitempty"` Tolerance uint16 `json:"tolerance,omitempty"`
IdleTimeout Duration `json:"idle_timeout,omitempty"` IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"`
InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"`
} }

View file

@ -7,6 +7,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
) )
@ -51,17 +52,17 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro
type InboundOptions struct { type InboundOptions struct {
SniffEnabled bool `json:"sniff,omitempty"` SniffEnabled bool `json:"sniff,omitempty"`
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"` SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
SniffTimeout Duration `json:"sniff_timeout,omitempty"` SniffTimeout badoption.Duration `json:"sniff_timeout,omitempty"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
Detour string `json:"detour,omitempty"` Detour string `json:"detour,omitempty"`
} }
type ListenOptions struct { type ListenOptions struct {
Listen *ListenAddress `json:"listen,omitempty"` Listen *badoption.Addr `json:"listen,omitempty"`
ListenPort uint16 `json:"listen_port,omitempty"` ListenPort uint16 `json:"listen_port,omitempty"`
TCPKeepAlive Duration `json:"tcp_keep_alive,omitempty"` TCPKeepAlive badoption.Duration `json:"tcp_keep_alive,omitempty"`
TCPKeepAliveInterval Duration `json:"tcp_keep_alive_interval,omitempty"` TCPKeepAliveInterval badoption.Duration `json:"tcp_keep_alive_interval,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
TCPMultiPath bool `json:"tcp_multi_path,omitempty"` TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"`
@ -75,7 +76,7 @@ type ListenOptions struct {
InboundOptions InboundOptions
} }
type UDPTimeoutCompat Duration type UDPTimeoutCompat badoption.Duration
func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) { func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) {
return json.Marshal((time.Duration)(c).String()) 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)) *c = UDPTimeoutCompat(time.Second * time.Duration(valueNumber))
return nil return nil
} }
return json.Unmarshal(data, (*Duration)(c)) return json.Unmarshal(data, (*badoption.Duration)(c))
} }
type ListenOptionsWrapper interface { type ListenOptionsWrapper interface {

View file

@ -1,8 +1,10 @@
package option package option
import "github.com/sagernet/sing/common/json/badoption"
type NTPOptions struct { type NTPOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
Interval Duration `json:"interval,omitempty"` Interval badoption.Duration `json:"interval,omitempty"`
WriteToSystem bool `json:"write_to_system,omitempty"` WriteToSystem bool `json:"write_to_system,omitempty"`
ServerOptions ServerOptions
DialerOptions DialerOptions

View file

@ -8,6 +8,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
) )
@ -66,18 +67,18 @@ type DialerOptionsWrapper interface {
type DialerOptions struct { type DialerOptions struct {
Detour string `json:"detour,omitempty"` Detour string `json:"detour,omitempty"`
BindInterface string `json:"bind_interface,omitempty"` BindInterface string `json:"bind_interface,omitempty"`
Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"` Inet4BindAddress *badoption.Addr `json:"inet4_bind_address,omitempty"`
Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"` Inet6BindAddress *badoption.Addr `json:"inet6_bind_address,omitempty"`
ProtectPath string `json:"protect_path,omitempty"` ProtectPath string `json:"protect_path,omitempty"`
RoutingMark uint32 `json:"routing_mark,omitempty"` RoutingMark uint32 `json:"routing_mark,omitempty"`
ReuseAddr bool `json:"reuse_addr,omitempty"` ReuseAddr bool `json:"reuse_addr,omitempty"`
ConnectTimeout Duration `json:"connect_timeout,omitempty"` ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
TCPMultiPath bool `json:"tcp_multi_path,omitempty"` TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"` UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"` UDPFragmentDefault bool `json:"-"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
FallbackDelay Duration `json:"fallback_delay,omitempty"` FallbackDelay badoption.Duration `json:"fallback_delay,omitempty"`
IsWireGuardListener bool `json:"-"` IsWireGuardListener bool `json:"-"`
} }

View file

@ -3,6 +3,7 @@ package option
import ( import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badoption"
) )
type OnDemandOptions struct { type OnDemandOptions struct {
@ -12,10 +13,10 @@ type OnDemandOptions struct {
type OnDemandRule struct { type OnDemandRule struct {
Action *OnDemandRuleAction `json:"action,omitempty"` Action *OnDemandRuleAction `json:"action,omitempty"`
DNSSearchDomainMatch Listable[string] `json:"dns_search_domain_match,omitempty"` DNSSearchDomainMatch badoption.Listable[string] `json:"dns_search_domain_match,omitempty"`
DNSServerAddressMatch Listable[string] `json:"dns_server_address_match,omitempty"` DNSServerAddressMatch badoption.Listable[string] `json:"dns_server_address_match,omitempty"`
InterfaceTypeMatch *OnDemandRuleInterfaceType `json:"interface_type_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"` ProbeURL string `json:"probe_url,omitempty"`
} }

View file

@ -8,6 +8,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
) )
type _Rule struct { type _Rule struct {
@ -66,37 +67,37 @@ func (r Rule) IsValid() bool {
} }
type RawDefaultRule struct { type RawDefaultRule struct {
Inbound Listable[string] `json:"inbound,omitempty"` Inbound badoption.Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"` IPVersion int `json:"ip_version,omitempty"`
Network Listable[string] `json:"network,omitempty"` Network badoption.Listable[string] `json:"network,omitempty"`
AuthUser Listable[string] `json:"auth_user,omitempty"` AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
Protocol Listable[string] `json:"protocol,omitempty"` Protocol badoption.Listable[string] `json:"protocol,omitempty"`
Client Listable[string] `json:"client,omitempty"` Client badoption.Listable[string] `json:"client,omitempty"`
Domain Listable[string] `json:"domain,omitempty"` Domain badoption.Listable[string] `json:"domain,omitempty"`
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex Listable[string] `json:"domain_regex,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
Geosite Listable[string] `json:"geosite,omitempty"` Geosite badoption.Listable[string] `json:"geosite,omitempty"`
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
GeoIP Listable[string] `json:"geoip,omitempty"` GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
IPCIDR Listable[string] `json:"ip_cidr,omitempty"` IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
IPIsPrivate bool `json:"ip_is_private,omitempty"` IPIsPrivate bool `json:"ip_is_private,omitempty"`
SourcePort Listable[uint16] `json:"source_port,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
SourcePortRange Listable[string] `json:"source_port_range,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
Port Listable[uint16] `json:"port,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"`
PortRange Listable[string] `json:"port_range,omitempty"` PortRange badoption.Listable[string] `json:"port_range,omitempty"`
ProcessName Listable[string] `json:"process_name,omitempty"` ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
ProcessPath Listable[string] `json:"process_path,omitempty"` ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName Listable[string] `json:"package_name,omitempty"` PackageName badoption.Listable[string] `json:"package_name,omitempty"`
User Listable[string] `json:"user,omitempty"` User badoption.Listable[string] `json:"user,omitempty"`
UserID Listable[int32] `json:"user_id,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,omitempty"` ClashMode string `json:"clash_mode,omitempty"`
WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet Listable[string] `json:"rule_set,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
Invert bool `json:"invert,omitempty"` Invert bool `json:"invert,omitempty"`

View file

@ -3,6 +3,7 @@ package option
import ( import (
"context" "context"
"fmt" "fmt"
"net/netip"
"time" "time"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@ -11,6 +12,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
) )
type _RuleAction struct { type _RuleAction struct {
@ -177,7 +179,7 @@ type _DNSRouteActionOptions struct {
// Deprecated: Use DNSRouteOptionsActionOptions instead. // Deprecated: Use DNSRouteOptionsActionOptions instead.
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
// Deprecated: Use DNSRouteOptionsActionOptions instead. // Deprecated: Use DNSRouteOptionsActionOptions instead.
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
} }
type DNSRouteActionOptions _DNSRouteActionOptions type DNSRouteActionOptions _DNSRouteActionOptions
@ -199,7 +201,7 @@ func (r *DNSRouteActionOptions) UnmarshalJSONContext(ctx context.Context, data [
type _DNSRouteOptionsActionOptions struct { type _DNSRouteOptionsActionOptions struct {
DisableCache bool `json:"disable_cache,omitempty"` DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"` RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"` ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
} }
type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions type DNSRouteOptionsActionOptions _DNSRouteOptionsActionOptions
@ -225,10 +227,10 @@ func (d DirectActionOptions) Descriptions() []string {
descriptions = append(descriptions, "bind_interface="+d.BindInterface) descriptions = append(descriptions, "bind_interface="+d.BindInterface)
} }
if d.Inet4BindAddress != nil { 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 { 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 { if d.RoutingMark != 0 {
descriptions = append(descriptions, "routing_mark="+fmt.Sprintf("0x%x", d.RoutingMark)) 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 { type RouteActionSniff struct {
Sniffer Listable[string] `json:"sniffer,omitempty"` Sniffer badoption.Listable[string] `json:"sniffer,omitempty"`
Timeout Duration `json:"timeout,omitempty"` Timeout badoption.Duration `json:"timeout,omitempty"`
} }
type RouteActionResolve struct { type RouteActionResolve struct {

View file

@ -9,6 +9,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
) )
type _DNSRule struct { type _DNSRule struct {
@ -67,38 +68,38 @@ func (r DNSRule) IsValid() bool {
} }
type RawDefaultDNSRule struct { type RawDefaultDNSRule struct {
Inbound Listable[string] `json:"inbound,omitempty"` Inbound badoption.Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"` IPVersion int `json:"ip_version,omitempty"`
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
Network Listable[string] `json:"network,omitempty"` Network badoption.Listable[string] `json:"network,omitempty"`
AuthUser Listable[string] `json:"auth_user,omitempty"` AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
Protocol Listable[string] `json:"protocol,omitempty"` Protocol badoption.Listable[string] `json:"protocol,omitempty"`
Domain Listable[string] `json:"domain,omitempty"` Domain badoption.Listable[string] `json:"domain,omitempty"`
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex Listable[string] `json:"domain_regex,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
Geosite Listable[string] `json:"geosite,omitempty"` Geosite badoption.Listable[string] `json:"geosite,omitempty"`
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"` SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
GeoIP Listable[string] `json:"geoip,omitempty"` GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
IPCIDR Listable[string] `json:"ip_cidr,omitempty"` IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
IPIsPrivate bool `json:"ip_is_private,omitempty"` IPIsPrivate bool `json:"ip_is_private,omitempty"`
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"` SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
SourcePort Listable[uint16] `json:"source_port,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
SourcePortRange Listable[string] `json:"source_port_range,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
Port Listable[uint16] `json:"port,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"`
PortRange Listable[string] `json:"port_range,omitempty"` PortRange badoption.Listable[string] `json:"port_range,omitempty"`
ProcessName Listable[string] `json:"process_name,omitempty"` ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
ProcessPath Listable[string] `json:"process_path,omitempty"` ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName Listable[string] `json:"package_name,omitempty"` PackageName badoption.Listable[string] `json:"package_name,omitempty"`
User Listable[string] `json:"user,omitempty"` User badoption.Listable[string] `json:"user,omitempty"`
UserID Listable[int32] `json:"user_id,omitempty"` UserID badoption.Listable[int32] `json:"user_id,omitempty"`
Outbound Listable[string] `json:"outbound,omitempty"` Outbound badoption.Listable[string] `json:"outbound,omitempty"`
ClashMode string `json:"clash_mode,omitempty"` ClashMode string `json:"clash_mode,omitempty"`
WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
RuleSet Listable[string] `json:"rule_set,omitempty"` RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"` RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"` RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
Invert bool `json:"invert,omitempty"` Invert bool `json:"invert,omitempty"`

View file

@ -10,6 +10,7 @@ import (
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
"go4.org/netipx" "go4.org/netipx"
) )
@ -86,7 +87,7 @@ type LocalRuleSet struct {
type RemoteRuleSet struct { type RemoteRuleSet struct {
URL string `json:"url"` URL string `json:"url"`
DownloadDetour string `json:"download_detour,omitempty"` DownloadDetour string `json:"download_detour,omitempty"`
UpdateInterval Duration `json:"update_interval,omitempty"` UpdateInterval badoption.Duration `json:"update_interval,omitempty"`
} }
type _HeadlessRule struct { type _HeadlessRule struct {
@ -145,31 +146,31 @@ func (r HeadlessRule) IsValid() bool {
} }
type DefaultHeadlessRule struct { type DefaultHeadlessRule struct {
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"` QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
Network Listable[string] `json:"network,omitempty"` Network badoption.Listable[string] `json:"network,omitempty"`
Domain Listable[string] `json:"domain,omitempty"` Domain badoption.Listable[string] `json:"domain,omitempty"`
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"` DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"` DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex Listable[string] `json:"domain_regex,omitempty"` DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"` SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
IPCIDR Listable[string] `json:"ip_cidr,omitempty"` IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
SourcePort Listable[uint16] `json:"source_port,omitempty"` SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
SourcePortRange Listable[string] `json:"source_port_range,omitempty"` SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
Port Listable[uint16] `json:"port,omitempty"` Port badoption.Listable[uint16] `json:"port,omitempty"`
PortRange Listable[string] `json:"port_range,omitempty"` PortRange badoption.Listable[string] `json:"port_range,omitempty"`
ProcessName Listable[string] `json:"process_name,omitempty"` ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
ProcessPath Listable[string] `json:"process_path,omitempty"` ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
ProcessPathRegex Listable[string] `json:"process_path_regex,omitempty"` ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
PackageName Listable[string] `json:"package_name,omitempty"` PackageName badoption.Listable[string] `json:"package_name,omitempty"`
WIFISSID Listable[string] `json:"wifi_ssid,omitempty"` WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
WIFIBSSID Listable[string] `json:"wifi_bssid,omitempty"` WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
Invert bool `json:"invert,omitempty"` Invert bool `json:"invert,omitempty"`
DomainMatcher *domain.Matcher `json:"-"` DomainMatcher *domain.Matcher `json:"-"`
SourceIPSet *netipx.IPSet `json:"-"` SourceIPSet *netipx.IPSet `json:"-"`
IPSet *netipx.IPSet `json:"-"` IPSet *netipx.IPSet `json:"-"`
AdGuardDomain Listable[string] `json:"-"` AdGuardDomain badoption.Listable[string] `json:"-"`
AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"` AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"`
} }

View file

@ -1,6 +1,9 @@
package option 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 { type SocksInboundOptions struct {
ListenOptions ListenOptions
@ -31,5 +34,5 @@ type HTTPOutboundOptions struct {
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
OutboundTLSOptionsContainer OutboundTLSOptionsContainer
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
Headers HTTPHeader `json:"headers,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"`
} }

View file

@ -1,14 +1,16 @@
package option package option
import "github.com/sagernet/sing/common/json/badoption"
type SSHOutboundOptions struct { type SSHOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
User string `json:"user,omitempty"` User string `json:"user,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
PrivateKey Listable[string] `json:"private_key,omitempty"` PrivateKey badoption.Listable[string] `json:"private_key,omitempty"`
PrivateKeyPath string `json:"private_key_path,omitempty"` PrivateKeyPath string `json:"private_key_path,omitempty"`
PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"` PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"`
HostKey Listable[string] `json:"host_key,omitempty"` HostKey badoption.Listable[string] `json:"host_key,omitempty"`
HostKeyAlgorithms Listable[string] `json:"host_key_algorithms,omitempty"` HostKeyAlgorithms badoption.Listable[string] `json:"host_key_algorithms,omitempty"`
ClientVersion string `json:"client_version,omitempty"` ClientVersion string `json:"client_version,omitempty"`
} }

View file

@ -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)
}

View file

@ -1,16 +1,18 @@
package option package option
import "github.com/sagernet/sing/common/json/badoption"
type InboundTLSOptions struct { type InboundTLSOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
ServerName string `json:"server_name,omitempty"` ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"` Insecure bool `json:"insecure,omitempty"`
ALPN Listable[string] `json:"alpn,omitempty"` ALPN badoption.Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"` MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"` MaxVersion string `json:"max_version,omitempty"`
CipherSuites Listable[string] `json:"cipher_suites,omitempty"` CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
Certificate Listable[string] `json:"certificate,omitempty"` Certificate badoption.Listable[string] `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"` CertificatePath string `json:"certificate_path,omitempty"`
Key Listable[string] `json:"key,omitempty"` Key badoption.Listable[string] `json:"key,omitempty"`
KeyPath string `json:"key_path,omitempty"` KeyPath string `json:"key_path,omitempty"`
ACME *InboundACMEOptions `json:"acme,omitempty"` ACME *InboundACMEOptions `json:"acme,omitempty"`
ECH *InboundECHOptions `json:"ech,omitempty"` ECH *InboundECHOptions `json:"ech,omitempty"`
@ -39,11 +41,11 @@ type OutboundTLSOptions struct {
DisableSNI bool `json:"disable_sni,omitempty"` DisableSNI bool `json:"disable_sni,omitempty"`
ServerName string `json:"server_name,omitempty"` ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"` Insecure bool `json:"insecure,omitempty"`
ALPN Listable[string] `json:"alpn,omitempty"` ALPN badoption.Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"` MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"` MaxVersion string `json:"max_version,omitempty"`
CipherSuites Listable[string] `json:"cipher_suites,omitempty"` CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
Certificate Listable[string] `json:"certificate,omitempty"` Certificate badoption.Listable[string] `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"` CertificatePath string `json:"certificate_path,omitempty"`
ECH *OutboundECHOptions `json:"ech,omitempty"` ECH *OutboundECHOptions `json:"ech,omitempty"`
UTLS *OutboundUTLSOptions `json:"utls,omitempty"` UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
@ -71,8 +73,8 @@ type InboundRealityOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"` Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"`
PrivateKey string `json:"private_key,omitempty"` PrivateKey string `json:"private_key,omitempty"`
ShortID Listable[string] `json:"short_id,omitempty"` ShortID badoption.Listable[string] `json:"short_id,omitempty"`
MaxTimeDifference Duration `json:"max_time_difference,omitempty"` MaxTimeDifference badoption.Duration `json:"max_time_difference,omitempty"`
} }
type InboundRealityHandshakeOptions struct { type InboundRealityHandshakeOptions struct {
@ -84,7 +86,7 @@ type InboundECHOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
Key Listable[string] `json:"key,omitempty"` Key badoption.Listable[string] `json:"key,omitempty"`
KeyPath string `json:"key_path,omitempty"` KeyPath string `json:"key_path,omitempty"`
} }
@ -92,7 +94,7 @@ type OutboundECHOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"` PQSignatureSchemesEnabled bool `json:"pq_signature_schemes_enabled,omitempty"`
DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"` DynamicRecordSizingDisabled bool `json:"dynamic_record_sizing_disabled,omitempty"`
Config Listable[string] `json:"config,omitempty"` Config badoption.Listable[string] `json:"config,omitempty"`
ConfigPath string `json:"config_path,omitempty"` ConfigPath string `json:"config_path,omitempty"`
} }

View file

@ -5,10 +5,11 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
) )
type InboundACMEOptions struct { type InboundACMEOptions struct {
Domain Listable[string] `json:"domain,omitempty"` Domain badoption.Listable[string] `json:"domain,omitempty"`
DataDirectory string `json:"data_directory,omitempty"` DataDirectory string `json:"data_directory,omitempty"`
DefaultServerName string `json:"default_server_name,omitempty"` DefaultServerName string `json:"default_server_name,omitempty"`
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`

View file

@ -1,12 +1,14 @@
package option package option
import "github.com/sagernet/sing/common/json/badoption"
type TUICInboundOptions struct { type TUICInboundOptions struct {
ListenOptions ListenOptions
Users []TUICUser `json:"users,omitempty"` Users []TUICUser `json:"users,omitempty"`
CongestionControl string `json:"congestion_control,omitempty"` CongestionControl string `json:"congestion_control,omitempty"`
AuthTimeout Duration `json:"auth_timeout,omitempty"` AuthTimeout badoption.Duration `json:"auth_timeout,omitempty"`
ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"`
Heartbeat Duration `json:"heartbeat,omitempty"` Heartbeat badoption.Duration `json:"heartbeat,omitempty"`
InboundTLSOptionsContainer InboundTLSOptionsContainer
} }
@ -25,7 +27,7 @@ type TUICOutboundOptions struct {
UDPRelayMode string `json:"udp_relay_mode,omitempty"` UDPRelayMode string `json:"udp_relay_mode,omitempty"`
UDPOverStream bool `json:"udp_over_stream,omitempty"` UDPOverStream bool `json:"udp_over_stream,omitempty"`
ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"` ZeroRTTHandshake bool `json:"zero_rtt_handshake,omitempty"`
Heartbeat Duration `json:"heartbeat,omitempty"` Heartbeat badoption.Duration `json:"heartbeat,omitempty"`
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
OutboundTLSOptionsContainer OutboundTLSOptionsContainer
} }

View file

@ -7,13 +7,14 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badoption"
) )
type TunInboundOptions struct { type TunInboundOptions struct {
InterfaceName string `json:"interface_name,omitempty"` InterfaceName string `json:"interface_name,omitempty"`
MTU uint32 `json:"mtu,omitempty"` MTU uint32 `json:"mtu,omitempty"`
GSO bool `json:"gso,omitempty"` GSO bool `json:"gso,omitempty"`
Address Listable[netip.Prefix] `json:"address,omitempty"` Address badoption.Listable[netip.Prefix] `json:"address,omitempty"`
AutoRoute bool `json:"auto_route,omitempty"` AutoRoute bool `json:"auto_route,omitempty"`
IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"` IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"`
IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"` IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"`
@ -21,19 +22,19 @@ type TunInboundOptions struct {
AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"` AutoRedirectInputMark FwMark `json:"auto_redirect_input_mark,omitempty"`
AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"` AutoRedirectOutputMark FwMark `json:"auto_redirect_output_mark,omitempty"`
StrictRoute bool `json:"strict_route,omitempty"` StrictRoute bool `json:"strict_route,omitempty"`
RouteAddress Listable[netip.Prefix] `json:"route_address,omitempty"` RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"`
RouteAddressSet Listable[string] `json:"route_address_set,omitempty"` RouteAddressSet badoption.Listable[string] `json:"route_address_set,omitempty"`
RouteExcludeAddress Listable[netip.Prefix] `json:"route_exclude_address,omitempty"` RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"route_exclude_address,omitempty"`
RouteExcludeAddressSet Listable[string] `json:"route_exclude_address_set,omitempty"` RouteExcludeAddressSet badoption.Listable[string] `json:"route_exclude_address_set,omitempty"`
IncludeInterface Listable[string] `json:"include_interface,omitempty"` IncludeInterface badoption.Listable[string] `json:"include_interface,omitempty"`
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"` ExcludeInterface badoption.Listable[string] `json:"exclude_interface,omitempty"`
IncludeUID Listable[uint32] `json:"include_uid,omitempty"` IncludeUID badoption.Listable[uint32] `json:"include_uid,omitempty"`
IncludeUIDRange Listable[string] `json:"include_uid_range,omitempty"` IncludeUIDRange badoption.Listable[string] `json:"include_uid_range,omitempty"`
ExcludeUID Listable[uint32] `json:"exclude_uid,omitempty"` ExcludeUID badoption.Listable[uint32] `json:"exclude_uid,omitempty"`
ExcludeUIDRange Listable[string] `json:"exclude_uid_range,omitempty"` ExcludeUIDRange badoption.Listable[string] `json:"exclude_uid_range,omitempty"`
IncludeAndroidUser Listable[int] `json:"include_android_user,omitempty"` IncludeAndroidUser badoption.Listable[int] `json:"include_android_user,omitempty"`
IncludePackage Listable[string] `json:"include_package,omitempty"` IncludePackage badoption.Listable[string] `json:"include_package,omitempty"`
ExcludePackage Listable[string] `json:"exclude_package,omitempty"` ExcludePackage badoption.Listable[string] `json:"exclude_package,omitempty"`
EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"` EndpointIndependentNat bool `json:"endpoint_independent_nat,omitempty"`
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
Stack string `json:"stack,omitempty"` Stack string `json:"stack,omitempty"`
@ -41,17 +42,17 @@ type TunInboundOptions struct {
InboundOptions InboundOptions
// Deprecated: merged to Address // 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 // 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 // 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 // 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 // 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 // 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 type FwMark uint32

View file

@ -1,5 +1,7 @@
package option package option
import "github.com/sagernet/sing/common/json/badoption"
type TunPlatformOptions struct { type TunPlatformOptions struct {
HTTPProxy *HTTPProxyOptions `json:"http_proxy,omitempty"` HTTPProxy *HTTPProxyOptions `json:"http_proxy,omitempty"`
} }
@ -7,6 +9,6 @@ type TunPlatformOptions struct {
type HTTPProxyOptions struct { type HTTPProxyOptions struct {
Enabled bool `json:"enabled,omitempty"` Enabled bool `json:"enabled,omitempty"`
ServerOptions ServerOptions
BypassDomain Listable[string] `json:"bypass_domain,omitempty"` BypassDomain badoption.Listable[string] `json:"bypass_domain,omitempty"`
MatchDomain Listable[string] `json:"match_domain,omitempty"` MatchDomain badoption.Listable[string] `json:"match_domain,omitempty"`
} }

View file

@ -1,10 +1,7 @@
package option package option
import ( import (
"net/http"
"net/netip"
"strings" "strings"
"time"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -15,79 +12,6 @@ import (
mDNS "github.com/miekg/dns" 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 type NetworkList string
func (v *NetworkList) UnmarshalJSON(content []byte) error { func (v *NetworkList) UnmarshalJSON(content []byte) error {
@ -120,30 +44,6 @@ func (v NetworkList) Build() []string {
return strings.Split(string(v), "\n") 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 type DomainStrategy dns.DomainStrategy
func (s DomainStrategy) String() string { func (s DomainStrategy) String() string {
@ -206,26 +106,6 @@ func (s *DomainStrategy) UnmarshalJSON(bytes []byte) error {
return nil 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 type DNSQueryType uint16
func (t DNSQueryType) String() string { func (t DNSQueryType) String() string {
@ -270,15 +150,3 @@ func DNSQueryTypeToString(queryType uint16) string {
} }
return F.ToString(queryType) 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
}

View file

@ -5,6 +5,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson" "github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
) )
type _V2RayTransportOptions struct { type _V2RayTransportOptions struct {
@ -67,17 +68,17 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
} }
type V2RayHTTPOptions struct { type V2RayHTTPOptions struct {
Host Listable[string] `json:"host,omitempty"` Host badoption.Listable[string] `json:"host,omitempty"`
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
Method string `json:"method,omitempty"` Method string `json:"method,omitempty"`
Headers HTTPHeader `json:"headers,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"`
IdleTimeout Duration `json:"idle_timeout,omitempty"` IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"`
PingTimeout Duration `json:"ping_timeout,omitempty"` PingTimeout badoption.Duration `json:"ping_timeout,omitempty"`
} }
type V2RayWebsocketOptions struct { type V2RayWebsocketOptions struct {
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
Headers HTTPHeader `json:"headers,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"`
MaxEarlyData uint32 `json:"max_early_data,omitempty"` MaxEarlyData uint32 `json:"max_early_data,omitempty"`
EarlyDataHeaderName string `json:"early_data_header_name,omitempty"` EarlyDataHeaderName string `json:"early_data_header_name,omitempty"`
} }
@ -86,8 +87,8 @@ type V2RayQUICOptions struct{}
type V2RayGRPCOptions struct { type V2RayGRPCOptions struct {
ServiceName string `json:"service_name,omitempty"` ServiceName string `json:"service_name,omitempty"`
IdleTimeout Duration `json:"idle_timeout,omitempty"` IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"`
PingTimeout Duration `json:"ping_timeout,omitempty"` PingTimeout badoption.Duration `json:"ping_timeout,omitempty"`
PermitWithoutStream bool `json:"permit_without_stream,omitempty"` PermitWithoutStream bool `json:"permit_without_stream,omitempty"`
ForceLite bool `json:"-"` // for test ForceLite bool `json:"-"` // for test
} }
@ -95,5 +96,5 @@ type V2RayGRPCOptions struct {
type V2RayHTTPUpgradeOptions struct { type V2RayHTTPUpgradeOptions struct {
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
Headers HTTPHeader `json:"headers,omitempty"` Headers badoption.HTTPHeader `json:"headers,omitempty"`
} }

View file

@ -1,13 +1,17 @@
package option package option
import "net/netip" import (
"net/netip"
"github.com/sagernet/sing/common/json/badoption"
)
type WireGuardOutboundOptions struct { type WireGuardOutboundOptions struct {
DialerOptions DialerOptions
SystemInterface bool `json:"system_interface,omitempty"` SystemInterface bool `json:"system_interface,omitempty"`
GSO bool `json:"gso,omitempty"` GSO bool `json:"gso,omitempty"`
InterfaceName string `json:"interface_name,omitempty"` InterfaceName string `json:"interface_name,omitempty"`
LocalAddress Listable[netip.Prefix] `json:"local_address"` LocalAddress badoption.Listable[netip.Prefix] `json:"local_address"`
PrivateKey string `json:"private_key"` PrivateKey string `json:"private_key"`
Peers []WireGuardPeer `json:"peers,omitempty"` Peers []WireGuardPeer `json:"peers,omitempty"`
ServerOptions ServerOptions
@ -23,6 +27,6 @@ type WireGuardPeer struct {
ServerOptions ServerOptions
PublicKey string `json:"public_key,omitempty"` PublicKey string `json:"public_key,omitempty"`
PreSharedKey string `json:"pre_shared_key,omitempty"` PreSharedKey string `json:"pre_shared_key,omitempty"`
AllowedIPs Listable[string] `json:"allowed_ips,omitempty"` AllowedIPs badoption.Listable[string] `json:"allowed_ips,omitempty"`
Reserved []uint8 `json:"reserved,omitempty"` Reserved []uint8 `json:"reserved,omitempty"`
} }

View file

@ -21,6 +21,7 @@ import (
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json/badoption"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ranges" "github.com/sagernet/sing/common/ranges"
@ -257,7 +258,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
return inbound, nil 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 common.Map(uidList, func(uid uint32) ranges.Range[uint32] {
return ranges.NewSingle(uid) return ranges.NewSingle(uid)
}) })

View file

@ -239,9 +239,9 @@ func NewRouter(
} }
var clientSubnet netip.Prefix var clientSubnet netip.Prefix
if server.ClientSubnet != nil { if server.ClientSubnet != nil {
clientSubnet = server.ClientSubnet.Build() clientSubnet = netip.Prefix(common.PtrValueOrDefault(server.ClientSubnet))
} else if dnsOptions.ClientSubnet != nil { } else if dnsOptions.ClientSubnet != nil {
clientSubnet = dnsOptions.ClientSubnet.Build() clientSubnet = netip.Prefix(common.PtrValueOrDefault(dnsOptions.ClientSubnet))
} }
if serverProtocol == "" { if serverProtocol == "" {
serverProtocol = "transport" serverProtocol = "transport"

View file

@ -88,13 +88,13 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
Server: action.RouteOptions.Server, Server: action.RouteOptions.Server,
DisableCache: action.RouteOptions.DisableCache, DisableCache: action.RouteOptions.DisableCache,
RewriteTTL: action.RouteOptions.RewriteTTL, RewriteTTL: action.RouteOptions.RewriteTTL,
ClientSubnet: action.RouteOptions.ClientSubnet.Build(), ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
} }
case C.RuleActionTypeRouteOptions: case C.RuleActionTypeRouteOptions:
return &RuleActionDNSRouteOptions{ return &RuleActionDNSRouteOptions{
DisableCache: action.RouteOptionsOptions.DisableCache, DisableCache: action.RouteOptionsOptions.DisableCache,
RewriteTTL: action.RouteOptionsOptions.RewriteTTL, RewriteTTL: action.RouteOptionsOptions.RewriteTTL,
ClientSubnet: action.RouteOptionsOptions.ClientSubnet.Build(), ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
} }
case C.RuleActionTypeReject: case C.RuleActionTypeReject:
return &RuleActionReject{ return &RuleActionReject{

View file

@ -12,6 +12,7 @@ import (
"github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-box/transport/v2ray"
"github.com/sagernet/sing-vmess" "github.com/sagernet/sing-vmess"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json/badoption"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@ -67,7 +68,7 @@ func newV2RayPlugin(ctx context.Context, pluginOpts Args, router adapter.Router,
transportOptions = option.V2RayTransportOptions{ transportOptions = option.V2RayTransportOptions{
Type: C.V2RayTransportTypeWebsocket, Type: C.V2RayTransportTypeWebsocket,
WebsocketOptions: option.V2RayWebsocketOptions{ WebsocketOptions: option.V2RayWebsocketOptions{
Headers: map[string]option.Listable[string]{ Headers: map[string]badoption.Listable[string]{
"Host": []string{host}, "Host": []string{host},
}, },
Path: path, Path: path,