diff --git a/adapter/handler.go b/adapter/handler.go new file mode 100644 index 00000000..226f76fb --- /dev/null +++ b/adapter/handler.go @@ -0,0 +1,117 @@ +package adapter + +import ( + "context" + "net" + + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type ( + ConnectionHandler interface { + NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error + } + PacketHandler interface { + NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error + } + PacketConnectionHandler interface { + NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error + } + UpstreamHandlerAdapter interface { + N.TCPConnectionHandler + N.UDPConnectionHandler + E.Handler + } + ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error + PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error +) + +func NewUpstreamHandler( + metadata InboundContext, + connectionHandler ConnectionHandlerFunc, + packetHandler PacketConnectionHandlerFunc, + errorHandler E.Handler, +) UpstreamHandlerAdapter { + return &myUpstreamHandlerWrapper{ + metadata: metadata, + connectionHandler: connectionHandler, + packetHandler: packetHandler, + errorHandler: errorHandler, + } +} + +var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil) + +type myUpstreamHandlerWrapper struct { + metadata InboundContext + connectionHandler ConnectionHandlerFunc + packetHandler PacketConnectionHandlerFunc + errorHandler E.Handler +} + +func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + w.metadata.Destination = metadata.Destination + return w.connectionHandler(ctx, conn, w.metadata) +} + +func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + w.metadata.Destination = metadata.Destination + return w.packetHandler(ctx, conn, w.metadata) +} + +func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) { + w.errorHandler.NewError(ctx, err) +} + +var myContextType = (*MetadataContext)(nil) + +type MetadataContext struct { + context.Context + Metadata InboundContext +} + +func (c *MetadataContext) Value(key any) any { + if key == myContextType { + return c + } + return c.Context.Value(key) +} + +type myUpstreamContextHandlerWrapper struct { + connectionHandler ConnectionHandlerFunc + packetHandler PacketConnectionHandlerFunc + errorHandler E.Handler +} + +func NewUpstreamContextHandler( + connectionHandler ConnectionHandlerFunc, + packetHandler PacketConnectionHandlerFunc, + errorHandler E.Handler, +) UpstreamHandlerAdapter { + return &myUpstreamContextHandlerWrapper{ + connectionHandler: connectionHandler, + packetHandler: packetHandler, + errorHandler: errorHandler, + } +} + +func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + myCtx := ctx.Value(myContextType).(*MetadataContext) + ctx = myCtx.Context + myCtx.Metadata.Destination = metadata.Destination + return w.connectionHandler(ctx, conn, myCtx.Metadata) +} + +func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { + myCtx := ctx.Value(myContextType).(*MetadataContext) + ctx = myCtx.Context + myCtx.Metadata.Destination = metadata.Destination + return w.packetHandler(ctx, conn, myCtx.Metadata) +} + +func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) { + w.errorHandler.NewError(ctx, err) +} diff --git a/adapter/http/inbound.go b/adapter/http/inbound.go deleted file mode 100644 index 96e88e77..00000000 --- a/adapter/http/inbound.go +++ /dev/null @@ -1,67 +0,0 @@ -package http - -import ( - std_bufio "bufio" - "context" - "net" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/http" -) - -var _ adapter.InboundHandler = (*Inbound)(nil) - -type Inbound struct { - router adapter.Router - logger log.Logger - authenticator auth.Authenticator -} - -func NewInbound(router adapter.Router, logger log.Logger, options *config.SimpleInboundOptions) *Inbound { - return &Inbound{ - router: router, - logger: logger, - authenticator: auth.NewAuthenticator(options.Users), - } -} - -func (i *Inbound) Type() string { - return C.TypeHTTP -} - -func (i *Inbound) Network() []string { - return []string{C.NetworkTCP} -} - -func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - ctx = &inboundContext{ctx, metadata} - return http.HandleConnection(ctx, conn, std_bufio.NewReader(conn), i.authenticator, (*inboundHandler)(i), M.Metadata{}) -} - -func (i *Inbound) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return os.ErrInvalid -} - -type inboundContext struct { - context.Context - metadata adapter.InboundContext -} - -type inboundHandler Inbound - -func (h *inboundHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - inboundCtx, _ := common.Cast[*inboundContext](ctx) - ctx = inboundCtx.Context - h.logger.WithContext(ctx).Info("inbound connection to ", metadata.Destination) - inboundCtx.metadata.Destination = metadata.Destination - return h.router.RouteConnection(ctx, conn, inboundCtx.metadata) -} diff --git a/adapter/inbound.go b/adapter/inbound.go index 1322c9be..d7ddd6eb 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -1,7 +1,22 @@ package adapter +import ( + "net/netip" + + M "github.com/sagernet/sing/common/metadata" +) + type Inbound interface { Service Type() string Tag() string } + +type InboundContext struct { + Source netip.AddrPort + Destination M.Socksaddr + Inbound string + Network string + Protocol string + Domain string +} diff --git a/adapter/inbound/default.go b/adapter/inbound/default.go new file mode 100644 index 00000000..f7aff602 --- /dev/null +++ b/adapter/inbound/default.go @@ -0,0 +1,297 @@ +package inbound + +import ( + "context" + "net" + "net/netip" + "os" + "sync" + "time" + + "github.com/database64128/tfo-go" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/config" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var _ adapter.Inbound = (*myInboundAdapter)(nil) + +type myInboundAdapter struct { + protocol string + network []string + ctx context.Context + router adapter.Router + logger log.Logger + tag string + listenOptions config.ListenOptions + connHandler adapter.ConnectionHandler + packetHandler adapter.PacketHandler + packetUpstream any + + // internal + + tcpListener *net.TCPListener + udpConn *net.UDPConn + packetForce6 bool + packetAccess sync.RWMutex + packetOutboundClosed chan struct{} + packetOutbound chan *myInboundPacket +} + +func (a *myInboundAdapter) Type() string { + return a.protocol +} + +func (a *myInboundAdapter) Tag() string { + return a.tag +} + +func (a *myInboundAdapter) Start() error { + bindAddr := M.SocksaddrFromAddrPort(netip.Addr(a.listenOptions.Listen), a.listenOptions.Port) + var listenAddr net.Addr + if common.Contains(a.network, C.NetworkTCP) { + var tcpListener *net.TCPListener + var err error + if !a.listenOptions.TCPFastOpen { + tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr("tcp", bindAddr.Addr), bindAddr.TCPAddr()) + } else { + tcpListener, err = tfo.ListenTCP(M.NetworkFromNetAddr("tcp", bindAddr.Addr), bindAddr.TCPAddr()) + } + if err != nil { + return err + } + a.tcpListener = tcpListener + go a.loopTCPIn() + listenAddr = tcpListener.Addr() + } + if common.Contains(a.network, C.NetworkUDP) { + udpConn, err := net.ListenUDP(M.NetworkFromNetAddr("udp", bindAddr.Addr), bindAddr.UDPAddr()) + if err != nil { + return err + } + a.udpConn = udpConn + a.packetForce6 = M.SocksaddrFromNet(udpConn.LocalAddr()).Addr.Is6() + a.packetOutboundClosed = make(chan struct{}) + a.packetOutbound = make(chan *myInboundPacket) + if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { + go a.loopUDPIn() + } else { + go a.loopUDPInThreadSafe() + } + go a.loopUDPOut() + if listenAddr == nil { + listenAddr = udpConn.LocalAddr() + } + } + a.logger.Info("server started at ", listenAddr) + return nil +} + +func (a *myInboundAdapter) Close() error { + return common.Close( + common.PtrOrNil(a.tcpListener), + common.PtrOrNil(a.udpConn), + ) +} + +func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { + return adapter.NewUpstreamHandler(metadata, a.newConnection, a.newPacketConnection, a) +} + +func (a *myInboundAdapter) upstreamContextHandler() adapter.UpstreamHandlerAdapter { + return adapter.NewUpstreamContextHandler(a.newConnection, a.newPacketConnection, a) +} + +func (a *myInboundAdapter) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + a.logger.WithContext(ctx).Info("inbound connection to ", metadata.Destination) + return a.router.RouteConnection(ctx, conn, metadata) +} + +func (a *myInboundAdapter) newPacketConnection(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) loopTCPIn() { + tcpListener := a.tcpListener + for { + conn, err := tcpListener.Accept() + if err != nil { + return + } + var metadata adapter.InboundContext + metadata.Inbound = a.tag + metadata.Source = M.AddrPortFromNet(conn.RemoteAddr()) + go func() { + metadata.Network = "tcp" + ctx := log.ContextWithID(a.ctx) + a.logger.WithContext(ctx).Info("inbound connection from ", conn.RemoteAddr()) + hErr := a.connHandler.NewConnection(ctx, conn, metadata) + if hErr != nil { + a.NewError(ctx, E.Cause(hErr, "process connection from ", conn.RemoteAddr())) + } + }() + } +} + +func (a *myInboundAdapter) loopUDPIn() { + defer close(a.packetOutboundClosed) + _buffer := buf.StackNewPacket() + defer common.KeepAlive(_buffer) + buffer := common.Dup(_buffer) + defer buffer.Release() + buffer.IncRef() + defer buffer.DecRef() + packetService := (*myInboundPacketAdapter)(a) + var metadata adapter.InboundContext + metadata.Inbound = a.tag + metadata.Network = "udp" + for { + buffer.Reset() + n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) + if err != nil { + return + } + buffer.Truncate(n) + metadata.Source = addr + err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) + if err != nil { + a.newError(E.Cause(err, "process packet from ", addr)) + } + } +} + +func (a *myInboundAdapter) loopUDPInThreadSafe() { + defer close(a.packetOutboundClosed) + packetService := (*myInboundPacketAdapter)(a) + var metadata adapter.InboundContext + metadata.Inbound = a.tag + metadata.Network = "udp" + for { + buffer := buf.NewPacket() + n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) + if err != nil { + return + } + buffer.Truncate(n) + metadata.Source = addr + err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata) + if err != nil { + buffer.Release() + a.newError(E.Cause(err, "process packet from ", addr)) + } + } +} + +func (a *myInboundAdapter) loopUDPOut() { + for { + select { + case packet := <-a.packetOutbound: + err := a.writePacket(packet.buffer, packet.destination) + if err != nil && !E.IsClosed(err) { + a.newError(E.New("write back udp: ", err)) + } + continue + case <-a.packetOutboundClosed: + } + for { + select { + case packet := <-a.packetOutbound: + packet.buffer.Release() + default: + return + } + } + } +} + +func (a *myInboundAdapter) newError(err error) { + a.logger.Warn(err) +} + +func (a *myInboundAdapter) NewError(ctx context.Context, err error) { + common.Close(err) + if E.IsClosed(err) { + a.logger.WithContext(ctx).Debug("connection closed") + return + } + a.logger.Error(err) +} + +func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + defer buffer.Release() + if destination.Family().IsFqdn() { + udpAddr, err := net.ResolveUDPAddr("udp", destination.String()) + if err != nil { + return err + } + return common.Error(a.udpConn.WriteTo(buffer.Bytes(), udpAddr)) + } + if a.packetForce6 && destination.Addr.Is4() { + destination.Addr = netip.AddrFrom16(destination.Addr.As16()) + } + return common.Error(a.udpConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort())) +} + +type myInboundPacketAdapter myInboundAdapter + +func (s *myInboundPacketAdapter) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { + n, addr, err := s.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) + if err != nil { + return M.Socksaddr{}, err + } + buffer.Truncate(n) + return M.SocksaddrFromNetIP(addr), nil +} + +func (s *myInboundPacketAdapter) WriteIsThreadUnsafe() { +} + +type myInboundPacket struct { + buffer *buf.Buffer + destination M.Socksaddr +} + +func (s *myInboundPacketAdapter) Upstream() any { + return s.udpConn +} + +func (s *myInboundPacketAdapter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + s.packetAccess.RLock() + defer s.packetAccess.RUnlock() + + select { + case <-s.packetOutboundClosed: + return os.ErrClosed + default: + } + + s.packetOutbound <- &myInboundPacket{buffer, destination} + return nil +} + +func (s *myInboundPacketAdapter) Close() error { + return s.udpConn.Close() +} + +func (s *myInboundPacketAdapter) LocalAddr() net.Addr { + return s.udpConn.LocalAddr() +} + +func (s *myInboundPacketAdapter) SetDeadline(t time.Time) error { + return s.udpConn.SetDeadline(t) +} + +func (s *myInboundPacketAdapter) SetReadDeadline(t time.Time) error { + return s.udpConn.SetReadDeadline(t) +} + +func (s *myInboundPacketAdapter) SetWriteDeadline(t time.Time) error { + return s.udpConn.SetWriteDeadline(t) +} diff --git a/adapter/direct/inbound.go b/adapter/inbound/direct.go similarity index 58% rename from adapter/direct/inbound.go rename to adapter/inbound/direct.go index 779b2bbb..3560f7a7 100644 --- a/adapter/direct/inbound.go +++ b/adapter/inbound/direct.go @@ -1,4 +1,4 @@ -package direct +package inbound import ( "context" @@ -6,31 +6,35 @@ import ( "net/netip" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/udpnat" "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common/buf" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat" ) -var _ adapter.InboundHandler = (*Inbound)(nil) +var _ adapter.Inbound = (*Direct)(nil) -type Inbound struct { - router adapter.Router - logger log.Logger - network []string +type Direct struct { + myInboundAdapter udpNat *udpnat.Service[netip.AddrPort] overrideOption int overrideDestination M.Socksaddr } -func NewInbound(router adapter.Router, logger log.Logger, options *config.DirectInboundOptions) (inbound *Inbound) { - inbound = &Inbound{ - router: router, - logger: logger, - network: options.Network.Build(), +func NewDirect(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.DirectInboundOptions) *Direct { + inbound := &Direct{ + myInboundAdapter: myInboundAdapter{ + protocol: C.TypeDirect, + network: options.Network.Build(), + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + }, } if options.OverrideAddress != "" && options.OverridePort != 0 { inbound.overrideOption = 1 @@ -42,19 +46,13 @@ func NewInbound(router adapter.Router, logger log.Logger, options *config.Direct inbound.overrideOption = 3 inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} } - inbound.udpNat = udpnat.New[netip.AddrPort](options.UDPTimeout, inbound) - return + inbound.udpNat = udpnat.New[netip.AddrPort](options.UDPTimeout, inbound.upstreamContextHandler()) + inbound.connHandler = inbound + inbound.packetHandler = inbound + return inbound } -func (d *Inbound) Type() string { - return C.TypeDirect -} - -func (d *Inbound) Network() []string { - return d.network -} - -func (d *Inbound) 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 { case 0: metadata.Destination = d.overrideDestination @@ -69,7 +67,7 @@ func (d *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata ada return d.router.RouteConnection(ctx, conn, metadata) } -func (d *Inbound) 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 { case 0: metadata.Destination = d.overrideDestination @@ -80,15 +78,9 @@ func (d *Inbound) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf. case 2: metadata.Destination.Port = d.overrideDestination.Port } - d.udpNat.NewPacketDirect(ctx, metadata.Source, conn, buffer, metadata) + var upstreamMetadata M.Metadata + upstreamMetadata.Source = M.SocksaddrFromNetIP(metadata.Source) + upstreamMetadata.Destination = metadata.Destination + d.udpNat.NewPacketDirect(&adapter.MetadataContext{Context: ctx, Metadata: metadata}, metadata.Source, conn, buffer, upstreamMetadata) return nil } - -func (d *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - d.logger.WithContext(ctx).Info("inbound packet connection to ", metadata.Destination) - return d.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (d *Inbound) NewError(ctx context.Context, err error) { - d.logger.WithContext(ctx).Error(err) -} diff --git a/adapter/inbound/http.go b/adapter/inbound/http.go new file mode 100644 index 00000000..d0060b59 --- /dev/null +++ b/adapter/inbound/http.go @@ -0,0 +1,43 @@ +package inbound + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/config" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/auth" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/protocol/http" +) + +var _ adapter.Inbound = (*HTTP)(nil) + +type HTTP struct { + myInboundAdapter + authenticator auth.Authenticator +} + +func NewHTTP(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.SimpleInboundOptions) *HTTP { + inbound := &HTTP{ + myInboundAdapter{ + protocol: C.TypeHTTP, + network: []string{C.NetworkTCP}, + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + }, + auth.NewAuthenticator(options.Users), + } + inbound.connHandler = inbound + return inbound +} + +func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return http.HandleConnection(ctx, conn, std_bufio.NewReader(conn), h.authenticator, h.upstreamHandler(metadata), M.Metadata{}) +} diff --git a/adapter/inbound/mixed.go b/adapter/inbound/mixed.go new file mode 100644 index 00000000..436701c1 --- /dev/null +++ b/adapter/inbound/mixed.go @@ -0,0 +1,58 @@ +package inbound + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/config" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/auth" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/rw" + "github.com/sagernet/sing/protocol/http" + "github.com/sagernet/sing/protocol/socks" + "github.com/sagernet/sing/protocol/socks/socks4" + "github.com/sagernet/sing/protocol/socks/socks5" +) + +var _ adapter.Inbound = (*Mixed)(nil) + +type Mixed struct { + myInboundAdapter + authenticator auth.Authenticator +} + +func NewMixed(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.SimpleInboundOptions) *Mixed { + inbound := &Mixed{ + myInboundAdapter{ + protocol: C.TypeMixed, + network: []string{C.NetworkTCP}, + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + }, + auth.NewAuthenticator(options.Users), + } + inbound.connHandler = inbound + return inbound +} + +func (h *Mixed) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + headerType, err := rw.ReadByte(conn) + if err != nil { + return err + } + switch headerType { + case socks4.Version, socks5.Version: + return socks.HandleConnection0(ctx, conn, headerType, h.authenticator, h.upstreamHandler(metadata), M.Metadata{}) + } + reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType}))) + return http.HandleConnection(ctx, conn, reader, h.authenticator, h.upstreamHandler(metadata), M.Metadata{}) +} diff --git a/adapter/inbound/shadowsocks.go b/adapter/inbound/shadowsocks.go new file mode 100644 index 00000000..36d745c2 --- /dev/null +++ b/adapter/inbound/shadowsocks.go @@ -0,0 +1,73 @@ +package inbound + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/config" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-shadowsocks" + "github.com/sagernet/sing-shadowsocks/shadowaead" + "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +var _ adapter.Inbound = (*Shadowsocks)(nil) + +type Shadowsocks struct { + myInboundAdapter + service shadowsocks.Service +} + +func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.ShadowsocksInboundOptions) (*Shadowsocks, error) { + inbound := &Shadowsocks{ + myInboundAdapter: myInboundAdapter{ + protocol: C.TypeShadowsocks, + network: options.Network.Build(), + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + }, + } + inbound.connHandler = inbound + inbound.packetHandler = inbound + inbound.packetUpstream = true + var udpTimeout int64 + if options.UDPTimeout != 0 { + udpTimeout = options.UDPTimeout + } else { + udpTimeout = 300 + } + var err error + switch { + case options.Method == shadowsocks.MethodNone: + inbound.service = shadowsocks.NewNoneService(options.UDPTimeout, inbound.upstreamContextHandler()) + case common.Contains(shadowaead.List, options.Method): + inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, udpTimeout, inbound.upstreamContextHandler()) + case common.Contains(shadowaead_2022.List, options.Method): + inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, udpTimeout, inbound.upstreamContextHandler()) + default: + err = E.New("shadowsocks: unsupported method: ", options.Method) + } + return inbound, err +} + +func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return h.service.NewConnection(&adapter.MetadataContext{Context: ctx, Metadata: metadata}, conn, M.Metadata{ + Source: M.SocksaddrFromNetIP(metadata.Source), + }) +} + +func (h *Shadowsocks) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { + return h.service.NewPacket(&adapter.MetadataContext{Context: ctx, Metadata: metadata}, conn, buffer, M.Metadata{ + Source: M.SocksaddrFromNetIP(metadata.Source), + }) +} diff --git a/adapter/inbound/socks.go b/adapter/inbound/socks.go new file mode 100644 index 00000000..b25bd1a9 --- /dev/null +++ b/adapter/inbound/socks.go @@ -0,0 +1,42 @@ +package inbound + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/config" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common/auth" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/protocol/socks" +) + +var _ adapter.Inbound = (*Socks)(nil) + +type Socks struct { + myInboundAdapter + authenticator auth.Authenticator +} + +func NewSocks(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options *config.SimpleInboundOptions) *Socks { + inbound := &Socks{ + myInboundAdapter{ + protocol: C.TypeSocks, + network: []string{C.NetworkTCP}, + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + }, + auth.NewAuthenticator(options.Users), + } + inbound.connHandler = inbound + return inbound +} + +func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return socks.HandleConnection(ctx, conn, h.authenticator, h.upstreamHandler(metadata), M.Metadata{}) +} diff --git a/adapter/inbound_context.go b/adapter/inbound_context.go deleted file mode 100644 index 185843d6..00000000 --- a/adapter/inbound_context.go +++ /dev/null @@ -1,16 +0,0 @@ -package adapter - -import ( - "net/netip" - - M "github.com/sagernet/sing/common/metadata" -) - -type InboundContext struct { - Source netip.AddrPort - Destination M.Socksaddr - Inbound string - Network string - Protocol string - Domain string -} diff --git a/adapter/inbound_default.go b/adapter/inbound_default.go deleted file mode 100644 index e9c41a60..00000000 --- a/adapter/inbound_default.go +++ /dev/null @@ -1,291 +0,0 @@ -package adapter - -import ( - "context" - "net" - "net/netip" - "os" - "sync" - "time" - - "github.com/database64128/tfo-go" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type InboundHandler interface { - Type() string - Network() []string - NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error - NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error -} - -var _ Inbound = (*DefaultInboundService)(nil) - -type DefaultInboundService struct { - ctx context.Context - logger log.Logger - tag string - listen netip.AddrPort - listenerTFO bool - handler InboundHandler - tcpListener *net.TCPListener - udpConn *net.UDPConn - forceAddr6 bool - access sync.RWMutex - closed chan struct{} - outbound chan *defaultInboundUDPServiceOutboundPacket -} - -func NewDefaultInboundService(ctx context.Context, tag string, logger log.Logger, listen netip.AddrPort, listenerTFO bool, handler InboundHandler) *DefaultInboundService { - return &DefaultInboundService{ - ctx: ctx, - logger: logger, - tag: tag, - listen: listen, - listenerTFO: listenerTFO, - handler: handler, - closed: make(chan struct{}), - outbound: make(chan *defaultInboundUDPServiceOutboundPacket), - } -} - -func (s *DefaultInboundService) Type() string { - return s.handler.Type() -} - -func (s *DefaultInboundService) Tag() string { - return s.tag -} - -func (s *DefaultInboundService) Start() error { - var listenAddr net.Addr - if common.Contains(s.handler.Network(), C.NetworkTCP) { - var tcpListener *net.TCPListener - var err error - if !s.listenerTFO { - tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr("tcp", s.listen.Addr()), M.SocksaddrFromNetIP(s.listen).TCPAddr()) - } else { - tcpListener, err = tfo.ListenTCP(M.NetworkFromNetAddr("tcp", s.listen.Addr()), M.SocksaddrFromNetIP(s.listen).TCPAddr()) - } - if err != nil { - return err - } - s.tcpListener = tcpListener - go s.loopTCPIn() - listenAddr = tcpListener.Addr() - } - if common.Contains(s.handler.Network(), C.NetworkUDP) { - udpConn, err := net.ListenUDP(M.NetworkFromNetAddr("udp", s.listen.Addr()), M.SocksaddrFromNetIP(s.listen).UDPAddr()) - if err != nil { - return err - } - s.udpConn = udpConn - s.forceAddr6 = M.SocksaddrFromNet(udpConn.LocalAddr()).Addr.Is6() - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](s.handler); !threadUnsafeHandler { - go s.loopUDPIn() - } else { - go s.loopUDPInThreadSafe() - } - go s.loopUDPOut() - if listenAddr == nil { - listenAddr = udpConn.LocalAddr() - } - } - s.logger.Info("server started at ", listenAddr) - return nil -} - -func (s *DefaultInboundService) Close() error { - return common.Close( - common.PtrOrNil(s.tcpListener), - common.PtrOrNil(s.udpConn), - ) -} - -func (s *DefaultInboundService) Upstream() any { - return s.handler -} - -func (s *DefaultInboundService) loopTCPIn() { - tcpListener := s.tcpListener - for { - conn, err := tcpListener.Accept() - if err != nil { - return - } - var metadata InboundContext - metadata.Inbound = s.tag - metadata.Source = M.AddrPortFromNet(conn.RemoteAddr()) - go func() { - metadata.Network = "tcp" - ctx := log.ContextWithID(s.ctx) - s.logger.WithContext(ctx).Info("inbound connection from ", conn.RemoteAddr()) - hErr := s.handler.NewConnection(ctx, conn, metadata) - if hErr != nil { - s.newContextError(ctx, E.Cause(hErr, "process connection from ", conn.RemoteAddr())) - } - }() - } -} - -func (s *DefaultInboundService) loopUDPIn() { - defer close(s.closed) - _buffer := buf.StackNewPacket() - defer common.KeepAlive(_buffer) - buffer := common.Dup(_buffer) - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - packetService := (*defaultInboundUDPService)(s) - var metadata InboundContext - metadata.Inbound = s.tag - metadata.Network = "udp" - for { - buffer.Reset() - n, addr, err := s.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return - } - buffer.Truncate(n) - metadata.Source = addr - err = s.handler.NewPacket(s.ctx, packetService, buffer, metadata) - if err != nil { - s.newError(E.Cause(err, "process packet from ", addr)) - } - } -} - -func (s *DefaultInboundService) loopUDPInThreadSafe() { - defer close(s.closed) - packetService := (*defaultInboundUDPService)(s) - var metadata InboundContext - metadata.Inbound = s.tag - metadata.Network = "udp" - for { - buffer := buf.NewPacket() - n, addr, err := s.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return - } - buffer.Truncate(n) - metadata.Source = addr - err = s.handler.NewPacket(s.ctx, packetService, buffer, metadata) - if err != nil { - buffer.Release() - s.newError(E.Cause(err, "process packet from ", addr)) - } - } -} - -func (s *DefaultInboundService) loopUDPOut() { - for { - select { - case packet := <-s.outbound: - err := s.writePacket(packet.buffer, packet.destination) - if err != nil && !E.IsClosed(err) { - s.newError(E.New("write back udp: ", err)) - } - continue - case <-s.closed: - } - for { - select { - case packet := <-s.outbound: - packet.buffer.Release() - default: - return - } - } - } -} - -func (s *DefaultInboundService) newError(err error) { - s.logger.Warn(err) -} - -func (s *DefaultInboundService) newContextError(ctx context.Context, err error) { - common.Close(err) - if E.IsClosed(err) { - s.logger.WithContext(ctx).Debug("connection closed") - return - } - s.logger.Error(err) -} - -func (s *DefaultInboundService) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - defer buffer.Release() - if destination.Family().IsFqdn() { - udpAddr, err := net.ResolveUDPAddr("udp", destination.String()) - if err != nil { - return err - } - return common.Error(s.udpConn.WriteTo(buffer.Bytes(), udpAddr)) - } - if s.forceAddr6 && destination.Addr.Is4() { - destination.Addr = netip.AddrFrom16(destination.Addr.As16()) - } - return common.Error(s.udpConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort())) -} - -type defaultInboundUDPService DefaultInboundService - -func (s *defaultInboundUDPService) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { - n, addr, err := s.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return M.Socksaddr{}, err - } - buffer.Truncate(n) - return M.SocksaddrFromNetIP(addr), nil -} - -func (s *defaultInboundUDPService) WriteIsThreadUnsafe() { -} - -type defaultInboundUDPServiceOutboundPacket struct { - buffer *buf.Buffer - destination M.Socksaddr -} - -func (s *defaultInboundUDPService) Upstream() any { - return s.udpConn -} - -func (s *defaultInboundUDPService) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - s.access.RLock() - defer s.access.RUnlock() - - select { - case <-s.closed: - return os.ErrClosed - default: - } - - s.outbound <- &defaultInboundUDPServiceOutboundPacket{buffer, destination} - return nil -} - -func (s *defaultInboundUDPService) Close() error { - return s.udpConn.Close() -} - -func (s *defaultInboundUDPService) LocalAddr() net.Addr { - return s.udpConn.LocalAddr() -} - -func (s *defaultInboundUDPService) SetDeadline(t time.Time) error { - return s.udpConn.SetDeadline(t) -} - -func (s *defaultInboundUDPService) SetReadDeadline(t time.Time) error { - return s.udpConn.SetReadDeadline(t) -} - -func (s *defaultInboundUDPService) SetWriteDeadline(t time.Time) error { - return s.udpConn.SetWriteDeadline(t) -} diff --git a/adapter/mixed/inbound.go b/adapter/mixed/inbound.go deleted file mode 100644 index 86c9e91e..00000000 --- a/adapter/mixed/inbound.go +++ /dev/null @@ -1,89 +0,0 @@ -package mixed - -import ( - std_bufio "bufio" - "context" - "net" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/rw" - "github.com/sagernet/sing/protocol/http" - "github.com/sagernet/sing/protocol/socks" - "github.com/sagernet/sing/protocol/socks/socks4" - "github.com/sagernet/sing/protocol/socks/socks5" -) - -var _ adapter.InboundHandler = (*Inbound)(nil) - -type Inbound struct { - router adapter.Router - logger log.Logger - authenticator auth.Authenticator -} - -func NewInbound(router adapter.Router, logger log.Logger, options *config.SimpleInboundOptions) *Inbound { - return &Inbound{ - router: router, - logger: logger, - authenticator: auth.NewAuthenticator(options.Users), - } -} - -func (i *Inbound) Type() string { - return C.TypeMixed -} - -func (i *Inbound) Network() []string { - return []string{C.NetworkTCP} -} - -func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - headerType, err := rw.ReadByte(conn) - if err != nil { - return err - } - ctx = &inboundContext{ctx, metadata} - switch headerType { - case socks4.Version, socks5.Version: - return socks.HandleConnection0(ctx, conn, headerType, i.authenticator, (*inboundHandler)(i), M.Metadata{}) - } - reader := std_bufio.NewReader(bufio.NewCachedReader(conn, buf.As([]byte{headerType}))) - return http.HandleConnection(ctx, conn, reader, i.authenticator, (*inboundHandler)(i), M.Metadata{}) -} - -func (i *Inbound) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return os.ErrInvalid -} - -type inboundContext struct { - context.Context - metadata adapter.InboundContext -} - -type inboundHandler Inbound - -func (h *inboundHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - inboundCtx, _ := common.Cast[*inboundContext](ctx) - ctx = inboundCtx.Context - h.logger.WithContext(ctx).Info("inbound connection to ", metadata.Destination) - inboundCtx.metadata.Destination = metadata.Destination - return h.router.RouteConnection(ctx, conn, inboundCtx.metadata) -} - -func (h *inboundHandler) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - inboundCtx, _ := common.Cast[*inboundContext](ctx) - ctx = inboundCtx.Context - h.logger.WithContext(ctx).Info("inbound packet connection to ", metadata.Destination) - inboundCtx.metadata.Destination = metadata.Destination - return h.router.RoutePacketConnection(ctx, conn, inboundCtx.metadata) -} diff --git a/adapter/outbound.go b/adapter/outbound.go index f3a0a877..d12b3e95 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -15,7 +15,3 @@ type Outbound interface { NewPacketConnection(ctx context.Context, conn N.PacketConn, destination M.Socksaddr) error N.Dialer } - -type OutboundInitializer interface { - Init(outbound Outbound) error -} diff --git a/adapter/outbound/default.go b/adapter/outbound/default.go new file mode 100644 index 00000000..497271ac --- /dev/null +++ b/adapter/outbound/default.go @@ -0,0 +1,138 @@ +package outbound + +import ( + "context" + "net" + "runtime" + "sync" + "time" + + "github.com/database64128/tfo-go" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/config" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type myOutboundAdapter struct { + protocol string + logger log.Logger + tag string + dialer N.Dialer +} + +func (a *myOutboundAdapter) Type() string { + return a.protocol +} + +func (a *myOutboundAdapter) Tag() string { + return a.tag +} + +type defaultDialer struct { + tfo.Dialer + net.ListenConfig +} + +func (d *defaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) { + return d.Dialer.DialContext(ctx, network, address.String()) +} + +func (d *defaultDialer) ListenPacket(ctx context.Context) (net.PacketConn, error) { + return d.ListenConfig.ListenPacket(ctx, "udp", "") +} + +func newDialer(options config.DialerOptions) N.Dialer { + var dialer net.Dialer + var listener net.ListenConfig + if options.BindInterface != "" { + dialer.Control = control.Append(dialer.Control, control.BindToInterface(options.BindInterface)) + listener.Control = control.Append(listener.Control, control.BindToInterface(options.BindInterface)) + } + if options.RoutingMark != 0 { + dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) + listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) + } + if options.ReuseAddr { + listener.Control = control.Append(listener.Control, control.ReuseAddr()) + } + if options.ConnectTimeout != 0 { + dialer.Timeout = time.Duration(options.ConnectTimeout) * time.Second + } + return &defaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, listener} +} + +type lazyDialer struct { + router adapter.Router + options config.DialerOptions + dialer N.Dialer + initOnce sync.Once + initErr error +} + +func NewDialer(router adapter.Router, options config.DialerOptions) N.Dialer { + if options.Detour == "" { + return newDialer(options) + } + return &lazyDialer{ + router: router, + options: options, + } +} + +func (d *lazyDialer) Dialer() (N.Dialer, error) { + d.initOnce.Do(func() { + var loaded bool + d.dialer, loaded = d.router.Outbound(d.options.Detour) + if !loaded { + d.initErr = E.New("outbound detour not found: ", d.options.Detour) + } + }) + return d.dialer, d.initErr +} + +func (d *lazyDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + dialer, err := d.Dialer() + if err != nil { + return nil, err + } + return dialer.DialContext(ctx, network, destination) +} + +func (d *lazyDialer) ListenPacket(ctx context.Context) (net.PacketConn, error) { + dialer, err := d.Dialer() + if err != nil { + return nil, err + } + return dialer.ListenPacket(ctx) +} + +func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error { + _payload := buf.StackNew() + payload := common.Dup(_payload) + err := conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + if err != nil { + return err + } + _, err = payload.ReadFrom(conn) + if err != nil && !E.IsTimeout(err) { + return E.Cause(err, "read payload") + } + err = conn.SetReadDeadline(time.Time{}) + if err != nil { + payload.Release() + return err + } + _, err = serverConn.Write(payload.Bytes()) + if err != nil { + return E.Cause(err, "client handshake") + } + runtime.KeepAlive(_payload) + return bufio.CopyConn(ctx, conn, serverConn) +} diff --git a/adapter/direct/outbound.go b/adapter/outbound/direct.go similarity index 64% rename from adapter/direct/outbound.go rename to adapter/outbound/direct.go index 80522c87..c4ceefe9 100644 --- a/adapter/direct/outbound.go +++ b/adapter/outbound/direct.go @@ -1,11 +1,10 @@ -package direct +package outbound import ( "context" "net" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -14,21 +13,22 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Outbound = (*Outbound)(nil) +var _ adapter.Outbound = (*Direct)(nil) -type Outbound struct { - tag string - logger log.Logger - dialer N.Dialer +type Direct struct { + myOutboundAdapter overrideOption int overrideDestination M.Socksaddr } -func NewOutbound(tag string, router adapter.Router, logger log.Logger, options *config.DirectOutboundOptions) (outbound *Outbound) { - outbound = &Outbound{ - tag: tag, - logger: logger, - dialer: dialer.NewDialer(router, options.DialerOptions), +func NewDirect(router adapter.Router, logger log.Logger, tag string, options *config.DirectOutboundOptions) *Direct { + outbound := &Direct{ + myOutboundAdapter: myOutboundAdapter{ + protocol: C.TypeDirect, + logger: logger, + tag: tag, + dialer: NewDialer(router, options.DialerOptions), + }, } if options.OverrideAddress != "" && options.OverridePort != 0 { outbound.overrideOption = 1 @@ -40,18 +40,10 @@ func NewOutbound(tag string, router adapter.Router, logger log.Logger, options * outbound.overrideOption = 3 outbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} } - return + return outbound } -func (d *Outbound) Type() string { - return C.TypeDirect -} - -func (d *Outbound) Tag() string { - return d.tag -} - -func (d *Outbound) 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 { case 0: destination = d.overrideDestination @@ -71,12 +63,12 @@ func (d *Outbound) DialContext(ctx context.Context, network string, destination return d.dialer.DialContext(ctx, network, destination) } -func (d *Outbound) 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") return d.dialer.ListenPacket(ctx) } -func (d *Outbound) NewConnection(ctx context.Context, conn net.Conn, destination M.Socksaddr) error { +func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, destination M.Socksaddr) error { outConn, err := d.DialContext(ctx, "tcp", destination) if err != nil { return err @@ -84,7 +76,7 @@ func (d *Outbound) NewConnection(ctx context.Context, conn net.Conn, destination return bufio.CopyConn(ctx, conn, outConn) } -func (d *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, destination M.Socksaddr) error { +func (d *Direct) NewPacketConnection(ctx context.Context, conn N.PacketConn, destination M.Socksaddr) error { outConn, err := d.ListenPacket(ctx) if err != nil { return err diff --git a/adapter/shadowsocks/outbound.go b/adapter/outbound/shadowsocks.go similarity index 61% rename from adapter/shadowsocks/outbound.go rename to adapter/outbound/shadowsocks.go index e678c4ea..6414005c 100644 --- a/adapter/shadowsocks/outbound.go +++ b/adapter/outbound/shadowsocks.go @@ -1,12 +1,10 @@ -package shadowsocks +package outbound import ( "context" "net" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/dialer" - "github.com/sagernet/sing-box/common/tunnel" "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -18,54 +16,46 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Outbound = (*Outbound)(nil) +var _ adapter.Outbound = (*Shadowsocks)(nil) -type Outbound struct { - tag string - logger log.Logger - dialer N.Dialer +type Shadowsocks struct { + myOutboundAdapter method shadowsocks.Method serverAddr M.Socksaddr } -func NewOutbound(tag string, router adapter.Router, logger log.Logger, options *config.ShadowsocksOutboundOptions) (outbound *Outbound, err error) { - outbound = &Outbound{ - tag: tag, - logger: logger, - dialer: dialer.NewDialer(router, options.DialerOptions), +func NewShadowsocks(router adapter.Router, logger log.Logger, tag string, options *config.ShadowsocksOutboundOptions) (*Shadowsocks, error) { + outbound := &Shadowsocks{ + myOutboundAdapter: myOutboundAdapter{ + protocol: C.TypeDirect, + logger: logger, + tag: tag, + dialer: NewDialer(router, options.DialerOptions), + }, } + var err error outbound.method, err = shadowimpl.FetchMethod(options.Method, options.Password) if err != nil { - return + return nil, err } if options.Server == "" { - err = E.New("missing server address") - return + return nil, E.New("missing server address") } else if options.ServerPort == 0 { - err = E.New("missing server port") - return + return nil, E.New("missing server port") } outbound.serverAddr = M.ParseSocksaddrHostPort(options.Server, options.ServerPort) - return + return outbound, nil } -func (o *Outbound) Type() string { - return C.TypeShadowsocks -} - -func (o *Outbound) Tag() string { - return o.tag -} - -func (o *Outbound) NewConnection(ctx context.Context, conn net.Conn, destination M.Socksaddr) error { +func (o *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, destination M.Socksaddr) error { serverConn, err := o.DialContext(ctx, "tcp", destination) if err != nil { return err } - return tunnel.CopyEarlyConn(ctx, conn, serverConn) + return CopyEarlyConn(ctx, conn, serverConn) } -func (o *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, destination M.Socksaddr) error { +func (o *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, destination M.Socksaddr) error { serverConn, err := o.ListenPacket(ctx) if err != nil { return err @@ -73,7 +63,7 @@ func (o *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, d return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(serverConn)) } -func (o *Outbound) 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 { case C.NetworkTCP: o.logger.WithContext(ctx).Debug("outbound connection to ", destination) @@ -94,7 +84,7 @@ func (o *Outbound) DialContext(ctx context.Context, network string, destination } } -func (o *Outbound) 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) outConn, err := o.dialer.ListenPacket(ctx) if err != nil { diff --git a/route/router.go b/adapter/route/router.go similarity index 100% rename from route/router.go rename to adapter/route/router.go diff --git a/adapter/shadowsocks/inbound.go b/adapter/shadowsocks/inbound.go deleted file mode 100644 index 41585588..00000000 --- a/adapter/shadowsocks/inbound.go +++ /dev/null @@ -1,114 +0,0 @@ -package shadowsocks - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-shadowsocks" - "github.com/sagernet/sing-shadowsocks/shadowaead" - "github.com/sagernet/sing-shadowsocks/shadowaead_2022" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var ErrUnsupportedMethod = E.New("unsupported method") - -var _ adapter.InboundHandler = (*Inbound)(nil) - -type Inbound struct { - router adapter.Router - logger log.Logger - network []string - service shadowsocks.Service -} - -func (i *Inbound) Network() []string { - return i.network -} - -func NewInbound(router adapter.Router, logger log.Logger, options *config.ShadowsocksInboundOptions) (inbound *Inbound, err error) { - inbound = &Inbound{ - router: router, - logger: logger, - network: options.Network.Build(), - } - handler := (*inboundHandler)(inbound) - - var udpTimeout int64 - if options.UDPTimeout != 0 { - udpTimeout = options.UDPTimeout - } else { - udpTimeout = 300 - } - - switch { - case options.Method == shadowsocks.MethodNone: - inbound.service = shadowsocks.NewNoneService(options.UDPTimeout, handler) - case common.Contains(shadowaead.List, options.Method): - inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, udpTimeout, handler) - case common.Contains(shadowaead_2022.List, options.Method): - inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, udpTimeout, handler) - default: - err = E.Extend(ErrUnsupportedMethod, options.Method) - } - return -} - -func (i *Inbound) Type() string { - return C.TypeShadowsocks -} - -func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return i.service.NewConnection(&inboundContext{ctx, metadata}, conn, M.Metadata{ - Source: M.SocksaddrFromNetIP(metadata.Source), - }) -} - -func (i *Inbound) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return i.service.NewPacket(&inboundContext{ctx, metadata}, conn, buffer, M.Metadata{ - Source: M.SocksaddrFromNetIP(metadata.Source), - }) -} - -func (i *Inbound) Upstream() any { - return i.service -} - -type inboundContext struct { - context.Context - metadata adapter.InboundContext -} - -type inboundHandler Inbound - -func (h *inboundHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - inboundCtx, _ := common.Cast[*inboundContext](ctx) - ctx = inboundCtx.Context - h.logger.WithContext(ctx).Info("inbound connection to ", metadata.Destination) - inboundCtx.metadata.Destination = metadata.Destination - return h.router.RouteConnection(ctx, conn, inboundCtx.metadata) -} - -func (h *inboundHandler) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - inboundCtx, _ := common.Cast[*inboundContext](ctx) - ctx = log.ContextWithID(inboundCtx.Context) - h.logger.WithContext(ctx).Info("inbound packet connection from ", inboundCtx.metadata.Source) - h.logger.WithContext(ctx).Info("inbound packet connection to ", metadata.Destination) - inboundCtx.metadata.Destination = metadata.Destination - return h.router.RoutePacketConnection(ctx, conn, inboundCtx.metadata) -} - -func (h *inboundHandler) NewError(ctx context.Context, err error) { - common.Close(err) - if E.IsClosed(err) { - return - } - h.logger.WithContext(ctx).Warn(err) -} diff --git a/adapter/socks/inbound.go b/adapter/socks/inbound.go deleted file mode 100644 index f04d4422..00000000 --- a/adapter/socks/inbound.go +++ /dev/null @@ -1,74 +0,0 @@ -package socks - -import ( - "context" - "net" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/buf" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/socks" -) - -var _ adapter.InboundHandler = (*Inbound)(nil) - -type Inbound struct { - router adapter.Router - logger log.Logger - authenticator auth.Authenticator -} - -func NewInbound(router adapter.Router, logger log.Logger, options *config.SimpleInboundOptions) *Inbound { - return &Inbound{ - router: router, - logger: logger, - authenticator: auth.NewAuthenticator(options.Users), - } -} - -func (i *Inbound) Type() string { - return C.TypeSocks -} - -func (i *Inbound) Network() []string { - return []string{C.NetworkTCP} -} - -func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - ctx = &inboundContext{ctx, metadata} - return socks.HandleConnection(ctx, conn, i.authenticator, (*inboundHandler)(i), M.Metadata{}) -} - -func (i *Inbound) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error { - return os.ErrInvalid -} - -type inboundContext struct { - context.Context - metadata adapter.InboundContext -} - -type inboundHandler Inbound - -func (h *inboundHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { - inboundCtx, _ := common.Cast[*inboundContext](ctx) - ctx = inboundCtx.Context - h.logger.WithContext(ctx).Info("inbound connection to ", metadata.Destination) - inboundCtx.metadata.Destination = metadata.Destination - return h.router.RouteConnection(ctx, conn, inboundCtx.metadata) -} - -func (h *inboundHandler) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { - inboundCtx, _ := common.Cast[*inboundContext](ctx) - ctx = inboundCtx.Context - h.logger.WithContext(ctx).Info("inbound packet connection to ", metadata.Destination) - inboundCtx.metadata.Destination = metadata.Destination - return h.router.RoutePacketConnection(ctx, conn, inboundCtx.metadata) -} diff --git a/main.go b/cmd/sing-box/main.go similarity index 84% rename from main.go rename to cmd/sing-box/main.go index 7fdaae14..df4ec4a2 100644 --- a/main.go +++ b/cmd/sing-box/main.go @@ -2,17 +2,13 @@ package main import ( "context" - "crypto/rand" - "encoding/binary" "encoding/json" - mRand "math/rand" "os" "os/signal" "syscall" - "github.com/sagernet/sing-box/box" + "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/config" - "github.com/sagernet/sing/common" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -20,9 +16,6 @@ import ( func init() { logrus.StandardLogger().SetLevel(logrus.TraceLevel) logrus.StandardLogger().Formatter.(*logrus.TextFormatter).ForceColors = true - var seed int64 - common.Must(binary.Read(rand.Reader, binary.LittleEndian, &seed)) - mRand.Seed(seed) } var configPath string diff --git a/common/dialer/dialer.go b/common/dialer/dialer.go deleted file mode 100644 index b7f53f9e..00000000 --- a/common/dialer/dialer.go +++ /dev/null @@ -1,46 +0,0 @@ -package dialer - -import ( - "context" - "net" - "time" - - "github.com/database64128/tfo-go" - "github.com/sagernet/sing-box/config" - "github.com/sagernet/sing/common/control" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type defaultDialer struct { - tfo.Dialer - net.ListenConfig -} - -func (d *defaultDialer) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) { - return d.Dialer.DialContext(ctx, network, address.String()) -} - -func (d *defaultDialer) ListenPacket(ctx context.Context) (net.PacketConn, error) { - return d.ListenConfig.ListenPacket(ctx, "udp", "") -} - -func newDialer(options config.DialerOptions) N.Dialer { - var dialer net.Dialer - var listener net.ListenConfig - if options.BindInterface != "" { - dialer.Control = control.Append(dialer.Control, control.BindToInterface(options.BindInterface)) - listener.Control = control.Append(listener.Control, control.BindToInterface(options.BindInterface)) - } - if options.RoutingMark != 0 { - dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark)) - listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark)) - } - if options.ReuseAddr { - listener.Control = control.Append(listener.Control, control.ReuseAddr()) - } - if options.ConnectTimeout != 0 { - dialer.Timeout = time.Duration(options.ConnectTimeout) * time.Second - } - return &defaultDialer{tfo.Dialer{Dialer: dialer, DisableTFO: !options.TCPFastOpen}, listener} -} diff --git a/common/dialer/lazy.go b/common/dialer/lazy.go deleted file mode 100644 index 6cff629d..00000000 --- a/common/dialer/lazy.go +++ /dev/null @@ -1,59 +0,0 @@ -package dialer - -import ( - "context" - "net" - "sync" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/config" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type LazyDialer struct { - router adapter.Router - options config.DialerOptions - dialer N.Dialer - initOnce sync.Once - initErr error -} - -func NewDialer(router adapter.Router, options config.DialerOptions) N.Dialer { - return &LazyDialer{ - router: router, - options: options, - } -} - -func (d *LazyDialer) Dialer() (N.Dialer, error) { - d.initOnce.Do(func() { - if d.options.Detour != "" { - var loaded bool - d.dialer, loaded = d.router.Outbound(d.options.Detour) - if !loaded { - d.initErr = E.New("outbound detour not found: ", d.options.Detour) - } - } else { - d.dialer = newDialer(d.options) - } - }) - return d.dialer, d.initErr -} - -func (d *LazyDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - dialer, err := d.Dialer() - if err != nil { - return nil, err - } - return dialer.DialContext(ctx, network, destination) -} - -func (d *LazyDialer) ListenPacket(ctx context.Context) (net.PacketConn, error) { - dialer, err := d.Dialer() - if err != nil { - return nil, err - } - return dialer.ListenPacket(ctx) -} diff --git a/common/tunnel/copy.go b/common/tunnel/copy.go deleted file mode 100644 index 8e82915e..00000000 --- a/common/tunnel/copy.go +++ /dev/null @@ -1,37 +0,0 @@ -package tunnel - -import ( - "context" - "net" - "runtime" - "time" - - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - E "github.com/sagernet/sing/common/exceptions" -) - -func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error { - _payload := buf.StackNew() - payload := common.Dup(_payload) - err := conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) - if err != nil { - return err - } - _, err = payload.ReadFrom(conn) - if err != nil && !E.IsTimeout(err) { - return E.Cause(err, "read payload") - } - err = conn.SetReadDeadline(time.Time{}) - if err != nil { - payload.Release() - return err - } - _, err = serverConn.Write(payload.Bytes()) - if err != nil { - return E.Cause(err, "client handshake") - } - runtime.KeepAlive(_payload) - return bufio.CopyConn(ctx, conn, serverConn) -} diff --git a/common/udpnat/service.go b/common/udpnat/service.go deleted file mode 100644 index a0c04a79..00000000 --- a/common/udpnat/service.go +++ /dev/null @@ -1,224 +0,0 @@ -package udpnat - -import ( - "context" - "io" - "net" - "net/netip" - "os" - "sync/atomic" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/cache" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type Handler interface { - NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error - E.Handler -} - -type Service[K comparable] struct { - nat *cache.LruCache[K, *conn] - handler Handler -} - -func New[K comparable](maxAge int64, handler Handler) *Service[K] { - return &Service[K]{ - nat: cache.New( - cache.WithAge[K, *conn](maxAge), - cache.WithUpdateAgeOnGet[K, *conn](), - cache.WithEvict[K, *conn](func(key K, conn *conn) { - conn.Close() - }), - ), - handler: handler, - } -} - -func (s *Service[T]) NewPacketDirect(ctx context.Context, key T, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) { - s.NewContextPacket(ctx, key, buffer, metadata, func(natConn N.PacketConn) (context.Context, N.PacketWriter) { - return ctx, &DirectBackWriter{conn, natConn} - }) -} - -type DirectBackWriter struct { - Source N.PacketConn - Nat N.PacketConn -} - -func (w *DirectBackWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { - return w.Source.WritePacket(buffer, M.SocksaddrFromNet(w.Nat.LocalAddr())) -} - -func (w *DirectBackWriter) Upstream() any { - return w.Source -} - -func (s *Service[T]) NewPacket(ctx context.Context, key T, buffer *buf.Buffer, metadata adapter.InboundContext, init func(natConn N.PacketConn) N.PacketWriter) { - s.NewContextPacket(ctx, key, buffer, metadata, func(natConn N.PacketConn) (context.Context, N.PacketWriter) { - return ctx, init(natConn) - }) -} - -func (s *Service[T]) NewContextPacket(ctx context.Context, key T, buffer *buf.Buffer, metadata adapter.InboundContext, init func(natConn N.PacketConn) (context.Context, N.PacketWriter)) { - var maxAge int64 - switch metadata.Destination.Port { - case 443, 853: - maxAge = 30 - case 53, 3478: - maxAge = 10 - } - c, loaded := s.nat.LoadOrStoreWithAge(key, maxAge, func() *conn { - c := &conn{ - data: make(chan packet, 64), - localAddr: metadata.Source, - remoteAddr: metadata.Destination, - fastClose: metadata.Destination.Port == 53, - } - c.ctx, c.cancel = context.WithCancel(ctx) - return c - }) - if !loaded { - ctx, c.source = init(c) - go func() { - err := s.handler.NewPacketConnection(ctx, c, metadata) - if err != nil { - s.handler.NewError(ctx, err) - } - c.Close() - s.nat.Delete(key) - }() - } else { - c.localAddr = metadata.Source - } - if common.Done(c.ctx) { - s.nat.Delete(key) - if !common.Done(ctx) { - s.NewContextPacket(ctx, key, buffer, metadata, init) - } - return - } - c.data <- packet{ - data: buffer, - destination: metadata.Destination, - } -} - -type packet struct { - data *buf.Buffer - destination M.Socksaddr -} - -type conn struct { - ctx context.Context - cancel context.CancelFunc - data chan packet - localAddr netip.AddrPort - remoteAddr M.Socksaddr - source N.PacketWriter - fastClose bool - readDeadline atomic.Value -} - -func (c *conn) ReadPacketThreadSafe() (buffer *buf.Buffer, addr M.Socksaddr, err error) { - var deadline <-chan time.Time - if d, ok := c.readDeadline.Load().(time.Time); ok && !d.IsZero() { - timer := time.NewTimer(time.Until(d)) - defer timer.Stop() - deadline = timer.C - } - select { - case p := <-c.data: - return p.data, p.destination, nil - case <-c.ctx.Done(): - return nil, M.Socksaddr{}, io.ErrClosedPipe - case <-deadline: - return nil, M.Socksaddr{}, os.ErrDeadlineExceeded - } -} - -func (c *conn) ReadPacket(buffer *buf.Buffer) (addr M.Socksaddr, err error) { - var deadline <-chan time.Time - if d, ok := c.readDeadline.Load().(time.Time); ok && !d.IsZero() { - timer := time.NewTimer(time.Until(d)) - defer timer.Stop() - deadline = timer.C - } - select { - case p := <-c.data: - _, err = buffer.ReadFrom(p.data) - p.data.Release() - return p.destination, err - case <-c.ctx.Done(): - return M.Socksaddr{}, io.ErrClosedPipe - case <-deadline: - return M.Socksaddr{}, os.ErrDeadlineExceeded - } -} - -func (c *conn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - if c.fastClose { - defer c.Close() - } - return c.source.WritePacket(buffer, destination) -} - -func (c *conn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - select { - case pkt := <-c.data: - n = copy(p, pkt.data.Bytes()) - pkt.data.Release() - addr = pkt.destination.UDPAddr() - return n, addr, nil - case <-c.ctx.Done(): - return 0, nil, io.ErrClosedPipe - } -} - -func (c *conn) WriteTo(p []byte, addr net.Addr) (n int, err error) { - if c.fastClose { - defer c.Close() - } - return len(p), c.source.WritePacket(buf.As(p).ToOwned(), M.SocksaddrFromNet(addr)) -} - -func (c *conn) Close() error { - select { - case <-c.ctx.Done(): - return os.ErrClosed - default: - } - c.cancel() - return nil -} - -func (c *conn) LocalAddr() net.Addr { - return M.SocksaddrFromNetIP(c.localAddr).UDPAddr() -} - -func (c *conn) RemoteAddr() net.Addr { - return c.remoteAddr.UDPAddr() -} - -func (c *conn) SetDeadline(t time.Time) error { - return os.ErrInvalid -} - -func (c *conn) SetReadDeadline(t time.Time) error { - c.readDeadline.Store(t) - return nil -} - -func (c *conn) SetWriteDeadline(t time.Time) error { - return os.ErrInvalid -} - -func (c *conn) Upstream() any { - return c.source -} diff --git a/config/route.go b/config/route.go index 498f73ba..1b98e5a6 100644 --- a/config/route.go +++ b/config/route.go @@ -9,5 +9,6 @@ type SimpleRule struct { IPVersion []int `json:"ip_version,omitempty"` Network []string `json:"network,omitempty"` Protocol []string `json:"protocol,omitempty"` + Domain []string `json:"domain,omitempty"` Outbound string `json:"outbound,omitempty"` } diff --git a/format.go b/format.go index 37faa453..46555340 100644 --- a/format.go +++ b/format.go @@ -1,4 +1,4 @@ -package main +package box //go:generate go install -v mvdan.cc/gofumpt@latest //go:generate go install -v github.com/daixiang0/gci@latest diff --git a/go.mod b/go.mod index e9ffddd8..73dd4eda 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.18 require ( github.com/database64128/tfo-go v1.0.4 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/sagernet/sing v0.0.0-20220701053004-e85528b42fb3 - github.com/sagernet/sing-shadowsocks v0.0.0-20220701010118-c2032fe11c46 + github.com/sagernet/sing v0.0.0-20220701084654-2a0502dd664e + github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.5.0 ) diff --git a/go.sum b/go.sum index 49b3a8e9..a797eb07 100644 --- a/go.sum +++ b/go.sum @@ -13,10 +13,10 @@ github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/z github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagernet/sing v0.0.0-20220701053004-e85528b42fb3 h1:N+I+g5md4NG7iwznCktg6N+sMPKMSEAG1Yq8rWNuQmE= -github.com/sagernet/sing v0.0.0-20220701053004-e85528b42fb3/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= -github.com/sagernet/sing-shadowsocks v0.0.0-20220701010118-c2032fe11c46 h1:LvMohGqz36YU9mst2BVpxdmbP+B3q3Jw3iu9Ni7ENZ4= -github.com/sagernet/sing-shadowsocks v0.0.0-20220701010118-c2032fe11c46/go.mod h1:4N/34mC/YhclM/dlI7DA4/pPpXGZy360d4ofmLB5XX8= +github.com/sagernet/sing v0.0.0-20220701084654-2a0502dd664e h1:GHT5FW/T8ckRe2BuHoCpzx9zrMPtUO7hvfjqs1Tak0I= +github.com/sagernet/sing v0.0.0-20220701084654-2a0502dd664e/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= +github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k= +github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= diff --git a/log/hook.go b/log/hook.go new file mode 100644 index 00000000..b68dcc41 --- /dev/null +++ b/log/hook.go @@ -0,0 +1,48 @@ +package log + +import ( + "github.com/logrusorgru/aurora" + F "github.com/sagernet/sing/common/format" + "github.com/sirupsen/logrus" +) + +type Hook struct{} + +func (h *Hook) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (h *Hook) Fire(entry *logrus.Entry) error { + if prefix, loaded := entry.Data["prefix"]; loaded { + prefixStr := prefix.(string) + delete(entry.Data, "prefix") + entry.Message = prefixStr + entry.Message + } + var idCtx *idContext + if entry.Context != nil { + idCtx = entry.Context.Value(idType).(*idContext) + } + if idCtx != nil { + var color aurora.Color + color = aurora.Color(uint8(idCtx.id)) + color %= 215 + row := uint(color / 36) + column := uint(color % 36) + + var r, g, b float32 + r = float32(row * 51) + g = float32(column / 6 * 51) + b = float32((column % 6) * 51) + luma := 0.2126*r + 0.7152*g + 0.0722*b + if luma < 60 { + row = 5 - row + column = 35 - column + color = aurora.Color(row*36 + column) + } + color += 16 + color = color << 16 + color |= 1 << 14 + entry.Message = F.ToString("[", aurora.Colorize(idCtx.id, color).String(), "] ", entry.Message) + } + return nil +} diff --git a/log/id.go b/log/id.go index aac6214a..4f77b77c 100644 --- a/log/id.go +++ b/log/id.go @@ -3,13 +3,31 @@ package log import ( "context" "math/rand" + + "github.com/sagernet/sing/common/random" ) +func init() { + random.InitializeSeed() +} + +var idType = (*idContext)(nil) + type idContext struct { context.Context id uint32 } +func (c *idContext) Value(key any) any { + if key == idType { + return c + } + return c.Context.Value(key) +} + func ContextWithID(ctx context.Context) context.Context { + if ctx.Value(idType) != nil { + return ctx + } return &idContext{ctx, rand.Uint32()} } diff --git a/log/log.go b/log/log.go index e57b3b09..667db290 100644 --- a/log/log.go +++ b/log/log.go @@ -3,9 +3,6 @@ package log import ( "context" - "github.com/logrusorgru/aurora" - "github.com/sagernet/sing/common" - F "github.com/sagernet/sing/common/format" "github.com/sirupsen/logrus" ) @@ -13,40 +10,3 @@ type Logger interface { logrus.FieldLogger WithContext(ctx context.Context) *logrus.Entry } - -type Hook struct{} - -func (h *Hook) Levels() []logrus.Level { - return logrus.AllLevels -} - -func (h *Hook) Fire(entry *logrus.Entry) error { - if prefix, loaded := entry.Data["prefix"]; loaded { - prefixStr := prefix.(string) - delete(entry.Data, "prefix") - entry.Message = prefixStr + entry.Message - } - if idCtx, loaded := common.Cast[*idContext](entry.Context); loaded { - var color aurora.Color - color = aurora.Color(uint8(idCtx.id)) - color %= 215 - row := uint(color / 36) - column := uint(color % 36) - - var r, g, b float32 - r = float32(row * 51) - g = float32(column / 6 * 51) - b = float32((column % 6) * 51) - luma := 0.2126*r + 0.7152*g + 0.0722*b - if luma < 60 { - row = 5 - row - column = 35 - column - color = aurora.Color(row*36 + column) - } - color += 16 - color = color << 16 - color |= 1 << 14 - entry.Message = F.ToString("[", aurora.Colorize(idCtx.id, color).String(), "] ", entry.Message) - } - return nil -} diff --git a/box/service.go b/service.go similarity index 58% rename from box/service.go rename to service.go index 6093be6e..881ff7e4 100644 --- a/box/service.go +++ b/service.go @@ -2,18 +2,14 @@ package box import ( "context" - "net/netip" "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/adapter/direct" - "github.com/sagernet/sing-box/adapter/http" - "github.com/sagernet/sing-box/adapter/mixed" - "github.com/sagernet/sing-box/adapter/shadowsocks" - "github.com/sagernet/sing-box/adapter/socks" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/adapter/route" "github.com/sagernet/sing-box/config" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/route" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" @@ -56,39 +52,25 @@ func NewService(ctx context.Context, options *config.Config) (service *Service, } prefix = F.ToString("inbound/", inboundOptions.Type, "[", prefix, "]: ") inboundLogger := logrus.NewEntry(logger).WithFields(logrus.Fields{"prefix": prefix}) - var inbound adapter.InboundHandler - - var listenOptions config.ListenOptions + var inboundService adapter.Inbound switch inboundOptions.Type { case C.TypeDirect: - listenOptions = inboundOptions.DirectOptions.ListenOptions - inbound = direct.NewInbound(service.router, inboundLogger, inboundOptions.DirectOptions) + inboundService = inbound.NewDirect(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.DirectOptions) case C.TypeSocks: - listenOptions = inboundOptions.SocksOptions.ListenOptions - inbound = socks.NewInbound(service.router, inboundLogger, inboundOptions.SocksOptions) + inboundService = inbound.NewSocks(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.SocksOptions) case C.TypeHTTP: - listenOptions = inboundOptions.HTTPOptions.ListenOptions - inbound = http.NewInbound(service.router, inboundLogger, inboundOptions.HTTPOptions) + inboundService = inbound.NewHTTP(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.HTTPOptions) case C.TypeMixed: - listenOptions = inboundOptions.MixedOptions.ListenOptions - inbound = mixed.NewInbound(service.router, inboundLogger, inboundOptions.MixedOptions) + inboundService = inbound.NewMixed(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.MixedOptions) case C.TypeShadowsocks: - listenOptions = inboundOptions.ShadowsocksOptions.ListenOptions - inbound, err = shadowsocks.NewInbound(service.router, inboundLogger, inboundOptions.ShadowsocksOptions) + inboundService, err = inbound.NewShadowsocks(ctx, service.router, inboundLogger, inboundOptions.Tag, inboundOptions.ShadowsocksOptions) default: err = E.New("unknown inbound type: " + inboundOptions.Type) } if err != nil { return } - service.inbounds = append(service.inbounds, adapter.NewDefaultInboundService( - ctx, - inboundOptions.Tag, - inboundLogger, - netip.AddrPortFrom(netip.Addr(listenOptions.Listen), listenOptions.Port), - listenOptions.TCPFastOpen, - inbound, - )) + service.inbounds = append(service.inbounds, inboundService) } } for i, outboundOptions := range options.Outbounds { @@ -100,23 +82,23 @@ func NewService(ctx context.Context, options *config.Config) (service *Service, } prefix = F.ToString("outbound/", outboundOptions.Type, "[", prefix, "]: ") outboundLogger := logrus.NewEntry(logger).WithFields(logrus.Fields{"prefix": prefix}) - var outbound adapter.Outbound + var outboundHandler adapter.Outbound switch outboundOptions.Type { case C.TypeDirect: - outbound = direct.NewOutbound(outboundOptions.Tag, service.router, outboundLogger, outboundOptions.DirectOptions) + outboundHandler = outbound.NewDirect(service.router, outboundLogger, outboundOptions.Tag, outboundOptions.DirectOptions) case C.TypeShadowsocks: - outbound, err = shadowsocks.NewOutbound(outboundOptions.Tag, service.router, outboundLogger, outboundOptions.ShadowsocksOptions) + outboundHandler, err = outbound.NewShadowsocks(service.router, outboundLogger, outboundOptions.Tag, outboundOptions.ShadowsocksOptions) default: err = E.New("unknown outbound type: " + outboundOptions.Type) } if err != nil { return } - service.outbounds = append(service.outbounds, outbound) - service.router.AddOutbound(outbound) + service.outbounds = append(service.outbounds, outboundHandler) + service.router.AddOutbound(outboundHandler) } if len(service.outbounds) == 0 { - service.outbounds = append(service.outbounds, direct.NewOutbound("direct", service.router, logger, &config.DirectOutboundOptions{})) + service.outbounds = append(service.outbounds, outbound.NewDirect(nil, logger, "direct", &config.DirectOutboundOptions{})) service.router.AddOutbound(service.outbounds[0]) } return