Inbound rule support

This commit is contained in:
世界 2022-07-02 14:07:50 +08:00
parent 9f4c0ff624
commit 7c57eb70e8
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
34 changed files with 622 additions and 247 deletions

View File

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

View File

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

View File

@ -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
} }
go func() {
ctx := log.ContextWithID(a.ctx)
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Inbound = a.tag metadata.Inbound = a.tag
metadata.Source = M.AddrPortFromNet(conn.RemoteAddr())
go func() {
metadata.Network = "tcp" metadata.Network = "tcp"
ctx := log.ContextWithID(a.ctx) metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
a.logger.WithContext(ctx).Info("inbound connection from ", 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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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") for _, rule := range r.rules {
r.logger.WithContext(ctx).Debug("route connection to default outbound") 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") for _, rule := range r.rules {
r.logger.WithContext(ctx).Debug("route packet connection to default outbound") 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
adapter/route/rule.go Normal file
View File

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

View File

@ -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, " "), "]")
}
}

View File

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

View File

@ -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 var options option.Options
err = json.Unmarshal(configContent, &boxConfig) 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)
} }

View File

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

View File

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

View File

@ -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
constant/rule.go Normal file
View File

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

2
go.mod
View File

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

7
go.sum
View File

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

View File

@ -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 Trace(args ...interface{})
WithContext(ctx context.Context) *logrus.Entry 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
log/logrus.go Normal file
View File

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

View File

@ -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
log/nop.go Normal file
View File

@ -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
option/address.go Normal file
View File

@ -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
option/config.go Normal file
View File

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

View File

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

View File

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

View File

@ -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 { type ServerOptions struct {
DialerOptions
Server string `json:"server"` Server string `json:"server"`
ServerPort uint16 `json:"server_port"` ServerPort uint16 `json:"server_port"`
}
func (o ServerOptions) Build() M.Socksaddr {
return M.ParseSocksaddrHostPort(o.Server, o.ServerPort)
}
type ShadowsocksOutboundOptions struct {
DialerOptions
ServerOptions
Method string `json:"method"` Method string `json:"method"`
Password string `json:"password"` Password string `json:"password"`
} }

80
option/route.go Normal file
View File

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

View File

@ -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) { func NewService(ctx context.Context, options *option.Options) (*Service, error) {
logger := logrus.New() var logOptions option.LogOption
logger.SetLevel(logrus.TraceLevel)
logger.Formatter.(*logrus.TextFormatter).ForceColors = true
logger.AddHook(new(log.Hook))
if options.Log != nil { if options.Log != nil {
if options.Log.Level != "" { logOptions = *options.Log
logger.Level, err = logrus.ParseLevel(options.Log.Level) }
logger, err := log.NewLogger(logOptions)
if err != nil { if err != nil {
return return nil, err
}
}
}
service = &Service{
logger: logger,
router: route.NewRouter(logrus.NewEntry(logger).WithFields(logrus.Fields{"prefix": "router: "})),
} }
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 { inboundService, err = inbound.New(ctx, router, logger, i, inboundOptions)
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)
}
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 var outboundService adapter.Outbound
if outboundOptions.Tag != "" { outboundService, err = outbound.New(router, logger, i, outboundOptions)
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)
}
if err != nil { if err != nil {
return return nil, err
} }
service.outbounds = append(service.outbounds, outboundHandler) outbounds = append(outbounds, outboundService)
service.router.AddOutbound(outboundHandler)
} }
if len(service.outbounds) == 0 { if len(outbounds) == 0 {
service.outbounds = append(service.outbounds, outbound.NewDirect(nil, logger, "direct", &config.DirectOutboundOptions{})) outbounds = append(outbounds, outbound.NewDirect(nil, logger, "direct", &option.DirectOutboundOptions{}))
service.router.AddOutbound(service.outbounds[0])
} }
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 { func (s *Service) Start() error {
for _, inbound := range s.inbounds { for _, in := range s.inbounds {
err := inbound.Start() 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 { for _, in := range s.inbounds {
inbound.Close() in.Close()
} }
for _, outbound := range s.outbounds { for _, out := range s.outbounds {
common.Close(outbound) common.Close(out)
} }
s.logger.Close()
s.router.Close()
return nil return nil
} }