Invalid config check

This commit is contained in:
世界 2022-07-03 01:57:04 +08:00
parent 6eae8e361f
commit 30444057bd
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
16 changed files with 276 additions and 162 deletions

View file

@ -8,10 +8,14 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
) )
func New(ctx context.Context, router adapter.Router, logger log.Logger, index int, options option.Inbound) (adapter.Inbound, error) { func New(ctx context.Context, router adapter.Router, logger log.Logger, index int, options option.Inbound) (adapter.Inbound, error) {
if common.IsEmptyByEquals(options) {
return nil, E.New("empty inbound config")
}
var tag string var tag string
if options.Tag != "" { if options.Tag != "" {
tag = options.Tag tag = options.Tag
@ -21,16 +25,16 @@ func New(ctx context.Context, router adapter.Router, logger log.Logger, index in
inboundLogger := logger.WithPrefix(F.ToString("inbound/", options.Type, "[", tag, "]: ")) inboundLogger := logger.WithPrefix(F.ToString("inbound/", options.Type, "[", tag, "]: "))
switch options.Type { switch options.Type {
case C.TypeDirect: case C.TypeDirect:
return NewDirect(ctx, router, inboundLogger, options.Tag, common.PtrValueOrDefault(options.DirectOptions)), nil return NewDirect(ctx, router, inboundLogger, options.Tag, options.DirectOptions), nil
case C.TypeSocks: case C.TypeSocks:
return NewSocks(ctx, router, inboundLogger, options.Tag, common.PtrValueOrDefault(options.SocksOptions)), nil return NewSocks(ctx, router, inboundLogger, options.Tag, options.SocksOptions), nil
case C.TypeHTTP: case C.TypeHTTP:
return NewHTTP(ctx, router, inboundLogger, options.Tag, common.PtrValueOrDefault(options.HTTPOptions)), nil return NewHTTP(ctx, router, inboundLogger, options.Tag, options.HTTPOptions), nil
case C.TypeMixed: case C.TypeMixed:
return NewMixed(ctx, router, inboundLogger, options.Tag, common.PtrValueOrDefault(options.MixedOptions)), nil return NewMixed(ctx, router, inboundLogger, options.Tag, options.MixedOptions), nil
case C.TypeShadowsocks: case C.TypeShadowsocks:
return NewShadowsocks(ctx, router, inboundLogger, options.Tag, common.PtrValueOrDefault(options.ShadowsocksOptions)) return NewShadowsocks(ctx, router, inboundLogger, options.Tag, options.ShadowsocksOptions)
default: default:
panic(F.ToString("unknown inbound type: ", options.Type)) return nil, E.New("unknown inbound type: ", options.Type)
} }
} }

View file

@ -6,10 +6,14 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
) )
func New(router adapter.Router, logger log.Logger, index int, options option.Outbound) (adapter.Outbound, error) { func New(router adapter.Router, logger log.Logger, index int, options option.Outbound) (adapter.Outbound, error) {
if common.IsEmpty(options) {
return nil, E.New("empty outbound config")
}
var tag string var tag string
if options.Tag != "" { if options.Tag != "" {
tag = options.Tag tag = options.Tag
@ -19,10 +23,10 @@ func New(router adapter.Router, logger log.Logger, index int, options option.Out
outboundLogger := logger.WithPrefix(F.ToString("outbound/", options.Type, "[", tag, "]: ")) outboundLogger := logger.WithPrefix(F.ToString("outbound/", options.Type, "[", tag, "]: "))
switch options.Type { switch options.Type {
case C.TypeDirect: case C.TypeDirect:
return NewDirect(router, outboundLogger, options.Tag, common.PtrValueOrDefault(options.DirectOptions)), nil return NewDirect(router, outboundLogger, options.Tag, options.DirectOptions), nil
case C.TypeShadowsocks: case C.TypeShadowsocks:
return NewShadowsocks(router, outboundLogger, options.Tag, common.PtrValueOrDefault(options.ShadowsocksOptions)) return NewShadowsocks(router, outboundLogger, options.Tag, options.ShadowsocksOptions)
default: default:
panic(F.ToString("unknown outbound type: ", options.Type)) return nil, E.New("unknown outbound type: ", options.Type)
} }
} }

View file

@ -13,10 +13,25 @@ import (
) )
func NewRule(router adapter.Router, logger log.Logger, options option.Rule) (adapter.Rule, error) { func NewRule(router adapter.Router, logger log.Logger, options option.Rule) (adapter.Rule, error) {
if common.IsEmptyByEquals(options) {
return nil, E.New("empty rule config")
}
switch options.Type { switch options.Type {
case "", C.RuleTypeDefault: case "", C.RuleTypeDefault:
if !options.DefaultOptions.IsValid() {
return nil, E.New("missing conditions")
}
if options.DefaultOptions.Outbound == "" {
return nil, E.New("missing outbound field")
}
return NewDefaultRule(router, logger, common.PtrValueOrDefault(options.DefaultOptions)) return NewDefaultRule(router, logger, common.PtrValueOrDefault(options.DefaultOptions))
case C.RuleTypeLogical: case C.RuleTypeLogical:
if !options.LogicalOptions.IsValid() {
return nil, E.New("missing conditions")
}
if options.LogicalOptions.Outbound == "" {
return nil, E.New("missing outbound field")
}
return NewLogicalRule(router, logger, common.PtrValueOrDefault(options.LogicalOptions)) return NewLogicalRule(router, logger, common.PtrValueOrDefault(options.LogicalOptions))
default: default:
return nil, E.New("unknown rule type: ", options.Type) return nil, E.New("unknown rule type: ", options.Type)

View file

@ -18,7 +18,10 @@ func init() {
logrus.StandardLogger().Formatter.(*logrus.TextFormatter).ForceColors = true logrus.StandardLogger().Formatter.(*logrus.TextFormatter).ForceColors = true
} }
var configPath string var (
configPath string
workingDir string
)
func main() { func main() {
command := &cobra.Command{ command := &cobra.Command{
@ -26,12 +29,19 @@ func main() {
Run: run, Run: run,
} }
command.Flags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path") command.Flags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path")
command.Flags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
if err := command.Execute(); err != nil { if err := command.Execute(); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
} }
func run(cmd *cobra.Command, args []string) { func run(cmd *cobra.Command, args []string) {
if workingDir != "" {
if err := os.Chdir(workingDir); err != nil {
logrus.Fatal(err)
}
}
configContent, err := os.ReadFile(configPath) configContent, err := os.ReadFile(configPath)
if err != nil { if err != nil {
logrus.Fatal("read config: ", err) logrus.Fatal("read config: ", err)

2
go.mod
View file

@ -6,7 +6,7 @@ require (
github.com/database64128/tfo-go v1.0.4 github.com/database64128/tfo-go v1.0.4
github.com/logrusorgru/aurora v2.0.3+incompatible github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/oschwald/geoip2-golang v1.7.0 github.com/oschwald/geoip2-golang v1.7.0
github.com/sagernet/sing v0.0.0-20220702141141-b3923d54845b github.com/sagernet/sing v0.0.0-20220702174608-cb5bb5132de4
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.5.0 github.com/spf13/cobra v1.5.0

4
go.sum
View file

@ -18,8 +18,8 @@ github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagernet/sing v0.0.0-20220702141141-b3923d54845b h1:oK5RglZ0s4oXNSrIsLJkBiHbYoAUMOGLN3a0JgDNzVM= github.com/sagernet/sing v0.0.0-20220702174608-cb5bb5132de4 h1:Ce6nW9gV6g2hq/1K/nMtlVGiTtxh86EWs0/jOzMuNa4=
github.com/sagernet/sing v0.0.0-20220702141141-b3923d54845b/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= github.com/sagernet/sing v0.0.0-20220702174608-cb5bb5132de4/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=

View file

@ -7,6 +7,8 @@ import (
) )
type Logger interface { type Logger interface {
Start() error
Close() error
Trace(args ...interface{}) Trace(args ...interface{})
Debug(args ...interface{}) Debug(args ...interface{})
Info(args ...interface{}) Info(args ...interface{})
@ -18,7 +20,6 @@ type Logger interface {
Panic(args ...interface{}) Panic(args ...interface{})
WithContext(ctx context.Context) Logger WithContext(ctx context.Context) Logger
WithPrefix(prefix string) Logger WithPrefix(prefix string) Logger
Close() error
} }
func NewLogger(options option.LogOption) (Logger, error) { func NewLogger(options option.LogOption) (Logger, error) {

View file

@ -15,7 +15,8 @@ var _ Logger = (*logrusLogger)(nil)
type logrusLogger struct { type logrusLogger struct {
abstractLogrusLogger abstractLogrusLogger
output *os.File outputPath string
output *os.File
} }
type abstractLogrusLogger interface { type abstractLogrusLogger interface {
@ -28,7 +29,6 @@ func NewLogrusLogger(options option.LogOption) (*logrusLogger, error) {
logger.SetLevel(logrus.TraceLevel) logger.SetLevel(logrus.TraceLevel)
logger.Formatter.(*logrus.TextFormatter).ForceColors = true logger.Formatter.(*logrus.TextFormatter).ForceColors = true
logger.AddHook(new(logrusHook)) logger.AddHook(new(logrusHook))
var output *os.File
var err error var err error
if options.Level != "" { if options.Level != "" {
logger.Level, err = logrus.ParseLevel(options.Level) logger.Level, err = logrus.ParseLevel(options.Level)
@ -36,18 +36,26 @@ func NewLogrusLogger(options option.LogOption) (*logrusLogger, error) {
return nil, err return nil, err
} }
} }
if options.Output != "" { return &logrusLogger{logger, options.Output, nil}, nil
output, err = os.OpenFile(options.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) }
func (l *logrusLogger) Start() error {
if l.outputPath != "" {
output, err := os.OpenFile(l.outputPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil { if err != nil {
return nil, E.Extend(err, "open log output") return E.Cause(err, "open log output")
} }
logger.SetOutput(output) l.abstractLogrusLogger.(*logrus.Logger).SetOutput(output)
} }
return &logrusLogger{logger, output}, nil return nil
}
func (l *logrusLogger) Close() error {
return common.Close(common.PtrOrNil(l.output))
} }
func (l *logrusLogger) WithContext(ctx context.Context) Logger { func (l *logrusLogger) WithContext(ctx context.Context) Logger {
return &logrusLogger{l.abstractLogrusLogger.WithContext(ctx), nil} return &logrusLogger{abstractLogrusLogger: l.abstractLogrusLogger.WithContext(ctx)}
} }
func (l *logrusLogger) WithPrefix(prefix string) Logger { func (l *logrusLogger) WithPrefix(prefix string) Logger {
@ -57,9 +65,5 @@ func (l *logrusLogger) WithPrefix(prefix string) Logger {
prefix = F.ToString(loadedPrefix, prefix) prefix = F.ToString(loadedPrefix, prefix)
} }
} }
return &logrusLogger{l.WithField("prefix", prefix), nil} return &logrusLogger{abstractLogrusLogger: l.WithField("prefix", prefix)}
}
func (l *logrusLogger) Close() error {
return common.Close(common.PtrOrNil(l.output))
} }

View file

@ -10,6 +10,14 @@ func NewNopLogger() Logger {
return (*nopLogger)(nil) return (*nopLogger)(nil)
} }
func (l *nopLogger) Start() error {
return nil
}
func (l *nopLogger) Close() error {
return nil
}
func (l *nopLogger) Trace(args ...interface{}) { func (l *nopLogger) Trace(args ...interface{}) {
} }
@ -44,7 +52,3 @@ func (l *nopLogger) WithContext(ctx context.Context) Logger {
func (l *nopLogger) WithPrefix(prefix string) Logger { func (l *nopLogger) WithPrefix(prefix string) Logger {
return l return l
} }
func (l *nopLogger) Close() error {
return nil
}

View file

@ -1,5 +1,7 @@
package option package option
import "github.com/sagernet/sing/common"
type Options struct { type Options struct {
Log *LogOption `json:"log"` Log *LogOption `json:"log"`
Inbounds []Inbound `json:"inbounds,omitempty"` Inbounds []Inbound `json:"inbounds,omitempty"`
@ -7,6 +9,13 @@ type Options struct {
Route *RouteOptions `json:"route,omitempty"` Route *RouteOptions `json:"route,omitempty"`
} }
func (o Options) Equals(other Options) bool {
return common.ComparablePtrEquals(o.Log, other.Log) &&
common.SliceEquals(o.Inbounds, other.Inbounds) &&
common.ComparableSliceEquals(o.Outbounds, other.Outbounds) &&
common.PtrEquals(o.Route, other.Route)
}
type LogOption struct { type LogOption struct {
Disabled bool `json:"disabled,omitempty"` Disabled bool `json:"disabled,omitempty"`
Level string `json:"level,omitempty"` Level string `json:"level,omitempty"`

View file

@ -3,52 +3,50 @@ package option
import ( import (
"encoding/json" "encoding/json"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
var ErrUnknownInboundType = E.New("unknown inbound type")
type _Inbound struct { type _Inbound struct {
Tag string `json:"tag,omitempty"` Tag string `json:"tag,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type"`
DirectOptions *DirectInboundOptions `json:"directOptions,omitempty"` DirectOptions DirectInboundOptions `json:"-"`
SocksOptions *SimpleInboundOptions `json:"socksOptions,omitempty"` SocksOptions SimpleInboundOptions `json:"-"`
HTTPOptions *SimpleInboundOptions `json:"httpOptions,omitempty"` HTTPOptions SimpleInboundOptions `json:"-"`
MixedOptions *SimpleInboundOptions `json:"mixedOptions,omitempty"` MixedOptions SimpleInboundOptions `json:"-"`
ShadowsocksOptions *ShadowsocksInboundOptions `json:"shadowsocksOptions,omitempty"` ShadowsocksOptions ShadowsocksInboundOptions `json:"-"`
} }
type Inbound _Inbound type Inbound _Inbound
func (i Inbound) Equals(other Inbound) bool {
return i.Type == other.Type &&
i.Tag == other.Tag &&
common.Equals(i.DirectOptions, other.DirectOptions) &&
common.Equals(i.SocksOptions, other.SocksOptions) &&
common.Equals(i.HTTPOptions, other.HTTPOptions) &&
common.Equals(i.MixedOptions, other.MixedOptions) &&
common.Equals(i.ShadowsocksOptions, other.ShadowsocksOptions)
}
func (i *Inbound) MarshalJSON() ([]byte, error) { func (i *Inbound) MarshalJSON() ([]byte, error) {
var options []byte var v any
var err error
switch i.Type { switch i.Type {
case "direct": case "direct":
options, err = json.Marshal(i.DirectOptions) v = i.DirectOptions
case "socks": case "socks":
options, err = json.Marshal(i.SocksOptions) v = i.SocksOptions
case "http": case "http":
options, err = json.Marshal(i.HTTPOptions) v = i.HTTPOptions
case "mixed": case "mixed":
options, err = json.Marshal(i.MixedOptions) v = i.MixedOptions
case "shadowsocks": case "shadowsocks":
options, err = json.Marshal(i.ShadowsocksOptions) v = i.ShadowsocksOptions
default: default:
return nil, E.Extend(ErrUnknownInboundType, i.Type) return nil, E.New("unknown inbound type: ", i.Type)
} }
if err != nil { return MarshallObjects(i, v)
return nil, err
}
var content map[string]any
err = json.Unmarshal(options, &content)
if err != nil {
return nil, err
}
content["tag"] = i.Tag
content["type"] = i.Type
return json.Marshal(content)
} }
func (i *Inbound) UnmarshalJSON(bytes []byte) error { func (i *Inbound) UnmarshalJSON(bytes []byte) error {
@ -56,43 +54,29 @@ func (i *Inbound) UnmarshalJSON(bytes []byte) error {
if err != nil { if err != nil {
return err return err
} }
var v any
switch i.Type { switch i.Type {
case "direct": case "direct":
if i.DirectOptions != nil { v = &i.DirectOptions
break
}
err = json.Unmarshal(bytes, &i.DirectOptions)
case "socks": case "socks":
if i.SocksOptions != nil { v = &i.SocksOptions
break
}
err = json.Unmarshal(bytes, &i.SocksOptions)
case "http": case "http":
if i.HTTPOptions != nil { v = &i.HTTPOptions
break
}
err = json.Unmarshal(bytes, &i.HTTPOptions)
case "mixed": case "mixed":
if i.MixedOptions != nil { v = &i.MixedOptions
break
}
err = json.Unmarshal(bytes, &i.MixedOptions)
case "shadowsocks": case "shadowsocks":
if i.ShadowsocksOptions != nil { v = &i.ShadowsocksOptions
break
}
err = json.Unmarshal(bytes, &i.ShadowsocksOptions)
default: default:
return E.Extend(ErrUnknownInboundType, i.Type) return nil
} }
return err return json.Unmarshal(bytes, v)
} }
type ListenOptions struct { type ListenOptions struct {
Listen ListenAddress `json:"listen"` Listen ListenAddress `json:"listen"`
Port uint16 `json:"listen_port"` Port uint16 `json:"listen_port"`
TCPFastOpen bool `json:"tcpFastOpen,omitempty"` TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
UDPTimeout int64 `json:"udpTimeout,omitempty"` UDPTimeout int64 `json:"udp_timeout,omitempty"`
} }
type SimpleInboundOptions struct { type SimpleInboundOptions struct {
@ -100,11 +84,23 @@ type SimpleInboundOptions struct {
Users []auth.User `json:"users,omitempty"` Users []auth.User `json:"users,omitempty"`
} }
func (o SimpleInboundOptions) Equals(other SimpleInboundOptions) bool {
return o.ListenOptions == other.ListenOptions &&
common.ComparableSliceEquals(o.Users, other.Users)
}
type DirectInboundOptions struct { type DirectInboundOptions struct {
ListenOptions ListenOptions
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`
OverrideAddress string `json:"overrideAddress,omitempty"` OverrideAddress string `json:"override_address,omitempty"`
OverridePort uint16 `json:"overridePort,omitempty"` OverridePort uint16 `json:"override_port,omitempty"`
}
func (o DirectInboundOptions) Equals(other DirectInboundOptions) bool {
return o.ListenOptions == other.ListenOptions &&
common.ComparableSliceEquals(o.Network, other.Network) &&
o.OverrideAddress == other.OverrideAddress &&
o.OverridePort == other.OverridePort
} }
type ShadowsocksInboundOptions struct { type ShadowsocksInboundOptions struct {
@ -113,3 +109,10 @@ type ShadowsocksInboundOptions struct {
Method string `json:"method"` Method string `json:"method"`
Password string `json:"password"` Password string `json:"password"`
} }
func (o ShadowsocksInboundOptions) Equals(other ShadowsocksInboundOptions) bool {
return o.ListenOptions == other.ListenOptions &&
common.ComparableSliceEquals(o.Network, other.Network) &&
o.Method == other.Method &&
o.Password == other.Password
}

40
option/json.go Normal file
View file

@ -0,0 +1,40 @@
package option
import (
"encoding/json"
)
func ToMap(v any) (map[string]any, error) {
bytes, err := json.Marshal(v)
if err != nil {
return nil, err
}
var content map[string]any
err = json.Unmarshal(bytes, &content)
if err != nil {
return nil, err
}
return content, nil
}
func MergeObjects(objects ...any) (map[string]any, error) {
content := make(map[string]any)
for _, object := range objects {
objectMap, err := ToMap(object)
if err != nil {
return nil, err
}
for k, v := range objectMap {
content[k] = v
}
}
return content, nil
}
func MarshallObjects(objects ...any) ([]byte, error) {
content, err := MergeObjects(objects...)
if err != nil {
return nil, err
}
return json.Marshal(content)
}

View file

@ -2,7 +2,7 @@ package option
import "encoding/json" import "encoding/json"
type Listable[T any] []T type Listable[T comparable] []T
func (l *Listable[T]) MarshalJSON() ([]byte, error) { func (l *Listable[T]) MarshalJSON() ([]byte, error) {
arrayList := []T(*l) arrayList := []T(*l)

View file

@ -7,64 +7,43 @@ import (
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
) )
var ErrUnknownOutboundType = E.New("unknown outbound type")
type _Outbound struct { type _Outbound struct {
Tag string `json:"tag,omitempty"` Tag string `json:"tag,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
DirectOptions *DirectOutboundOptions `json:"directOptions,omitempty"` DirectOptions DirectOutboundOptions `json:"-"`
ShadowsocksOptions *ShadowsocksOutboundOptions `json:"shadowsocksOptions,omitempty"` ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"`
} }
type Outbound _Outbound type Outbound _Outbound
func (i *Outbound) MarshalJSON() ([]byte, error) { func (i *Outbound) MarshalJSON() ([]byte, error) {
var options []byte var v any
var err error
switch i.Type { switch i.Type {
case "direct": case "direct":
options, err = json.Marshal(i.DirectOptions) v = i.DirectOptions
case "shadowsocks": case "shadowsocks":
options, err = json.Marshal(i.ShadowsocksOptions) v = i.ShadowsocksOptions
default: default:
return nil, E.Extend(ErrUnknownOutboundType, i.Type) return nil, E.New("unknown outbound type: ", i.Type)
} }
if err != nil { return MarshallObjects(i, v)
return nil, err
}
var content map[string]any
err = json.Unmarshal(options, &content)
if err != nil {
return nil, err
}
content["tag"] = i.Tag
content["type"] = i.Type
return json.Marshal(content)
} }
func (i *Outbound) UnmarshalJSON(bytes []byte) error { func (i *Outbound) UnmarshalJSON(bytes []byte) error {
if err := json.Unmarshal(bytes, (*_Outbound)(i)); err != nil { err := json.Unmarshal(bytes, (*_Outbound)(i))
if err != nil {
return err return err
} }
var v any
switch i.Type { switch i.Type {
case "direct": case "direct":
if i.DirectOptions != nil { v = &i.DirectOptions
break
}
if err := json.Unmarshal(bytes, &i.DirectOptions); err != nil {
return err
}
case "shadowsocks": case "shadowsocks":
if i.ShadowsocksOptions != nil { v = &i.ShadowsocksOptions
break
}
if err := json.Unmarshal(bytes, &i.ShadowsocksOptions); err != nil {
return err
}
default: default:
return E.Extend(ErrUnknownOutboundType, i.Type) return nil
} }
return nil return json.Unmarshal(bytes, v)
} }
type DialerOptions struct { type DialerOptions struct {

View file

@ -4,16 +4,20 @@ import (
"encoding/json" "encoding/json"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
) )
var ErrUnknownRuleType = E.New("unknown rule type")
type RouteOptions struct { type RouteOptions struct {
GeoIP *GeoIPOptions `json:"geoip,omitempty"` GeoIP *GeoIPOptions `json:"geoip,omitempty"`
Rules []Rule `json:"rules,omitempty"` Rules []Rule `json:"rules,omitempty"`
} }
func (o RouteOptions) Equals(other RouteOptions) bool {
return common.ComparablePtrEquals(o.GeoIP, other.GeoIP) &&
common.SliceEquals(o.Rules, other.Rules)
}
type GeoIPOptions struct { type GeoIPOptions struct {
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
DownloadURL string `json:"download_url,omitempty"` DownloadURL string `json:"download_url,omitempty"`
@ -28,25 +32,23 @@ type _Rule struct {
type Rule _Rule type Rule _Rule
func (r Rule) Equals(other Rule) bool {
return r.Type == other.Type &&
common.PtrEquals(r.DefaultOptions, other.DefaultOptions) &&
common.PtrEquals(r.LogicalOptions, other.LogicalOptions)
}
func (r *Rule) MarshalJSON() ([]byte, error) { func (r *Rule) MarshalJSON() ([]byte, error) {
var content map[string]any var v any
switch r.Type { switch r.Type {
case "", C.RuleTypeDefault: case C.RuleTypeDefault:
return json.Marshal(r.DefaultOptions) v = r.DefaultOptions
case C.RuleTypeLogical: case C.RuleTypeLogical:
options, err := json.Marshal(r.LogicalOptions) v = r.LogicalOptions
if err != nil {
return nil, err
}
err = json.Unmarshal(options, &content)
if err != nil {
return nil, err
}
content["type"] = r.Type
return json.Marshal(content)
default: default:
return nil, E.Extend(ErrUnknownRuleType, r.Type) return nil, E.New("unknown rule type: " + r.Type)
} }
return MarshallObjects(r, v)
} }
func (r *Rule) UnmarshalJSON(bytes []byte) error { func (r *Rule) UnmarshalJSON(bytes []byte) error {
@ -54,21 +56,19 @@ func (r *Rule) UnmarshalJSON(bytes []byte) error {
if err != nil { if err != nil {
return err return err
} }
switch r.Type { if r.Type == "" {
case "", C.RuleTypeDefault: r.Type = C.RuleTypeDefault
if r.DefaultOptions == nil {
break
}
err = json.Unmarshal(bytes, r.DefaultOptions)
case C.RuleTypeLogical:
if r.LogicalOptions == nil {
break
}
err = json.Unmarshal(bytes, r.LogicalOptions)
default:
err = E.Extend(ErrUnknownRuleType, r.Type)
} }
return err var v any
switch r.Type {
case C.RuleTypeDefault:
v = &r.DefaultOptions
case C.RuleTypeLogical:
v = &r.LogicalOptions
default:
return E.New("unknown rule type: " + r.Type)
}
return json.Unmarshal(bytes, v)
} }
type DefaultRule struct { type DefaultRule struct {
@ -90,8 +90,41 @@ type DefaultRule struct {
Outbound string `json:"outbound,omitempty"` Outbound string `json:"outbound,omitempty"`
} }
func (r DefaultRule) IsValid() bool {
var defaultValue DefaultRule
defaultValue.Outbound = r.Outbound
return !r.Equals(defaultValue)
}
func (r DefaultRule) Equals(other DefaultRule) bool {
return common.ComparableSliceEquals(r.Inbound, other.Inbound) &&
r.IPVersion == other.IPVersion &&
r.Network == other.Network &&
common.ComparableSliceEquals(r.Protocol, other.Protocol) &&
common.ComparableSliceEquals(r.Domain, other.Domain) &&
common.ComparableSliceEquals(r.DomainSuffix, other.DomainSuffix) &&
common.ComparableSliceEquals(r.DomainKeyword, other.DomainKeyword) &&
common.ComparableSliceEquals(r.SourceGeoIP, other.SourceGeoIP) &&
common.ComparableSliceEquals(r.GeoIP, other.GeoIP) &&
common.ComparableSliceEquals(r.SourceIPCIDR, other.SourceIPCIDR) &&
common.ComparableSliceEquals(r.IPCIDR, other.IPCIDR) &&
common.ComparableSliceEquals(r.SourcePort, other.SourcePort) &&
common.ComparableSliceEquals(r.Port, other.Port) &&
r.Outbound == other.Outbound
}
type LogicalRule struct { type LogicalRule struct {
Mode string `json:"mode"` Mode string `json:"mode"`
Rules []DefaultRule `json:"rules,omitempty"` Rules []DefaultRule `json:"rules,omitempty"`
Outbound string `json:"outbound,omitempty"` Outbound string `json:"outbound,omitempty"`
} }
func (r LogicalRule) IsValid() bool {
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
}
func (r LogicalRule) Equals(other LogicalRule) bool {
return r.Mode == other.Mode &&
common.SliceEquals(r.Rules, other.Rules) &&
r.Outbound == other.Outbound
}

View file

@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
) )
var _ adapter.Service = (*Service)(nil) var _ adapter.Service = (*Service)(nil)
@ -24,11 +25,11 @@ type Service struct {
func NewService(ctx context.Context, options option.Options) (*Service, error) { func NewService(ctx context.Context, options option.Options) (*Service, error) {
logger, err := log.NewLogger(common.PtrValueOrDefault(options.Log)) logger, err := log.NewLogger(common.PtrValueOrDefault(options.Log))
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "parse log options")
} }
router, err := route.NewRouter(ctx, logger, common.PtrValueOrDefault(options.Route)) router, err := route.NewRouter(ctx, logger, common.PtrValueOrDefault(options.Route))
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "parse route options")
} }
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
@ -36,7 +37,7 @@ func NewService(ctx context.Context, options option.Options) (*Service, error) {
var inboundService adapter.Inbound var inboundService adapter.Inbound
inboundService, err = inbound.New(ctx, router, logger, i, inboundOptions) inboundService, err = inbound.New(ctx, router, logger, i, inboundOptions)
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "parse inbound[", i, "]")
} }
inbounds = append(inbounds, inboundService) inbounds = append(inbounds, inboundService)
} }
@ -44,7 +45,7 @@ func NewService(ctx context.Context, options option.Options) (*Service, error) {
var outboundService adapter.Outbound var outboundService adapter.Outbound
outboundService, err = outbound.New(router, logger, i, outboundOptions) outboundService, err = outbound.New(router, logger, i, outboundOptions)
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "parse outbound[", i, "]")
} }
outbounds = append(outbounds, outboundService) outbounds = append(outbounds, outboundService)
} }
@ -61,13 +62,19 @@ func NewService(ctx context.Context, options option.Options) (*Service, error) {
} }
func (s *Service) Start() error { func (s *Service) Start() error {
err := s.logger.Start()
if err != nil {
return err
}
for _, in := range s.inbounds { for _, in := range s.inbounds {
err := in.Start() err = in.Start()
if err != nil { if err != nil {
return err return err
} }
} }
return nil return common.AnyError(
s.router.Start(),
)
} }
func (s *Service) Close() error { func (s *Service) Close() error {
@ -77,7 +84,8 @@ func (s *Service) Close() error {
for _, out := range s.outbounds { for _, out := range s.outbounds {
common.Close(out) common.Close(out)
} }
s.logger.Close() return common.Close(
s.router.Close() s.router,
return nil s.logger,
)
} }