瀏覽代碼

Inbound rule support

世界 3 年之前
父節點
當前提交
7c57eb70e8

+ 3 - 5
adapter/inbound.go

@@ -1,8 +1,6 @@
 package adapter
 package adapter
 
 
 import (
 import (
-	"net/netip"
-
 	M "github.com/sagernet/sing/common/metadata"
 	M "github.com/sagernet/sing/common/metadata"
 )
 )
 
 
@@ -13,10 +11,10 @@ type Inbound interface {
 }
 }
 
 
 type InboundContext struct {
 type InboundContext struct {
-	Source      netip.AddrPort
-	Destination M.Socksaddr
 	Inbound     string
 	Inbound     string
 	Network     string
 	Network     string
-	Protocol    string
+	Source      M.Socksaddr
+	Destination M.Socksaddr
 	Domain      string
 	Domain      string
+	Protocol    string
 }
 }

+ 35 - 0
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))
+	}
+}

+ 27 - 20
adapter/inbound/default.go

@@ -10,9 +10,9 @@ import (
 
 
 	"github.com/database64128/tfo-go"
 	"github.com/database64128/tfo-go"
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/config"
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/buf"
 	"github.com/sagernet/sing/common/buf"
 	E "github.com/sagernet/sing/common/exceptions"
 	E "github.com/sagernet/sing/common/exceptions"
@@ -29,7 +29,7 @@ type myInboundAdapter struct {
 	router         adapter.Router
 	router         adapter.Router
 	logger         log.Logger
 	logger         log.Logger
 	tag            string
 	tag            string
-	listenOptions  config.ListenOptions
+	listenOptions  option.ListenOptions
 	connHandler    adapter.ConnectionHandler
 	connHandler    adapter.ConnectionHandler
 	packetHandler  adapter.PacketHandler
 	packetHandler  adapter.PacketHandler
 	packetUpstream any
 	packetUpstream any
@@ -101,7 +101,7 @@ func (a *myInboundAdapter) Close() error {
 }
 }
 
 
 func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter {
 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 {
 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)
 	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 {
 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)
 	a.logger.WithContext(ctx).Info("inbound packet connection to ", metadata.Destination)
 	return a.router.RoutePacketConnection(ctx, conn, metadata)
 	return a.router.RoutePacketConnection(ctx, conn, metadata)
 }
 }
@@ -125,16 +132,16 @@ func (a *myInboundAdapter) loopTCPIn() {
 		if err != nil {
 		if err != nil {
 			return
 			return
 		}
 		}
-		var metadata adapter.InboundContext
-		metadata.Inbound = a.tag
-		metadata.Source = M.AddrPortFromNet(conn.RemoteAddr())
 		go func() {
 		go func() {
-			metadata.Network = "tcp"
 			ctx := log.ContextWithID(a.ctx)
 			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)
 			hErr := a.connHandler.NewConnection(ctx, conn, metadata)
 			if hErr != nil {
 			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()
 	buffer.IncRef()
 	defer buffer.DecRef()
 	defer buffer.DecRef()
 	packetService := (*myInboundPacketAdapter)(a)
 	packetService := (*myInboundPacketAdapter)(a)
-	var metadata adapter.InboundContext
-	metadata.Inbound = a.tag
-	metadata.Network = "udp"
 	for {
 	for {
 		buffer.Reset()
 		buffer.Reset()
 		n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
 		n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
@@ -159,10 +163,13 @@ func (a *myInboundAdapter) loopUDPIn() {
 			return
 			return
 		}
 		}
 		buffer.Truncate(n)
 		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)
 		err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
 		if err != nil {
 		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() {
 func (a *myInboundAdapter) loopUDPInThreadSafe() {
 	defer close(a.packetOutboundClosed)
 	defer close(a.packetOutboundClosed)
 	packetService := (*myInboundPacketAdapter)(a)
 	packetService := (*myInboundPacketAdapter)(a)
-	var metadata adapter.InboundContext
-	metadata.Inbound = a.tag
-	metadata.Network = "udp"
 	for {
 	for {
 		buffer := buf.NewPacket()
 		buffer := buf.NewPacket()
 		n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
 		n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
@@ -180,11 +184,14 @@ func (a *myInboundAdapter) loopUDPInThreadSafe() {
 			return
 			return
 		}
 		}
 		buffer.Truncate(n)
 		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)
 		err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
 		if err != nil {
 		if err != nil {
 			buffer.Release()
 			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) {
 func (a *myInboundAdapter) newError(err error) {
-	a.logger.Warn(err)
+	a.logger.Error(err)
 }
 }
 
 
 func (a *myInboundAdapter) NewError(ctx context.Context, err error) {
 func (a *myInboundAdapter) NewError(ctx context.Context, err error) {

+ 10 - 10
adapter/inbound/direct.go

@@ -6,9 +6,9 @@ import (
 	"net/netip"
 	"net/netip"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/config"
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common/buf"
 	"github.com/sagernet/sing/common/buf"
 	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"
@@ -24,7 +24,7 @@ type Direct struct {
 	overrideDestination M.Socksaddr
 	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{
 	inbound := &Direct{
 		myInboundAdapter: myInboundAdapter{
 		myInboundAdapter: myInboundAdapter{
 			protocol:      C.TypeDirect,
 			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 {
 func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
 	switch d.overrideOption {
 	switch d.overrideOption {
-	case 0:
-		metadata.Destination = d.overrideDestination
 	case 1:
 	case 1:
+		metadata.Destination = d.overrideDestination
+	case 2:
 		destination := d.overrideDestination
 		destination := d.overrideDestination
 		destination.Port = metadata.Destination.Port
 		destination.Port = metadata.Destination.Port
 		metadata.Destination = destination
 		metadata.Destination = destination
-	case 2:
+	case 3:
 		metadata.Destination.Port = d.overrideDestination.Port
 		metadata.Destination.Port = d.overrideDestination.Port
 	}
 	}
 	d.logger.WithContext(ctx).Info("inbound connection to ", metadata.Destination)
 	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 {
 func (d *Direct) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error {
 	switch d.overrideOption {
 	switch d.overrideOption {
-	case 0:
-		metadata.Destination = d.overrideDestination
 	case 1:
 	case 1:
+		metadata.Destination = d.overrideDestination
+	case 2:
 		destination := d.overrideDestination
 		destination := d.overrideDestination
 		destination.Port = metadata.Destination.Port
 		destination.Port = metadata.Destination.Port
 		metadata.Destination = destination
 		metadata.Destination = destination
-	case 2:
+	case 3:
 		metadata.Destination.Port = d.overrideDestination.Port
 		metadata.Destination.Port = d.overrideDestination.Port
 	}
 	}
 	var upstreamMetadata M.Metadata
 	var upstreamMetadata M.Metadata
-	upstreamMetadata.Source = M.SocksaddrFromNetIP(metadata.Source)
+	upstreamMetadata.Source = metadata.Source
 	upstreamMetadata.Destination = metadata.Destination
 	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
 	return nil
 }
 }

+ 2 - 2
adapter/inbound/http.go

@@ -6,9 +6,9 @@ import (
 	"net"
 	"net"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/config"
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common/auth"
 	"github.com/sagernet/sing/common/auth"
 	M "github.com/sagernet/sing/common/metadata"
 	M "github.com/sagernet/sing/common/metadata"
 	"github.com/sagernet/sing/protocol/http"
 	"github.com/sagernet/sing/protocol/http"
@@ -21,7 +21,7 @@ type HTTP struct {
 	authenticator auth.Authenticator
 	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{
 	inbound := &HTTP{
 		myInboundAdapter{
 		myInboundAdapter{
 			protocol:      C.TypeHTTP,
 			protocol:      C.TypeHTTP,

+ 2 - 2
adapter/inbound/mixed.go

@@ -6,9 +6,9 @@ import (
 	"net"
 	"net"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/config"
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common/auth"
 	"github.com/sagernet/sing/common/auth"
 	"github.com/sagernet/sing/common/buf"
 	"github.com/sagernet/sing/common/buf"
 	"github.com/sagernet/sing/common/bufio"
 	"github.com/sagernet/sing/common/bufio"
@@ -27,7 +27,7 @@ type Mixed struct {
 	authenticator auth.Authenticator
 	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{
 	inbound := &Mixed{
 		myInboundAdapter{
 		myInboundAdapter{
 			protocol:      C.TypeMixed,
 			protocol:      C.TypeMixed,

+ 6 - 5
adapter/inbound/shadowsocks.go

@@ -5,9 +5,9 @@ import (
 	"net"
 	"net"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/config"
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-shadowsocks"
 	"github.com/sagernet/sing-shadowsocks"
 	"github.com/sagernet/sing-shadowsocks/shadowaead"
 	"github.com/sagernet/sing-shadowsocks/shadowaead"
 	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
 	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
@@ -25,7 +25,7 @@ type Shadowsocks struct {
 	service shadowsocks.Service
 	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{
 	inbound := &Shadowsocks{
 		myInboundAdapter: myInboundAdapter{
 		myInboundAdapter: myInboundAdapter{
 			protocol:      C.TypeShadowsocks,
 			protocol:      C.TypeShadowsocks,
@@ -39,7 +39,6 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logge
 	}
 	}
 	inbound.connHandler = inbound
 	inbound.connHandler = inbound
 	inbound.packetHandler = inbound
 	inbound.packetHandler = inbound
-	inbound.packetUpstream = true
 	var udpTimeout int64
 	var udpTimeout int64
 	if options.UDPTimeout != 0 {
 	if options.UDPTimeout != 0 {
 		udpTimeout = options.UDPTimeout
 		udpTimeout = options.UDPTimeout
@@ -57,17 +56,19 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logge
 	default:
 	default:
 		err = E.New("shadowsocks: unsupported method: ", options.Method)
 		err = E.New("shadowsocks: unsupported method: ", options.Method)
 	}
 	}
+	inbound.packetUpstream = inbound.service
 	return inbound, err
 	return inbound, err
 }
 }
 
 
 func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
 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{
 	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 {
 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{
 	return h.service.NewPacket(&adapter.MetadataContext{Context: ctx, Metadata: metadata}, conn, buffer, M.Metadata{
-		Source: M.SocksaddrFromNetIP(metadata.Source),
+		Source: metadata.Source,
 	})
 	})
 }
 }

+ 2 - 2
adapter/inbound/socks.go

@@ -5,9 +5,9 @@ import (
 	"net"
 	"net"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/config"
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common/auth"
 	"github.com/sagernet/sing/common/auth"
 	M "github.com/sagernet/sing/common/metadata"
 	M "github.com/sagernet/sing/common/metadata"
 	"github.com/sagernet/sing/protocol/socks"
 	"github.com/sagernet/sing/protocol/socks"
@@ -20,7 +20,7 @@ type Socks struct {
 	authenticator auth.Authenticator
 	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{
 	inbound := &Socks{
 		myInboundAdapter{
 		myInboundAdapter{
 			protocol:      C.TypeSocks,
 			protocol:      C.TypeSocks,

+ 27 - 0
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))
+	}
+}

+ 4 - 4
adapter/outbound/default.go

@@ -9,8 +9,8 @@ import (
 
 
 	"github.com/database64128/tfo-go"
 	"github.com/database64128/tfo-go"
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/config"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common"
 	"github.com/sagernet/sing/common/buf"
 	"github.com/sagernet/sing/common/buf"
 	"github.com/sagernet/sing/common/bufio"
 	"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", "")
 	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 dialer net.Dialer
 	var listener net.ListenConfig
 	var listener net.ListenConfig
 	if options.BindInterface != "" {
 	if options.BindInterface != "" {
@@ -70,13 +70,13 @@ func newDialer(options config.DialerOptions) N.Dialer {
 
 
 type lazyDialer struct {
 type lazyDialer struct {
 	router   adapter.Router
 	router   adapter.Router
-	options  config.DialerOptions
+	options  option.DialerOptions
 	dialer   N.Dialer
 	dialer   N.Dialer
 	initOnce sync.Once
 	initOnce sync.Once
 	initErr  error
 	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 == "" {
 	if options.Detour == "" {
 		return newDialer(options)
 		return newDialer(options)
 	}
 	}

+ 8 - 8
adapter/outbound/direct.go

@@ -5,9 +5,9 @@ import (
 	"net"
 	"net"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/config"
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing/common/bufio"
 	"github.com/sagernet/sing/common/bufio"
 	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"
@@ -21,7 +21,7 @@ type Direct struct {
 	overrideDestination M.Socksaddr
 	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{
 	outbound := &Direct{
 		myOutboundAdapter: myOutboundAdapter{
 		myOutboundAdapter: myOutboundAdapter{
 			protocol: C.TypeDirect,
 			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) {
 func (d *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
 	switch d.overrideOption {
 	switch d.overrideOption {
-	case 0:
-		destination = d.overrideDestination
 	case 1:
 	case 1:
+		destination = d.overrideDestination
+	case 2:
 		newDestination := d.overrideDestination
 		newDestination := d.overrideDestination
 		newDestination.Port = destination.Port
 		newDestination.Port = destination.Port
 		destination = newDestination
 		destination = newDestination
-	case 2:
+	case 3:
 		destination.Port = d.overrideDestination.Port
 		destination.Port = d.overrideDestination.Port
 	}
 	}
 	switch network {
 	switch network {
 	case C.NetworkTCP:
 	case C.NetworkTCP:
-		d.logger.WithContext(ctx).Debug("outbound connection to ", destination)
+		d.logger.WithContext(ctx).Info("outbound connection to ", destination)
 	case C.NetworkUDP:
 	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)
 	return d.dialer.DialContext(ctx, network, destination)
 }
 }
 
 
 func (d *Direct) ListenPacket(ctx context.Context) (net.PacketConn, error) {
 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)
 	return d.dialer.ListenPacket(ctx)
 }
 }
 
 

+ 5 - 5
adapter/outbound/shadowsocks.go

@@ -5,9 +5,9 @@ import (
 	"net"
 	"net"
 
 
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
-	"github.com/sagernet/sing-box/config"
 	C "github.com/sagernet/sing-box/constant"
 	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sagernet/sing-shadowsocks"
 	"github.com/sagernet/sing-shadowsocks"
 	"github.com/sagernet/sing-shadowsocks/shadowimpl"
 	"github.com/sagernet/sing-shadowsocks/shadowimpl"
 	"github.com/sagernet/sing/common/bufio"
 	"github.com/sagernet/sing/common/bufio"
@@ -24,7 +24,7 @@ type Shadowsocks struct {
 	serverAddr M.Socksaddr
 	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{
 	outbound := &Shadowsocks{
 		myOutboundAdapter: myOutboundAdapter{
 		myOutboundAdapter: myOutboundAdapter{
 			protocol: C.TypeDirect,
 			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) {
 func (o *Shadowsocks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
 	switch network {
 	switch network {
 	case C.NetworkTCP:
 	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)
 		outConn, err := o.dialer.DialContext(ctx, "tcp", o.serverAddr)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
 		return o.method.DialEarlyConn(outConn, destination), nil
 		return o.method.DialEarlyConn(outConn, destination), nil
 	case C.NetworkUDP:
 	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)
 		outConn, err := o.dialer.DialContext(ctx, "udp", o.serverAddr)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			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) {
 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)
 	outConn, err := o.dialer.ListenPacket(ctx)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 59 - 14
adapter/route/router.go

@@ -4,8 +4,12 @@ import (
 	"context"
 	"context"
 	"net"
 	"net"
 
 
+	"github.com/oschwald/geoip2-golang"
 	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/adapter"
+	C "github.com/sagernet/sing-box/constant"
 	"github.com/sagernet/sing-box/log"
 	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing/common"
 	N "github.com/sagernet/sing/common/network"
 	N "github.com/sagernet/sing/common/network"
 )
 )
 
 
@@ -15,24 +19,18 @@ type Router struct {
 	logger          log.Logger
 	logger          log.Logger
 	defaultOutbound adapter.Outbound
 	defaultOutbound adapter.Outbound
 	outboundByTag   map[string]adapter.Outbound
 	outboundByTag   map[string]adapter.Outbound
+
+	rules     []adapter.Rule
+	geoReader *geoip2.Reader
 }
 }
 
 
 func NewRouter(logger log.Logger) *Router {
 func NewRouter(logger log.Logger) *Router {
 	return &Router{
 	return &Router{
-		logger:        logger,
+		logger:        logger.WithPrefix("router: "),
 		outboundByTag: make(map[string]adapter.Outbound),
 		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 {
 func (r *Router) DefaultOutbound() adapter.Outbound {
 	if r.defaultOutbound == nil {
 	if r.defaultOutbound == nil {
 		panic("missing default outbound")
 		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 {
 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)
 	return r.defaultOutbound.NewConnection(ctx, conn, metadata.Destination)
 }
 }
 
 
 func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
 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)
 	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
+}

+ 55 - 0
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
+}

+ 35 - 0
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, " "), "]")
+	}
+}

+ 7 - 0
adapter/router.go

@@ -12,4 +12,11 @@ type Router interface {
 	Outbound(tag string) (Outbound, bool)
 	Outbound(tag string) (Outbound, bool)
 	RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
 	RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
 	RoutePacketConnection(ctx context.Context, conn N.PacketConn, 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
 }
 }

+ 5 - 4
cmd/sing-box/main.go

@@ -8,7 +8,7 @@ import (
 	"syscall"
 	"syscall"
 
 
 	"github.com/sagernet/sing-box"
 	"github.com/sagernet/sing-box"
-	"github.com/sagernet/sing-box/config"
+	"github.com/sagernet/sing-box/option"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
@@ -36,13 +36,14 @@ func run(cmd *cobra.Command, args []string) {
 	if err != nil {
 	if err != nil {
 		logrus.Fatal("read config: ", err)
 		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 {
 	if err != nil {
 		logrus.Fatal("parse config: ", err)
 		logrus.Fatal("parse config: ", err)
 	}
 	}
+
 	ctx, cancel := context.WithCancel(context.Background())
 	ctx, cancel := context.WithCancel(context.Background())
-	service, err := box.NewService(ctx, &boxConfig)
+	service, err := box.NewService(ctx, &options)
 	if err != nil {
 	if err != nil {
 		logrus.Fatal("create service: ", err)
 		logrus.Fatal("create service: ", err)
 	}
 	}

+ 0 - 50
config/address.go

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

+ 0 - 12
config/config.go

@@ -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"`
-}

+ 0 - 14
config/route.go

@@ -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"`
-}

+ 6 - 0
constant/rule.go

@@ -0,0 +1,6 @@
+package constant
+
+const (
+	RuleTypeDefault = "default"
+	RuleTypeLogical = "logical"
+)

+ 2 - 0
go.mod

@@ -5,6 +5,7 @@ go 1.18
 require (
 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/sagernet/sing v0.0.0-20220701084654-2a0502dd664e
 	github.com/sagernet/sing v0.0.0-20220701084654-2a0502dd664e
 	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
@@ -14,6 +15,7 @@ require (
 require (
 require (
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/klauspost/cpuid/v2 v2.0.12 // 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
 	github.com/spf13/pflag v1.0.5 // indirect
 	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
 	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
 	golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect
 	golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect

+ 6 - 1
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/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 h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
 github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
 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 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=
@@ -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/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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 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.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 h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 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=
 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=
 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/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.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 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
 lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
 lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=

+ 20 - 3
log/log.go

@@ -3,10 +3,27 @@ package log
 import (
 import (
 	"context"
 	"context"
 
 
-	"github.com/sirupsen/logrus"
+	"github.com/sagernet/sing-box/option"
 )
 )
 
 
 type Logger interface {
 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)
 }
 }

+ 65 - 0
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))
+}

+ 4 - 4
log/hook.go → log/logrus_hook.go

@@ -6,13 +6,13 @@ import (
 	"github.com/sirupsen/logrus"
 	"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
 	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 {
 	if prefix, loaded := entry.Data["prefix"]; loaded {
 		prefixStr := prefix.(string)
 		prefixStr := prefix.(string)
 		delete(entry.Data, "prefix")
 		delete(entry.Data, "prefix")
@@ -20,7 +20,7 @@ func (h *Hook) Fire(entry *logrus.Entry) error {
 	}
 	}
 	var idCtx *idContext
 	var idCtx *idContext
 	if entry.Context != nil {
 	if entry.Context != nil {
-		idCtx = entry.Context.Value(idType).(*idContext)
+		idCtx, _ = entry.Context.Value(idType).(*idContext)
 	}
 	}
 	if idCtx != nil {
 	if idCtx != nil {
 		var color aurora.Color
 		var color aurora.Color

+ 50 - 0
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
+}

+ 27 - 0
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
+}

+ 14 - 0
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"`
+}

+ 1 - 1
config/inbound.go → option/inbound.go

@@ -1,4 +1,4 @@
-package config
+package option
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"

+ 1 - 1
config/network.go → option/network.go

@@ -1,4 +1,4 @@
-package config
+package option
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"

+ 14 - 5
config/outbound.go → option/outbound.go

@@ -1,9 +1,10 @@
-package config
+package option
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 
 
 	E "github.com/sagernet/sing/common/exceptions"
 	E "github.com/sagernet/sing/common/exceptions"
+	M "github.com/sagernet/sing/common/metadata"
 )
 )
 
 
 var ErrUnknownOutboundType = E.New("unknown outbound type")
 var ErrUnknownOutboundType = E.New("unknown outbound type")
@@ -81,10 +82,18 @@ type DirectOutboundOptions struct {
 	OverridePort    uint16 `json:"override_port,omitempty"`
 	OverridePort    uint16 `json:"override_port,omitempty"`
 }
 }
 
 
-type ShadowsocksOutboundOptions struct {
-	DialerOptions
+type ServerOptions struct {
 	Server     string `json:"server"`
 	Server     string `json:"server"`
 	ServerPort uint16 `json:"server_port"`
 	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"`
 }
 }

+ 80 - 0
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"`
+}

+ 40 - 75
service.go

@@ -7,106 +7,69 @@ import (
 	"github.com/sagernet/sing-box/adapter/inbound"
 	"github.com/sagernet/sing-box/adapter/inbound"
 	"github.com/sagernet/sing-box/adapter/outbound"
 	"github.com/sagernet/sing-box/adapter/outbound"
 	"github.com/sagernet/sing-box/adapter/route"
 	"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/log"
+	"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"
-	"github.com/sirupsen/logrus"
 )
 )
 
 
 var _ adapter.Service = (*Service)(nil)
 var _ adapter.Service = (*Service)(nil)
 
 
 type Service struct {
 type Service struct {
-	logger    *logrus.Logger
+	router    adapter.Router
+	logger    log.Logger
 	inbounds  []adapter.Inbound
 	inbounds  []adapter.Inbound
 	outbounds []adapter.Outbound
 	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 != 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 {
 	if len(options.Inbounds) > 0 {
 		for i, inboundOptions := range options.Inbounds {
 		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
 			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 {
 			if err != nil {
-				return
+				return nil, err
 			}
 			}
-			service.inbounds = append(service.inbounds, inboundService)
+			inbounds = append(inbounds, inboundService)
 		}
 		}
 	}
 	}
 	for i, outboundOptions := range options.Outbounds {
 	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 {
 		if err != nil {
-			return
+			return nil, err
 		}
 		}
-		service.outbounds = append(service.outbounds, outboundHandler)
-		service.router.AddOutbound(outboundHandler)
+		outbounds = append(outbounds, outboundService)
+	}
+	if len(outbounds) == 0 {
+		outbounds = append(outbounds, outbound.NewDirect(nil, logger, "direct", &option.DirectOutboundOptions{}))
 	}
 	}
-	if len(service.outbounds) == 0 {
-		service.outbounds = append(service.outbounds, outbound.NewDirect(nil, logger, "direct", &config.DirectOutboundOptions{}))
-		service.router.AddOutbound(service.outbounds[0])
+	router.UpdateOutbounds(outbounds)
+	err = router.UpdateRules(options.Routes)
+	if err != nil {
+		return nil, err
 	}
 	}
-	return
+	return &Service{
+		router:    router,
+		logger:    logger,
+		inbounds:  inbounds,
+		outbounds: outbounds,
+	}, nil
 }
 }
 
 
 func (s *Service) Start() error {
 func (s *Service) Start() error {
-	for _, inbound := range s.inbounds {
-		err := inbound.Start()
+	for _, in := range s.inbounds {
+		err := in.Start()
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -115,11 +78,13 @@ func (s *Service) Start() error {
 }
 }
 
 
 func (s *Service) Close() 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
 	return nil
 }
 }