From 7c57eb70e8cae9b29cdb06bbc590d0f03fd57b70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 2 Jul 2022 14:07:50 +0800 Subject: [PATCH] Inbound rule support --- adapter/inbound.go | 8 +-- adapter/inbound/builder.go | 35 ++++++++++ adapter/inbound/default.go | 47 +++++++------ adapter/inbound/direct.go | 20 +++--- adapter/inbound/http.go | 4 +- adapter/inbound/mixed.go | 4 +- adapter/inbound/shadowsocks.go | 11 +-- adapter/inbound/socks.go | 4 +- adapter/outbound/builder.go | 27 ++++++++ adapter/outbound/default.go | 8 +-- adapter/outbound/direct.go | 16 ++--- adapter/outbound/shadowsocks.go | 10 +-- adapter/route/router.go | 73 ++++++++++++++++---- adapter/route/rule.go | 55 +++++++++++++++ adapter/route/rule_inbound.go | 35 ++++++++++ adapter/router.go | 7 ++ cmd/sing-box/main.go | 9 +-- config/address.go | 50 -------------- config/config.go | 12 ---- config/route.go | 14 ---- constant/rule.go | 6 ++ go.mod | 2 + go.sum | 7 +- log/log.go | 23 ++++++- log/logrus.go | 65 ++++++++++++++++++ log/{hook.go => logrus_hook.go} | 8 +-- log/nop.go | 50 ++++++++++++++ option/address.go | 27 ++++++++ option/config.go | 14 ++++ {config => option}/inbound.go | 2 +- {config => option}/network.go | 2 +- {config => option}/outbound.go | 19 ++++-- option/route.go | 80 ++++++++++++++++++++++ service.go | 115 +++++++++++--------------------- 34 files changed, 622 insertions(+), 247 deletions(-) create mode 100644 adapter/inbound/builder.go create mode 100644 adapter/outbound/builder.go create mode 100644 adapter/route/rule.go create mode 100644 adapter/route/rule_inbound.go delete mode 100644 config/address.go delete mode 100644 config/config.go delete mode 100644 config/route.go create mode 100644 constant/rule.go create mode 100644 log/logrus.go rename log/{hook.go => logrus_hook.go} (84%) create mode 100644 log/nop.go create mode 100644 option/address.go create mode 100644 option/config.go rename {config => option}/inbound.go (99%) rename {config => option}/network.go (97%) rename {config => option}/outbound.go (88%) create mode 100644 option/route.go diff --git a/adapter/inbound.go b/adapter/inbound.go index d7ddd6eb..66b40579 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -1,8 +1,6 @@ package adapter import ( - "net/netip" - M "github.com/sagernet/sing/common/metadata" ) @@ -13,10 +11,10 @@ type Inbound interface { } type InboundContext struct { - Source netip.AddrPort - Destination M.Socksaddr Inbound string Network string - Protocol string + Source M.Socksaddr + Destination M.Socksaddr Domain string + Protocol string } diff --git a/adapter/inbound/builder.go b/adapter/inbound/builder.go new file mode 100644 index 00000000..f1486673 --- /dev/null +++ b/adapter/inbound/builder.go @@ -0,0 +1,35 @@ +package inbound + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + 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) { + var tag string + if options.Tag != "" { + tag = options.Tag + } else { + tag = F.ToString(index) + } + inboundLogger := logger.WithPrefix(F.ToString("inbound/", options.Type, "[", tag, "]: ")) + switch options.Type { + case C.TypeDirect: + return NewDirect(ctx, router, inboundLogger, options.Tag, options.DirectOptions), nil + case C.TypeSocks: + return NewSocks(ctx, router, inboundLogger, options.Tag, options.SocksOptions), nil + case C.TypeHTTP: + return NewHTTP(ctx, router, inboundLogger, options.Tag, options.HTTPOptions), nil + case C.TypeMixed: + return NewMixed(ctx, router, inboundLogger, options.Tag, options.MixedOptions), nil + case C.TypeShadowsocks: + return NewShadowsocks(ctx, router, inboundLogger, options.Tag, options.ShadowsocksOptions) + default: + panic(F.ToString("unknown inbound type: ", options.Type)) + } +} diff --git a/adapter/inbound/default.go b/adapter/inbound/default.go index f7aff602..2f21c65b 100644 --- a/adapter/inbound/default.go +++ b/adapter/inbound/default.go @@ -10,9 +10,9 @@ import ( "github.com/database64128/tfo-go" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" @@ -29,7 +29,7 @@ type myInboundAdapter struct { router adapter.Router logger log.Logger tag string - listenOptions config.ListenOptions + listenOptions option.ListenOptions connHandler adapter.ConnectionHandler packetHandler adapter.PacketHandler packetUpstream any @@ -101,7 +101,7 @@ func (a *myInboundAdapter) Close() error { } func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamHandler(metadata, a.newConnection, a.newPacketConnection, a) + return adapter.NewUpstreamHandler(metadata, a.newConnection, a.streamPacketConnection, a) } func (a *myInboundAdapter) upstreamContextHandler() adapter.UpstreamHandlerAdapter { @@ -113,7 +113,14 @@ func (a *myInboundAdapter) newConnection(ctx context.Context, conn net.Conn, met return a.router.RouteConnection(ctx, conn, metadata) } +func (a *myInboundAdapter) streamPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + a.logger.WithContext(ctx).Info("inbound packet connection to ", metadata.Destination) + return a.router.RoutePacketConnection(ctx, conn, metadata) +} + func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + ctx = log.ContextWithID(ctx) + a.logger.WithContext(ctx).Info("inbound packet connection from ", metadata.Source) a.logger.WithContext(ctx).Info("inbound packet connection to ", metadata.Destination) return a.router.RoutePacketConnection(ctx, conn, metadata) } @@ -125,16 +132,16 @@ func (a *myInboundAdapter) loopTCPIn() { if err != nil { return } - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.Source = M.AddrPortFromNet(conn.RemoteAddr()) go func() { - metadata.Network = "tcp" ctx := log.ContextWithID(a.ctx) - a.logger.WithContext(ctx).Info("inbound connection from ", conn.RemoteAddr()) + var metadata adapter.InboundContext + metadata.Inbound = a.tag + metadata.Network = "tcp" + metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()) + a.logger.WithContext(ctx).Info("inbound connection from ", metadata.Source) hErr := a.connHandler.NewConnection(ctx, conn, metadata) if hErr != nil { - a.NewError(ctx, E.Cause(hErr, "process connection from ", conn.RemoteAddr())) + a.NewError(ctx, E.Cause(hErr, "process connection from ", metadata.Source)) } }() } @@ -149,9 +156,6 @@ func (a *myInboundAdapter) loopUDPIn() { buffer.IncRef() defer buffer.DecRef() packetService := (*myInboundPacketAdapter)(a) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.Network = "udp" for { buffer.Reset() n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) @@ -159,10 +163,13 @@ func (a *myInboundAdapter) loopUDPIn() { return } buffer.Truncate(n) - metadata.Source = addr + var metadata adapter.InboundContext + metadata.Inbound = a.tag + metadata.Network = "udp" + metadata.Source = M.SocksaddrFromNetIP(addr) err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) if err != nil { - a.newError(E.Cause(err, "process packet from ", addr)) + a.newError(E.Cause(err, "process packet from ", metadata.Source)) } } } @@ -170,9 +177,6 @@ func (a *myInboundAdapter) loopUDPIn() { func (a *myInboundAdapter) loopUDPInThreadSafe() { defer close(a.packetOutboundClosed) packetService := (*myInboundPacketAdapter)(a) - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.Network = "udp" for { buffer := buf.NewPacket() n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) @@ -180,11 +184,14 @@ func (a *myInboundAdapter) loopUDPInThreadSafe() { return } buffer.Truncate(n) - metadata.Source = addr + var metadata adapter.InboundContext + metadata.Inbound = a.tag + metadata.Network = "udp" + metadata.Source = M.SocksaddrFromNetIP(addr) err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) if err != nil { buffer.Release() - a.newError(E.Cause(err, "process packet from ", addr)) + a.newError(E.Cause(err, "process packet from ", metadata.Source)) } } } @@ -212,7 +219,7 @@ func (a *myInboundAdapter) loopUDPOut() { } func (a *myInboundAdapter) newError(err error) { - a.logger.Warn(err) + a.logger.Error(err) } func (a *myInboundAdapter) NewError(ctx context.Context, err error) { diff --git a/adapter/inbound/direct.go b/adapter/inbound/direct.go index 3560f7a7..8429d606 100644 --- a/adapter/inbound/direct.go +++ b/adapter/inbound/direct.go @@ -6,9 +6,9 @@ import ( "net/netip" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/buf" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -24,7 +24,7 @@ type Direct struct { overrideDestination M.Socksaddr } -func NewDirect(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.DirectInboundOptions) *Direct { +func NewDirect(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *option.DirectInboundOptions) *Direct { inbound := &Direct{ myInboundAdapter: myInboundAdapter{ protocol: C.TypeDirect, @@ -54,13 +54,13 @@ func NewDirect(ctx context.Context, router adapter.Router, logger log.Logger, ta func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { switch d.overrideOption { - case 0: - metadata.Destination = d.overrideDestination case 1: + metadata.Destination = d.overrideDestination + case 2: destination := d.overrideDestination destination.Port = metadata.Destination.Port metadata.Destination = destination - case 2: + case 3: metadata.Destination.Port = d.overrideDestination.Port } d.logger.WithContext(ctx).Info("inbound connection to ", metadata.Destination) @@ -69,18 +69,18 @@ func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adap func (d *Direct) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { switch d.overrideOption { - case 0: - metadata.Destination = d.overrideDestination case 1: + metadata.Destination = d.overrideDestination + case 2: destination := d.overrideDestination destination.Port = metadata.Destination.Port metadata.Destination = destination - case 2: + case 3: metadata.Destination.Port = d.overrideDestination.Port } var upstreamMetadata M.Metadata - upstreamMetadata.Source = M.SocksaddrFromNetIP(metadata.Source) + upstreamMetadata.Source = metadata.Source upstreamMetadata.Destination = metadata.Destination - d.udpNat.NewPacketDirect(&adapter.MetadataContext{Context: ctx, Metadata: metadata}, metadata.Source, conn, buffer, upstreamMetadata) + d.udpNat.NewPacketDirect(&adapter.MetadataContext{Context: log.ContextWithID(ctx), Metadata: metadata}, metadata.Source.AddrPort(), conn, buffer, upstreamMetadata) return nil } diff --git a/adapter/inbound/http.go b/adapter/inbound/http.go index d0060b59..da6f983a 100644 --- a/adapter/inbound/http.go +++ b/adapter/inbound/http.go @@ -6,9 +6,9 @@ import ( "net" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/auth" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/protocol/http" @@ -21,7 +21,7 @@ type HTTP struct { authenticator auth.Authenticator } -func NewHTTP(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.SimpleInboundOptions) *HTTP { +func NewHTTP(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *option.SimpleInboundOptions) *HTTP { inbound := &HTTP{ myInboundAdapter{ protocol: C.TypeHTTP, diff --git a/adapter/inbound/mixed.go b/adapter/inbound/mixed.go index 436701c1..e064a9bf 100644 --- a/adapter/inbound/mixed.go +++ b/adapter/inbound/mixed.go @@ -6,9 +6,9 @@ import ( "net" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -27,7 +27,7 @@ type Mixed struct { authenticator auth.Authenticator } -func NewMixed(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.SimpleInboundOptions) *Mixed { +func NewMixed(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *option.SimpleInboundOptions) *Mixed { inbound := &Mixed{ myInboundAdapter{ protocol: C.TypeMixed, diff --git a/adapter/inbound/shadowsocks.go b/adapter/inbound/shadowsocks.go index 36d745c2..a9b0238b 100644 --- a/adapter/inbound/shadowsocks.go +++ b/adapter/inbound/shadowsocks.go @@ -5,9 +5,9 @@ import ( "net" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks" "github.com/sagernet/sing-shadowsocks/shadowaead" "github.com/sagernet/sing-shadowsocks/shadowaead_2022" @@ -25,7 +25,7 @@ type Shadowsocks struct { service shadowsocks.Service } -func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.ShadowsocksInboundOptions) (*Shadowsocks, error) { +func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *option.ShadowsocksInboundOptions) (*Shadowsocks, error) { inbound := &Shadowsocks{ myInboundAdapter: myInboundAdapter{ protocol: C.TypeShadowsocks, @@ -39,7 +39,6 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logge } inbound.connHandler = inbound inbound.packetHandler = inbound - inbound.packetUpstream = true var udpTimeout int64 if options.UDPTimeout != 0 { udpTimeout = options.UDPTimeout @@ -57,17 +56,19 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logge default: err = E.New("shadowsocks: unsupported method: ", options.Method) } + inbound.packetUpstream = inbound.service return inbound, err } func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { return h.service.NewConnection(&adapter.MetadataContext{Context: ctx, Metadata: metadata}, conn, M.Metadata{ - Source: M.SocksaddrFromNetIP(metadata.Source), + Source: metadata.Source, }) } func (h *Shadowsocks) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { + h.logger.Trace("inbound packet from ", metadata.Source) return h.service.NewPacket(&adapter.MetadataContext{Context: ctx, Metadata: metadata}, conn, buffer, M.Metadata{ - Source: M.SocksaddrFromNetIP(metadata.Source), + Source: metadata.Source, }) } diff --git a/adapter/inbound/socks.go b/adapter/inbound/socks.go index b25bd1a9..6da71b16 100644 --- a/adapter/inbound/socks.go +++ b/adapter/inbound/socks.go @@ -5,9 +5,9 @@ import ( "net" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/auth" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/protocol/socks" @@ -20,7 +20,7 @@ type Socks struct { authenticator auth.Authenticator } -func NewSocks(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.SimpleInboundOptions) *Socks { +func NewSocks(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *option.SimpleInboundOptions) *Socks { inbound := &Socks{ myInboundAdapter{ protocol: C.TypeSocks, diff --git a/adapter/outbound/builder.go b/adapter/outbound/builder.go new file mode 100644 index 00000000..c69db47e --- /dev/null +++ b/adapter/outbound/builder.go @@ -0,0 +1,27 @@ +package outbound + +import ( + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + F "github.com/sagernet/sing/common/format" +) + +func New(router adapter.Router, logger log.Logger, index int, options option.Outbound) (adapter.Outbound, error) { + var tag string + if options.Tag != "" { + tag = options.Tag + } else { + tag = F.ToString(index) + } + outboundLogger := logger.WithPrefix(F.ToString("outbound/", options.Type, "[", tag, "]: ")) + switch options.Type { + case C.TypeDirect: + return NewDirect(router, outboundLogger, options.Tag, options.DirectOptions), nil + case C.TypeShadowsocks: + return NewShadowsocks(router, outboundLogger, options.Tag, options.ShadowsocksOptions) + default: + panic(F.ToString("unknown outbound type: ", options.Type)) + } +} diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go index 497271ac..ff246521 100644 --- a/adapter/outbound/default.go +++ b/adapter/outbound/default.go @@ -9,8 +9,8 @@ import ( "github.com/database64128/tfo-go" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -48,7 +48,7 @@ func (d *defaultDialer) ListenPacket(ctx context.Context) (net.PacketConn, error return d.ListenConfig.ListenPacket(ctx, "udp", "") } -func newDialer(options config.DialerOptions) N.Dialer { +func newDialer(options option.DialerOptions) N.Dialer { var dialer net.Dialer var listener net.ListenConfig if options.BindInterface != "" { @@ -70,13 +70,13 @@ func newDialer(options config.DialerOptions) N.Dialer { type lazyDialer struct { router adapter.Router - options config.DialerOptions + options option.DialerOptions dialer N.Dialer initOnce sync.Once initErr error } -func NewDialer(router adapter.Router, options config.DialerOptions) N.Dialer { +func NewDialer(router adapter.Router, options option.DialerOptions) N.Dialer { if options.Detour == "" { return newDialer(options) } diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index c4ceefe9..09ab628f 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -5,9 +5,9 @@ import ( "net" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -21,7 +21,7 @@ type Direct struct { overrideDestination M.Socksaddr } -func NewDirect(router adapter.Router, logger log.Logger, tag string, options *config.DirectOutboundOptions) *Direct { +func NewDirect(router adapter.Router, logger log.Logger, tag string, options *option.DirectOutboundOptions) *Direct { outbound := &Direct{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeDirect, @@ -45,26 +45,26 @@ func NewDirect(router adapter.Router, logger log.Logger, tag string, options *co func (d *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch d.overrideOption { - case 0: - destination = d.overrideDestination case 1: + destination = d.overrideDestination + case 2: newDestination := d.overrideDestination newDestination.Port = destination.Port destination = newDestination - case 2: + case 3: destination.Port = d.overrideDestination.Port } switch network { case C.NetworkTCP: - d.logger.WithContext(ctx).Debug("outbound connection to ", destination) + d.logger.WithContext(ctx).Info("outbound connection to ", destination) case C.NetworkUDP: - d.logger.WithContext(ctx).Debug("outbound packet connection to ", destination) + d.logger.WithContext(ctx).Info("outbound packet connection to ", destination) } return d.dialer.DialContext(ctx, network, destination) } func (d *Direct) ListenPacket(ctx context.Context) (net.PacketConn, error) { - d.logger.WithContext(ctx).Debug("outbound packet connection") + d.logger.WithContext(ctx).Info("outbound packet connection") return d.dialer.ListenPacket(ctx) } diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 6414005c..5a552eed 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -5,9 +5,9 @@ import ( "net" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-shadowsocks" "github.com/sagernet/sing-shadowsocks/shadowimpl" "github.com/sagernet/sing/common/bufio" @@ -24,7 +24,7 @@ type Shadowsocks struct { serverAddr M.Socksaddr } -func NewShadowsocks(router adapter.Router, logger log.Logger, tag string, options *config.ShadowsocksOutboundOptions) (*Shadowsocks, error) { +func NewShadowsocks(router adapter.Router, logger log.Logger, tag string, options *option.ShadowsocksOutboundOptions) (*Shadowsocks, error) { outbound := &Shadowsocks{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeDirect, @@ -66,14 +66,14 @@ func (o *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn func (o *Shadowsocks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch network { case C.NetworkTCP: - o.logger.WithContext(ctx).Debug("outbound connection to ", destination) + o.logger.WithContext(ctx).Info("outbound connection to ", destination) outConn, err := o.dialer.DialContext(ctx, "tcp", o.serverAddr) if err != nil { return nil, err } return o.method.DialEarlyConn(outConn, destination), nil case C.NetworkUDP: - o.logger.WithContext(ctx).Debug("outbound packet connection to ", destination) + o.logger.WithContext(ctx).Info("outbound packet connection to ", destination) outConn, err := o.dialer.DialContext(ctx, "udp", o.serverAddr) if err != nil { return nil, err @@ -85,7 +85,7 @@ func (o *Shadowsocks) DialContext(ctx context.Context, network string, destinati } func (o *Shadowsocks) ListenPacket(ctx context.Context) (net.PacketConn, error) { - o.logger.WithContext(ctx).Debug("outbound packet connection to ", o.serverAddr) + o.logger.WithContext(ctx).Info("outbound packet connection to ", o.serverAddr) outConn, err := o.dialer.ListenPacket(ctx) if err != nil { return nil, err diff --git a/adapter/route/router.go b/adapter/route/router.go index 157db37f..2993539f 100644 --- a/adapter/route/router.go +++ b/adapter/route/router.go @@ -4,8 +4,12 @@ import ( "context" "net" + "github.com/oschwald/geoip2-golang" "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" N "github.com/sagernet/sing/common/network" ) @@ -15,24 +19,18 @@ type Router struct { logger log.Logger defaultOutbound adapter.Outbound outboundByTag map[string]adapter.Outbound + + rules []adapter.Rule + geoReader *geoip2.Reader } func NewRouter(logger log.Logger) *Router { return &Router{ - logger: logger, + logger: logger.WithPrefix("router: "), outboundByTag: make(map[string]adapter.Outbound), } } -func (r *Router) AddOutbound(outbound adapter.Outbound) { - if outbound.Tag() != "" { - r.outboundByTag[outbound.Tag()] = outbound - } - if r.defaultOutbound == nil { - r.defaultOutbound = outbound - } -} - func (r *Router) DefaultOutbound() adapter.Outbound { if r.defaultOutbound == nil { panic("missing default outbound") @@ -46,13 +44,60 @@ func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { } func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - r.logger.WithContext(ctx).Debug("no match") - r.logger.WithContext(ctx).Debug("route connection to default outbound") + for _, rule := range r.rules { + if rule.Match(metadata) { + r.logger.WithContext(ctx).Info("match ", rule.String()) + if outbound, loaded := r.Outbound(rule.Outbound()); loaded { + return outbound.NewConnection(ctx, conn, metadata.Destination) + } + r.logger.WithContext(ctx).Error("outbound ", rule.Outbound(), " not found") + } + } + r.logger.WithContext(ctx).Info("no match => ", r.defaultOutbound.Tag()) return r.defaultOutbound.NewConnection(ctx, conn, metadata.Destination) } func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - r.logger.WithContext(ctx).Debug("no match") - r.logger.WithContext(ctx).Debug("route packet connection to default outbound") + for _, rule := range r.rules { + if rule.Match(metadata) { + r.logger.WithContext(ctx).Info("match ", rule.String()) + if outbound, loaded := r.Outbound(rule.Outbound()); loaded { + return outbound.NewPacketConnection(ctx, conn, metadata.Destination) + } + r.logger.WithContext(ctx).Error("outbound ", rule.Outbound(), " not found") + } + } + r.logger.WithContext(ctx).Info("no match => ", r.defaultOutbound.Tag()) return r.defaultOutbound.NewPacketConnection(ctx, conn, metadata.Destination) } + +func (r *Router) Close() error { + return common.Close( + common.PtrOrNil(r.geoReader), + ) +} + +func (r *Router) UpdateOutbounds(outbounds []adapter.Outbound) { + var defaultOutbound adapter.Outbound + outboundByTag := make(map[string]adapter.Outbound) + if len(outbounds) > 0 { + defaultOutbound = outbounds[0] + } + for _, outbound := range outbounds { + outboundByTag[outbound.Tag()] = outbound + } + r.defaultOutbound = defaultOutbound + r.outboundByTag = outboundByTag +} + +func (r *Router) UpdateRules(options []option.Rule) error { + rules := make([]adapter.Rule, 0, len(options)) + for i, rule := range options { + switch rule.Type { + case "", C.RuleTypeDefault: + rules = append(rules, NewDefaultRule(i, rule.DefaultOptions)) + } + } + r.rules = rules + return nil +} diff --git a/adapter/route/rule.go b/adapter/route/rule.go new file mode 100644 index 00000000..4a5c055a --- /dev/null +++ b/adapter/route/rule.go @@ -0,0 +1,55 @@ +package route + +import ( + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/option" + F "github.com/sagernet/sing/common/format" +) + +var _ adapter.Rule = (*DefaultRule)(nil) + +type DefaultRule struct { + index int + outbound string + items []RuleItem +} + +type RuleItem interface { + Match(metadata adapter.InboundContext) bool + String() string +} + +func NewDefaultRule(index int, options option.DefaultRule) *DefaultRule { + rule := &DefaultRule{ + index: index, + outbound: options.Outbound, + } + if len(options.Inbound) > 0 { + rule.items = append(rule.items, NewInboundRule(options.Inbound)) + } + return rule +} + +func (r *DefaultRule) Match(metadata adapter.InboundContext) bool { + for _, item := range r.items { + if item.Match(metadata) { + return true + } + } + return false +} + +func (r *DefaultRule) Outbound() string { + return r.outbound +} + +func (r *DefaultRule) String() string { + var description string + description = F.ToString("[", r.index, "]") + for _, item := range r.items { + description += " " + description += item.String() + } + description += " => " + r.outbound + return description +} diff --git a/adapter/route/rule_inbound.go b/adapter/route/rule_inbound.go new file mode 100644 index 00000000..ed6d669b --- /dev/null +++ b/adapter/route/rule_inbound.go @@ -0,0 +1,35 @@ +package route + +import ( + "strings" + + "github.com/sagernet/sing-box/adapter" + F "github.com/sagernet/sing/common/format" +) + +var _ RuleItem = (*InboundRule)(nil) + +type InboundRule struct { + inbounds []string + inboundMap map[string]bool +} + +func NewInboundRule(inbounds []string) RuleItem { + rule := &InboundRule{inbounds, make(map[string]bool)} + for _, inbound := range inbounds { + rule.inboundMap[inbound] = true + } + return rule +} + +func (r *InboundRule) Match(metadata adapter.InboundContext) bool { + return r.inboundMap[metadata.Inbound] +} + +func (r *InboundRule) String() string { + if len(r.inbounds) == 1 { + return F.ToString("inbound=", r.inbounds[0]) + } else { + return F.ToString("inbound=[", strings.Join(r.inbounds, " "), "]") + } +} diff --git a/adapter/router.go b/adapter/router.go index 7d352439..4fb385f1 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -12,4 +12,11 @@ type Router interface { Outbound(tag string) (Outbound, bool) RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error + Close() error +} + +type Rule interface { + Match(metadata InboundContext) bool + Outbound() string + String() string } diff --git a/cmd/sing-box/main.go b/cmd/sing-box/main.go index df4ec4a2..a3e22a3d 100644 --- a/cmd/sing-box/main.go +++ b/cmd/sing-box/main.go @@ -8,7 +8,7 @@ import ( "syscall" "github.com/sagernet/sing-box" - "github.com/sagernet/sing-box/config" + "github.com/sagernet/sing-box/option" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -36,13 +36,14 @@ func run(cmd *cobra.Command, args []string) { if err != nil { logrus.Fatal("read config: ", err) } - var boxConfig config.Config - err = json.Unmarshal(configContent, &boxConfig) + var options option.Options + err = json.Unmarshal(configContent, &options) if err != nil { logrus.Fatal("parse config: ", err) } + ctx, cancel := context.WithCancel(context.Background()) - service, err := box.NewService(ctx, &boxConfig) + service, err := box.NewService(ctx, &options) if err != nil { logrus.Fatal("create service: ", err) } diff --git a/config/address.go b/config/address.go deleted file mode 100644 index b06a2091..00000000 --- a/config/address.go +++ /dev/null @@ -1,50 +0,0 @@ -package config - -import ( - "encoding/json" - "net/netip" - - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" -) - -type ListenAddress netip.Addr - -func (a *ListenAddress) MarshalJSON() ([]byte, error) { - value := netip.Addr(*a).String() - return json.Marshal(value) -} - -func (a *ListenAddress) UnmarshalJSON(bytes []byte) error { - var value string - err := json.Unmarshal(bytes, &value) - if err != nil { - return err - } - addr, err := netip.ParseAddr(value) - if err != nil { - return err - } - *a = ListenAddress(addr) - return nil -} - -type ServerAddress M.Socksaddr - -func (a *ServerAddress) MarshalJSON() ([]byte, error) { - value := M.Socksaddr(*a).String() - return json.Marshal(value) -} - -func (a *ServerAddress) UnmarshalJSON(bytes []byte) error { - var value string - err := json.Unmarshal(bytes, &value) - if err != nil { - return err - } - if value == "" { - return E.New("empty server address") - } - *a = ServerAddress(M.ParseSocksaddr(value)) - return nil -} diff --git a/config/config.go b/config/config.go deleted file mode 100644 index c412c86d..00000000 --- a/config/config.go +++ /dev/null @@ -1,12 +0,0 @@ -package config - -type Config struct { - Log *LogConfig `json:"log"` - Inbounds []Inbound `json:"inbounds,omitempty"` - Outbounds []Outbound `json:"outbounds,omitempty"` - Routes []Route `json:"routes,omitempty"` -} - -type LogConfig struct { - Level string `json:"level,omitempty"` -} diff --git a/config/route.go b/config/route.go deleted file mode 100644 index 1b98e5a6..00000000 --- a/config/route.go +++ /dev/null @@ -1,14 +0,0 @@ -package config - -type Route struct { - Type string `json:"type"` -} - -type SimpleRule struct { - Inbound []string `json:"inbound,omitempty"` - IPVersion []int `json:"ip_version,omitempty"` - Network []string `json:"network,omitempty"` - Protocol []string `json:"protocol,omitempty"` - Domain []string `json:"domain,omitempty"` - Outbound string `json:"outbound,omitempty"` -} diff --git a/constant/rule.go b/constant/rule.go new file mode 100644 index 00000000..fc76183a --- /dev/null +++ b/constant/rule.go @@ -0,0 +1,6 @@ +package constant + +const ( + RuleTypeDefault = "default" + RuleTypeLogical = "logical" +) diff --git a/go.mod b/go.mod index 73dd4eda..d6200818 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( github.com/database64128/tfo-go v1.0.4 github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/oschwald/geoip2-golang v1.7.0 github.com/sagernet/sing v0.0.0-20220701084654-2a0502dd664e github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 github.com/sirupsen/logrus v1.8.1 @@ -14,6 +15,7 @@ require ( require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect + github.com/oschwald/maxminddb-golang v1.9.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect diff --git a/go.sum b/go.sum index a797eb07..00faa67a 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,10 @@ github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0 github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8= +github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ= +github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y= +github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm82Cp5HyvYbt8K3zLY= 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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -23,8 +27,8 @@ github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -32,5 +36,6 @@ golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/ golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= diff --git a/log/log.go b/log/log.go index 667db290..4096d605 100644 --- a/log/log.go +++ b/log/log.go @@ -3,10 +3,27 @@ package log import ( "context" - "github.com/sirupsen/logrus" + "github.com/sagernet/sing-box/option" ) type Logger interface { - logrus.FieldLogger - WithContext(ctx context.Context) *logrus.Entry + Trace(args ...interface{}) + Debug(args ...interface{}) + Info(args ...interface{}) + Print(args ...interface{}) + Warn(args ...interface{}) + Warning(args ...interface{}) + Error(args ...interface{}) + Fatal(args ...interface{}) + Panic(args ...interface{}) + WithContext(ctx context.Context) Logger + WithPrefix(prefix string) Logger + Close() error +} + +func NewLogger(options option.LogOption) (Logger, error) { + if options.Disabled { + return NewNopLogger(), nil + } + return NewLogrusLogger(options) } diff --git a/log/logrus.go b/log/logrus.go new file mode 100644 index 00000000..254aa202 --- /dev/null +++ b/log/logrus.go @@ -0,0 +1,65 @@ +package log + +import ( + "context" + "os" + + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" + "github.com/sirupsen/logrus" +) + +var _ Logger = (*logrusLogger)(nil) + +type logrusLogger struct { + abstractLogrusLogger + output *os.File +} + +type abstractLogrusLogger interface { + logrus.Ext1FieldLogger + WithContext(ctx context.Context) *logrus.Entry +} + +func NewLogrusLogger(options option.LogOption) (*logrusLogger, error) { + logger := logrus.New() + logger.SetLevel(logrus.TraceLevel) + logger.Formatter.(*logrus.TextFormatter).ForceColors = true + logger.AddHook(new(logrusHook)) + var output *os.File + var err error + if options.Level != "" { + logger.Level, err = logrus.ParseLevel(options.Level) + if err != nil { + return nil, err + } + } + if options.Output != "" { + output, err = os.OpenFile(options.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return nil, E.Extend(err, "open log output") + } + logger.SetOutput(output) + } + return &logrusLogger{logger, output}, nil +} + +func (l *logrusLogger) WithContext(ctx context.Context) Logger { + return &logrusLogger{l.abstractLogrusLogger.WithContext(ctx), nil} +} + +func (l *logrusLogger) WithPrefix(prefix string) Logger { + if entry, isEntry := l.abstractLogrusLogger.(*logrus.Entry); isEntry { + loadedPrefix := entry.Data["prefix"] + if loadedPrefix != "" { + prefix = F.ToString(loadedPrefix, prefix) + } + } + return &logrusLogger{l.WithField("prefix", prefix), nil} +} + +func (l *logrusLogger) Close() error { + return common.Close(common.PtrOrNil(l.output)) +} diff --git a/log/hook.go b/log/logrus_hook.go similarity index 84% rename from log/hook.go rename to log/logrus_hook.go index b68dcc41..672ced28 100644 --- a/log/hook.go +++ b/log/logrus_hook.go @@ -6,13 +6,13 @@ import ( "github.com/sirupsen/logrus" ) -type Hook struct{} +type logrusHook struct{} -func (h *Hook) Levels() []logrus.Level { +func (h *logrusHook) Levels() []logrus.Level { return logrus.AllLevels } -func (h *Hook) Fire(entry *logrus.Entry) error { +func (h *logrusHook) Fire(entry *logrus.Entry) error { if prefix, loaded := entry.Data["prefix"]; loaded { prefixStr := prefix.(string) delete(entry.Data, "prefix") @@ -20,7 +20,7 @@ func (h *Hook) Fire(entry *logrus.Entry) error { } var idCtx *idContext if entry.Context != nil { - idCtx = entry.Context.Value(idType).(*idContext) + idCtx, _ = entry.Context.Value(idType).(*idContext) } if idCtx != nil { var color aurora.Color diff --git a/log/nop.go b/log/nop.go new file mode 100644 index 00000000..ad21fe55 --- /dev/null +++ b/log/nop.go @@ -0,0 +1,50 @@ +package log + +import "context" + +var _ Logger = (*nopLogger)(nil) + +type nopLogger struct{} + +func NewNopLogger() Logger { + return (*nopLogger)(nil) +} + +func (l *nopLogger) Trace(args ...interface{}) { +} + +func (l *nopLogger) Debug(args ...interface{}) { +} + +func (l *nopLogger) Info(args ...interface{}) { +} + +func (l *nopLogger) Print(args ...interface{}) { +} + +func (l *nopLogger) Warn(args ...interface{}) { +} + +func (l *nopLogger) Warning(args ...interface{}) { +} + +func (l *nopLogger) Error(args ...interface{}) { +} + +func (l *nopLogger) Fatal(args ...interface{}) { +} + +func (l *nopLogger) Panic(args ...interface{}) { +} + +func (l *nopLogger) WithContext(ctx context.Context) Logger { + return l +} + +func (l *nopLogger) WithPrefix(prefix string) Logger { + return l +} + +func (l *nopLogger) Close() error { + return nil +} diff --git a/option/address.go b/option/address.go new file mode 100644 index 00000000..7781482c --- /dev/null +++ b/option/address.go @@ -0,0 +1,27 @@ +package option + +import ( + "encoding/json" + "net/netip" +) + +type ListenAddress netip.Addr + +func (a *ListenAddress) MarshalJSON() ([]byte, error) { + value := netip.Addr(*a).String() + return json.Marshal(value) +} + +func (a *ListenAddress) UnmarshalJSON(bytes []byte) error { + var value string + err := json.Unmarshal(bytes, &value) + if err != nil { + return err + } + addr, err := netip.ParseAddr(value) + if err != nil { + return err + } + *a = ListenAddress(addr) + return nil +} diff --git a/option/config.go b/option/config.go new file mode 100644 index 00000000..5756387a --- /dev/null +++ b/option/config.go @@ -0,0 +1,14 @@ +package option + +type Options struct { + Log *LogOption `json:"log"` + Inbounds []Inbound `json:"inbounds,omitempty"` + Outbounds []Outbound `json:"outbounds,omitempty"` + Routes []Rule `json:"routes,omitempty"` +} + +type LogOption struct { + Disabled bool `json:"disabled,omitempty"` + Level string `json:"level,omitempty"` + Output string `json:"output,omitempty"` +} diff --git a/config/inbound.go b/option/inbound.go similarity index 99% rename from config/inbound.go rename to option/inbound.go index e0e3c55d..21afb334 100644 --- a/config/inbound.go +++ b/option/inbound.go @@ -1,4 +1,4 @@ -package config +package option import ( "encoding/json" diff --git a/config/network.go b/option/network.go similarity index 97% rename from config/network.go rename to option/network.go index 409386d3..442e640e 100644 --- a/config/network.go +++ b/option/network.go @@ -1,4 +1,4 @@ -package config +package option import ( "encoding/json" diff --git a/config/outbound.go b/option/outbound.go similarity index 88% rename from config/outbound.go rename to option/outbound.go index 91d0e354..53982285 100644 --- a/config/outbound.go +++ b/option/outbound.go @@ -1,9 +1,10 @@ -package config +package option import ( "encoding/json" E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" ) var ErrUnknownOutboundType = E.New("unknown outbound type") @@ -81,10 +82,18 @@ type DirectOutboundOptions struct { OverridePort uint16 `json:"override_port,omitempty"` } -type ShadowsocksOutboundOptions struct { - DialerOptions +type ServerOptions struct { Server string `json:"server"` ServerPort uint16 `json:"server_port"` - Method string `json:"method"` - Password string `json:"password"` +} + +func (o ServerOptions) Build() M.Socksaddr { + return M.ParseSocksaddrHostPort(o.Server, o.ServerPort) +} + +type ShadowsocksOutboundOptions struct { + DialerOptions + ServerOptions + Method string `json:"method"` + Password string `json:"password"` } diff --git a/option/route.go b/option/route.go new file mode 100644 index 00000000..21c4c019 --- /dev/null +++ b/option/route.go @@ -0,0 +1,80 @@ +package option + +import ( + "encoding/json" + + C "github.com/sagernet/sing-box/constant" + E "github.com/sagernet/sing/common/exceptions" +) + +var ErrUnknownRuleType = E.New("unknown rule type") + +type _Rule struct { + Type string `json:"type"` + DefaultOptions DefaultRule `json:"default_options,omitempty"` + LogicalOptions LogicalRule `json:"logical_options,omitempty"` +} + +type Rule _Rule + +func (r *Rule) MarshalJSON() ([]byte, error) { + var content map[string]any + switch r.Type { + case "", C.RuleTypeDefault: + return json.Marshal(r.DefaultOptions) + case C.RuleTypeLogical: + options, err := json.Marshal(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: + return nil, E.Extend(ErrUnknownRuleType, r.Type) + } +} + +func (r *Rule) UnmarshalJSON(bytes []byte) error { + err := json.Unmarshal(bytes, (*_Rule)(r)) + if err != nil { + return err + } + switch r.Type { + case "", C.RuleTypeDefault: + err = json.Unmarshal(bytes, &r.DefaultOptions) + case C.RuleTypeLogical: + err = json.Unmarshal(bytes, &r.LogicalOptions) + default: + err = E.Extend(ErrUnknownRuleType, r.Type) + } + return err +} + +type DefaultRule struct { + Inbound []string `json:"inbound,omitempty"` + IPVersion []int `json:"ip_version,omitempty"` + Network []string `json:"network,omitempty"` + Protocol []string `json:"protocol,omitempty"` + Domain []string `json:"domain,omitempty"` + DomainSuffix []string `json:"domain_suffix,omitempty"` + DomainKeyword []string `json:"domain_keyword,omitempty"` + SourceGeoIP []string `json:"source_geoip,omitempty"` + GeoIP []string `json:"geoip,omitempty"` + SourceIPCIDR []string `json:"source_ipcidr,omitempty"` + SourcePort []string `json:"source_port,omitempty"` + IPCIDR []string `json:"destination_ipcidr,omitempty"` + Port []string `json:"destination_port,omitempty"` + ProcessName []string `json:"process_name,omitempty"` + ProcessPath []string `json:"process_path,omitempty"` + Outbound string `json:"outbound,omitempty"` +} + +type LogicalRule struct { + Mode string `json:"mode"` + Rules []DefaultRule `json:"rules,omitempty"` + Outbound string `json:"outbound,omitempty"` +} diff --git a/service.go b/service.go index 881ff7e4..21112199 100644 --- a/service.go +++ b/service.go @@ -7,106 +7,69 @@ import ( "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/adapter/route" - "github.com/sagernet/sing-box/config" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - "github.com/sirupsen/logrus" ) var _ adapter.Service = (*Service)(nil) type Service struct { - logger *logrus.Logger + router adapter.Router + logger log.Logger inbounds []adapter.Inbound outbounds []adapter.Outbound - router *route.Router } -func NewService(ctx context.Context, options *config.Config) (service *Service, err error) { - logger := logrus.New() - logger.SetLevel(logrus.TraceLevel) - logger.Formatter.(*logrus.TextFormatter).ForceColors = true - logger.AddHook(new(log.Hook)) +func NewService(ctx context.Context, options *option.Options) (*Service, error) { + var logOptions option.LogOption if options.Log != nil { - if options.Log.Level != "" { - logger.Level, err = logrus.ParseLevel(options.Log.Level) - if err != nil { - return - } - } + logOptions = *options.Log } - service = &Service{ - logger: logger, - router: route.NewRouter(logrus.NewEntry(logger).WithFields(logrus.Fields{"prefix": "router: "})), + logger, err := log.NewLogger(logOptions) + if err != nil { + return nil, err } + router := route.NewRouter(logger) + var inbounds []adapter.Inbound + var outbounds []adapter.Outbound if len(options.Inbounds) > 0 { for i, inboundOptions := range options.Inbounds { - var prefix string - if inboundOptions.Tag != "" { - prefix = inboundOptions.Tag - } else { - prefix = F.ToString(i) - } - prefix = F.ToString("inbound/", inboundOptions.Type, "[", prefix, "]: ") - inboundLogger := logrus.NewEntry(logger).WithFields(logrus.Fields{"prefix": prefix}) var inboundService adapter.Inbound - switch inboundOptions.Type { - case C.TypeDirect: - inboundService = inbound.NewDirect(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.DirectOptions) - case C.TypeSocks: - inboundService = inbound.NewSocks(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.SocksOptions) - case C.TypeHTTP: - inboundService = inbound.NewHTTP(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.HTTPOptions) - case C.TypeMixed: - inboundService = inbound.NewMixed(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.MixedOptions) - case C.TypeShadowsocks: - inboundService, err = inbound.NewShadowsocks(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.ShadowsocksOptions) - default: - err = E.New("unknown inbound type: " + inboundOptions.Type) - } + inboundService, err = inbound.New(ctx, router, logger, i, inboundOptions) if err != nil { - return + return nil, err } - service.inbounds = append(service.inbounds, inboundService) + inbounds = append(inbounds, inboundService) } } for i, outboundOptions := range options.Outbounds { - var prefix string - if outboundOptions.Tag != "" { - prefix = outboundOptions.Tag - } else { - prefix = F.ToString(i) - } - prefix = F.ToString("outbound/", outboundOptions.Type, "[", prefix, "]: ") - outboundLogger := logrus.NewEntry(logger).WithFields(logrus.Fields{"prefix": prefix}) - var outboundHandler adapter.Outbound - switch outboundOptions.Type { - case C.TypeDirect: - outboundHandler = outbound.NewDirect(service.router, outboundLogger, outboundOptions.Tag, outboundOptions.DirectOptions) - case C.TypeShadowsocks: - outboundHandler, err = outbound.NewShadowsocks(service.router, outboundLogger, outboundOptions.Tag, outboundOptions.ShadowsocksOptions) - default: - err = E.New("unknown outbound type: " + outboundOptions.Type) - } + var outboundService adapter.Outbound + outboundService, err = outbound.New(router, logger, i, outboundOptions) if err != nil { - return + return nil, err } - service.outbounds = append(service.outbounds, outboundHandler) - service.router.AddOutbound(outboundHandler) + outbounds = append(outbounds, outboundService) } - if len(service.outbounds) == 0 { - service.outbounds = append(service.outbounds, outbound.NewDirect(nil, logger, "direct", &config.DirectOutboundOptions{})) - service.router.AddOutbound(service.outbounds[0]) + if len(outbounds) == 0 { + outbounds = append(outbounds, outbound.NewDirect(nil, logger, "direct", &option.DirectOutboundOptions{})) } - return + router.UpdateOutbounds(outbounds) + err = router.UpdateRules(options.Routes) + if err != nil { + return nil, err + } + return &Service{ + router: router, + logger: logger, + inbounds: inbounds, + outbounds: outbounds, + }, nil } func (s *Service) Start() error { - for _, inbound := range s.inbounds { - err := inbound.Start() + for _, in := range s.inbounds { + err := in.Start() if err != nil { return err } @@ -115,11 +78,13 @@ func (s *Service) Start() error { } func (s *Service) Close() error { - for _, inbound := range s.inbounds { - inbound.Close() + for _, in := range s.inbounds { + in.Close() } - for _, outbound := range s.outbounds { - common.Close(outbound) + for _, out := range s.outbounds { + common.Close(out) } + s.logger.Close() + s.router.Close() return nil }