package option import ( "context" "time" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/schema" 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" ) type InboundOptionsRegistry interface { OptionTypes() []string CreateOptions(outboundType string) (any, bool) } type _Inbound struct { Type string `json:"type"` Tag string `json:"tag,omitempty"` Options any `json:"-"` } type Inbound _Inbound func (h *Inbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { return badjson.MarshallObjectsContext(ctx, (*_Inbound)(h), h.Options) } func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { err := json.UnmarshalContext(ctx, content, (*_Inbound)(h)) if err != nil { return err } registry := service.FromContext[InboundOptionsRegistry](ctx) if registry == nil { return E.New("missing inbound fields registry in context") } options, loaded := registry.CreateOptions(h.Type) if !loaded { return E.New("unknown inbound type: ", h.Type) } err = badjson.UnmarshallExcludedContext(ctx, content, (*_Inbound)(h), options) if err != nil { return err } if listenWrapper, isListen := options.(ListenOptionsWrapper); isListen { //nolint:staticcheck if listenWrapper.TakeListenOptions().InboundOptions != (InboundOptions{}) { return E.New("legacy inbound fields are deprecated in sing-box 1.11.0 and removed in sing-box 1.13.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-legacy-inbound-fields-to-rule-actions") } } h.Options = options return nil } func (h Inbound) DescribeSchema(builder schema.Builder) (*schema.Node, error) { return builder.Define("Inbound", func() (*schema.Node, error) { registry := service.FromContext[InboundOptionsRegistry](builder.Context()) if registry == nil { return nil, E.New("missing inbound options registry in context") } return registryUnion(builder, registry, []string{C.TypeShadowsocksR}, true) }) } // Deprecated: Use rule action instead type InboundOptions struct { SniffEnabled bool `json:"sniff,omitempty" schema:"omit"` SniffOverrideDestination bool `json:"sniff_override_destination,omitempty" schema:"omit"` SniffTimeout badoption.Duration `json:"sniff_timeout,omitempty" schema:"omit"` DomainStrategy DomainStrategy `json:"domain_strategy,omitempty" schema:"omit"` UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty" schema:"omit"` } type ListenOptions struct { Listen *badoption.Addr `json:"listen,omitempty"` ListenPort uint16 `json:"listen_port,omitempty"` BindInterface string `json:"bind_interface,omitempty"` RoutingMark FwMark `json:"routing_mark,omitempty"` ReuseAddr bool `json:"reuse_addr,omitempty"` NetNs string `json:"netns,omitempty" reference:"network_namespace"` DisableTCPKeepAlive bool `json:"disable_tcp_keep_alive,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"` Detour string `json:"detour,omitempty" reference:"inbound"` // Deprecated: removed ProxyProtocol bool `json:"proxy_protocol,omitempty" schema:"omit"` // Deprecated: removed ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty" schema:"omit"` // Legacy inbound fields are rejected since sing-box 1.13.0. //nolint:staticcheck InboundOptions `schema:"omit"` } type UDPNATBehavior uint8 const ( UDPNATBehaviorEndpointIndependent UDPNATBehavior = iota UDPNATBehaviorAddressDependent UDPNATBehaviorAddressAndPortDependent ) func (b UDPNATBehavior) MarshalJSON() ([]byte, error) { var value string switch b { case UDPNATBehaviorEndpointIndependent: value = "endpoint_independent" case UDPNATBehaviorAddressDependent: value = "address_dependent" case UDPNATBehaviorAddressAndPortDependent: value = "address_and_port_dependent" default: return nil, E.New("unknown UDP NAT behavior: ", uint8(b)) } return json.Marshal(value) } func (b *UDPNATBehavior) UnmarshalJSON(data []byte) error { var value string err := json.Unmarshal(data, &value) if err != nil { return err } switch value { case "", "endpoint_independent": *b = UDPNATBehaviorEndpointIndependent case "address_dependent": *b = UDPNATBehaviorAddressDependent case "address_and_port_dependent": *b = UDPNATBehaviorAddressAndPortDependent default: return E.New("unknown UDP NAT behavior: ", value) } return nil } func (b UDPNATBehavior) DescribeSchema(builder schema.Builder) (*schema.Node, error) { return schema.StringEnum("", "endpoint_independent", "address_dependent", "address_and_port_dependent"), nil } type UDPTimeoutCompat badoption.Duration func (c UDPTimeoutCompat) MarshalJSON() ([]byte, error) { return json.Marshal((time.Duration)(c).String()) } func (c *UDPTimeoutCompat) UnmarshalJSON(data []byte) error { var valueNumber int64 err := json.Unmarshal(data, &valueNumber) if err == nil { *c = UDPTimeoutCompat(time.Second * time.Duration(valueNumber)) return nil } return json.Unmarshal(data, (*badoption.Duration)(c)) } func (c UDPTimeoutCompat) DescribeSchema(builder schema.Builder) (*schema.Node, error) { return builder.Define("UDPTimeout", func() (*schema.Node, error) { return schema.AnyOf(schema.UnsignedNode(32), schema.DurationNode()), nil }) } type ListenOptionsWrapper interface { TakeListenOptions() ListenOptions ReplaceListenOptions(options ListenOptions) } func (o *ListenOptions) TakeListenOptions() ListenOptions { return *o } func (o *ListenOptions) ReplaceListenOptions(options ListenOptions) { *o = options }