Browse Source

Shadowsocks multi-user/relay inbound

世界 3 years ago
parent
commit
8e7f215514

+ 14 - 2
adapter/handler.go

@@ -80,6 +80,20 @@ func (c *MetadataContext) Value(key any) any {
 	return c.Context.Value(key)
 }
 
+func ContextWithMetadata(ctx context.Context, metadata InboundContext) context.Context {
+	return &MetadataContext{
+		Context:  ctx,
+		Metadata: metadata,
+	}
+}
+
+func UpstreamMetadata(metadata InboundContext) M.Metadata {
+	return M.Metadata{
+		Source:      metadata.Source,
+		Destination: metadata.Destination,
+	}
+}
+
 type myUpstreamContextHandlerWrapper struct {
 	connectionHandler ConnectionHandlerFunc
 	packetHandler     PacketConnectionHandlerFunc
@@ -100,14 +114,12 @@ func NewUpstreamContextHandler(
 
 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)
 }

+ 1 - 0
common/badjson/array.go

@@ -41,6 +41,7 @@ func (a *JSONArray[T]) decodeJSON(decoder *json.Decoder) error {
 		if err != nil {
 			return err
 		}
+		*a = append(*a, item)
 	}
 	return nil
 }

+ 3 - 7
inbound/default.go

@@ -54,7 +54,6 @@ func (a *myInboundAdapter) Tag() string {
 
 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
@@ -68,7 +67,7 @@ func (a *myInboundAdapter) Start() error {
 		}
 		a.tcpListener = tcpListener
 		go a.loopTCPIn()
-		listenAddr = tcpListener.Addr()
+		a.logger.Info("tcp server started at ", tcpListener.Addr())
 	}
 	if common.Contains(a.network, C.NetworkUDP) {
 		udpConn, err := net.ListenUDP(M.NetworkFromNetAddr(C.NetworkUDP, bindAddr.Addr), bindAddr.UDPAddr())
@@ -85,11 +84,8 @@ func (a *myInboundAdapter) Start() error {
 			go a.loopUDPInThreadSafe()
 		}
 		go a.loopUDPOut()
-		if listenAddr == nil {
-			listenAddr = udpConn.LocalAddr()
-		}
+		a.logger.Info("udp server started at ", udpConn.LocalAddr())
 	}
-	a.logger.Info("server started at ", listenAddr)
 	return nil
 }
 
@@ -229,7 +225,7 @@ func (a *myInboundAdapter) NewError(ctx context.Context, err error) {
 		a.logger.WithContext(ctx).Debug("connection closed")
 		return
 	}
-	a.logger.Error(err)
+	a.logger.WithContext(ctx).Error(err)
 }
 
 func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error {

+ 1 - 4
inbound/direct.go

@@ -78,9 +78,6 @@ func (d *Direct) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.B
 	case 3:
 		metadata.Destination.Port = d.overrideDestination.Port
 	}
-	var upstreamMetadata M.Metadata
-	upstreamMetadata.Source = metadata.Source
-	upstreamMetadata.Destination = metadata.Destination
-	d.udpNat.NewPacketDirect(&adapter.MetadataContext{Context: log.ContextWithID(ctx), Metadata: metadata}, metadata.Source.AddrPort(), conn, buffer, upstreamMetadata)
+	d.udpNat.NewPacketDirect(adapter.ContextWithMetadata(log.ContextWithID(ctx), metadata), metadata.Source.AddrPort(), conn, buffer, adapter.UpstreamMetadata(metadata))
 	return nil
 }

+ 16 - 9
inbound/shadowsocks.go

@@ -14,10 +14,22 @@ import (
 	"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"
 )
 
+func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
+	if len(options.Users) > 0 && len(options.Destinations) > 0 {
+		return nil, E.New("users and destinations options must not be combined")
+	}
+	if len(options.Users) > 0 {
+		return newShadowsocksMulti(ctx, router, logger, tag, options)
+	} else if len(options.Destinations) > 0 {
+		return newShadowsocksRelay(ctx, router, logger, tag, options)
+	} else {
+		return newShadowsocks(ctx, router, logger, tag, options)
+	}
+}
+
 var _ adapter.Inbound = (*Shadowsocks)(nil)
 
 type Shadowsocks struct {
@@ -25,7 +37,7 @@ type Shadowsocks struct {
 	service shadowsocks.Service
 }
 
-func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options option.ShadowsocksInboundOptions) (*Shadowsocks, error) {
+func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options option.ShadowsocksInboundOptions) (*Shadowsocks, error) {
 	inbound := &Shadowsocks{
 		myInboundAdapter: myInboundAdapter{
 			protocol:      C.TypeShadowsocks,
@@ -61,14 +73,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Logge
 }
 
 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: metadata.Source,
-	})
+	return h.service.NewConnection(adapter.ContextWithMetadata(log.ContextWithID(ctx), metadata), conn, adapter.UpstreamMetadata(metadata))
 }
 
 func (h *Shadowsocks) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error {
-	h.logger.Trace("inbound packet from ", metadata.Source)
-	return h.service.NewPacket(&adapter.MetadataContext{Context: ctx, Metadata: metadata}, conn, buffer, M.Metadata{
-		Source: metadata.Source,
-	})
+	return h.service.NewPacket(adapter.ContextWithMetadata(log.ContextWithID(ctx), metadata), conn, buffer, adapter.UpstreamMetadata(metadata))
 }

+ 98 - 0
inbound/shadowsocks_multi.go

@@ -0,0 +1,98 @@
+package inbound
+
+import (
+	"context"
+	"net"
+
+	"github.com/sagernet/sing-box/adapter"
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-shadowsocks"
+	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/buf"
+	F "github.com/sagernet/sing/common/format"
+	N "github.com/sagernet/sing/common/network"
+)
+
+var _ adapter.Inbound = (*ShadowsocksMulti)(nil)
+
+type ShadowsocksMulti struct {
+	myInboundAdapter
+	service *shadowaead_2022.MultiService[int]
+	users   []option.ShadowsocksUser
+}
+
+func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksMulti, error) {
+	inbound := &ShadowsocksMulti{
+		myInboundAdapter: myInboundAdapter{
+			protocol:      C.TypeShadowsocks,
+			network:       options.Network.Build(),
+			ctx:           ctx,
+			router:        router,
+			logger:        logger,
+			tag:           tag,
+			listenOptions: options.ListenOptions,
+		},
+		users: options.Users,
+	}
+	inbound.connHandler = inbound
+	inbound.packetHandler = inbound
+	var udpTimeout int64
+	if options.UDPTimeout != 0 {
+		udpTimeout = options.UDPTimeout
+	} else {
+		udpTimeout = 300
+	}
+	service, err := shadowaead_2022.NewMultiServiceWithPassword[int](
+		options.Method,
+		options.Password,
+		udpTimeout,
+		adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
+	)
+	if err != nil {
+		return nil, err
+	}
+	err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int {
+		return index
+	}), common.Map(options.Users, func(user option.ShadowsocksUser) string {
+		return user.Password
+	}))
+	if err != nil {
+		return nil, err
+	}
+	inbound.service = service
+	inbound.packetUpstream = service
+	return inbound, err
+}
+
+func (h *ShadowsocksMulti) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	return h.service.NewConnection(adapter.ContextWithMetadata(log.ContextWithID(ctx), metadata), conn, adapter.UpstreamMetadata(metadata))
+}
+
+func (h *ShadowsocksMulti) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error {
+	return h.service.NewPacket(adapter.ContextWithMetadata(log.ContextWithID(ctx), metadata), conn, buffer, adapter.UpstreamMetadata(metadata))
+}
+
+func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	userCtx := ctx.(*shadowsocks.UserContext[int])
+	user := h.users[userCtx.User].Name
+	if user == "" {
+		user = F.ToString(userCtx.User)
+	}
+	h.logger.WithContext(ctx).Info("[", user, "] inbound connection to ", metadata.Destination)
+	return h.router.RouteConnection(ctx, conn, metadata)
+}
+
+func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
+	userCtx := ctx.(*shadowsocks.UserContext[int])
+	user := h.users[userCtx.User].Name
+	if user == "" {
+		user = F.ToString(userCtx.User)
+	}
+	ctx = log.ContextWithID(ctx)
+	h.logger.WithContext(ctx).Info("[", user, "] inbound packet connection from ", metadata.Source)
+	h.logger.WithContext(ctx).Info("[", user, "] inbound packet connection to ", metadata.Destination)
+	return h.router.RoutePacketConnection(ctx, conn, metadata)
+}

+ 98 - 0
inbound/shadowsocks_relay.go

@@ -0,0 +1,98 @@
+package inbound
+
+import (
+	"context"
+	"net"
+
+	"github.com/sagernet/sing-box/adapter"
+	C "github.com/sagernet/sing-box/constant"
+	"github.com/sagernet/sing-box/log"
+	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-shadowsocks"
+	"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
+	"github.com/sagernet/sing/common"
+	"github.com/sagernet/sing/common/buf"
+	F "github.com/sagernet/sing/common/format"
+	N "github.com/sagernet/sing/common/network"
+)
+
+var _ adapter.Inbound = (*ShadowsocksMulti)(nil)
+
+type ShadowsocksRelay struct {
+	myInboundAdapter
+	service      *shadowaead_2022.RelayService[int]
+	destinations []option.ShadowsocksDestination
+}
+
+func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.Logger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksRelay, error) {
+	inbound := &ShadowsocksRelay{
+		myInboundAdapter: myInboundAdapter{
+			protocol:      C.TypeShadowsocks,
+			network:       options.Network.Build(),
+			ctx:           ctx,
+			router:        router,
+			logger:        logger,
+			tag:           tag,
+			listenOptions: options.ListenOptions,
+		},
+		destinations: options.Destinations,
+	}
+	inbound.connHandler = inbound
+	inbound.packetHandler = inbound
+	var udpTimeout int64
+	if options.UDPTimeout != 0 {
+		udpTimeout = options.UDPTimeout
+	} else {
+		udpTimeout = 300
+	}
+	service, err := shadowaead_2022.NewRelayServiceWithPassword[int](
+		options.Method,
+		options.Password,
+		udpTimeout,
+		adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
+	)
+	if err != nil {
+		return nil, err
+	}
+	err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Destinations, func(index int, user option.ShadowsocksDestination) int {
+		return index
+	}), common.Map(options.Destinations, func(user option.ShadowsocksDestination) string {
+		return user.Password
+	}), common.Map(options.Destinations, option.ShadowsocksDestination.Build))
+	if err != nil {
+		return nil, err
+	}
+	inbound.service = service
+	inbound.packetUpstream = service
+	return inbound, err
+}
+
+func (h *ShadowsocksRelay) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	return h.service.NewConnection(adapter.ContextWithMetadata(log.ContextWithID(ctx), metadata), conn, adapter.UpstreamMetadata(metadata))
+}
+
+func (h *ShadowsocksRelay) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error {
+	return h.service.NewPacket(adapter.ContextWithMetadata(log.ContextWithID(ctx), metadata), conn, buffer, adapter.UpstreamMetadata(metadata))
+}
+
+func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
+	userCtx := ctx.(*shadowsocks.UserContext[int])
+	destination := h.destinations[userCtx.User].Name
+	if destination == "" {
+		destination = F.ToString(userCtx.User)
+	}
+	h.logger.WithContext(ctx).Info("[", destination, "] inbound connection to ", metadata.Destination)
+	return h.router.RouteConnection(ctx, conn, metadata)
+}
+
+func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
+	userCtx := ctx.(*shadowsocks.UserContext[int])
+	destination := h.destinations[userCtx.User].Name
+	if destination == "" {
+		destination = F.ToString(userCtx.User)
+	}
+	ctx = log.ContextWithID(ctx)
+	h.logger.WithContext(ctx).Info("[", destination, "] inbound packet connection from ", metadata.Source)
+	h.logger.WithContext(ctx).Info("[", destination, "] inbound packet connection to ", metadata.Destination)
+	return h.router.RoutePacketConnection(ctx, conn, metadata)
+}

+ 26 - 4
option/inbound.go

@@ -27,7 +27,7 @@ func (h Inbound) Equals(other Inbound) bool {
 		h.SocksOptions.Equals(other.SocksOptions) &&
 		h.HTTPOptions.Equals(other.HTTPOptions) &&
 		h.MixedOptions.Equals(other.MixedOptions) &&
-		h.ShadowsocksOptions == other.ShadowsocksOptions
+		h.ShadowsocksOptions.Equals(other.ShadowsocksOptions)
 }
 
 func (h Inbound) MarshalJSON() ([]byte, error) {
@@ -102,7 +102,29 @@ type DirectInboundOptions struct {
 
 type ShadowsocksInboundOptions struct {
 	ListenOptions
-	Network  NetworkList `json:"network,omitempty"`
-	Method   string      `json:"method"`
-	Password string      `json:"password"`
+	Network      NetworkList              `json:"network,omitempty"`
+	Method       string                   `json:"method"`
+	Password     string                   `json:"password"`
+	Users        []ShadowsocksUser        `json:"users,omitempty"`
+	Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
+}
+
+func (o ShadowsocksInboundOptions) Equals(other ShadowsocksInboundOptions) bool {
+	return o.ListenOptions == other.ListenOptions &&
+		o.Network == other.Network &&
+		o.Method == other.Method &&
+		o.Password == other.Password &&
+		common.ComparableSliceEquals(o.Users, other.Users) &&
+		common.ComparableSliceEquals(o.Destinations, other.Destinations)
+}
+
+type ShadowsocksUser struct {
+	Name     string `json:"name"`
+	Password string `json:"password"`
+}
+
+type ShadowsocksDestination struct {
+	Name     string `json:"name"`
+	Password string `json:"password"`
+	ServerOptions
 }

+ 1 - 1
option/outbound.go

@@ -8,8 +8,8 @@ import (
 )
 
 type _Outbound struct {
+	Type               string                     `json:"type"`
 	Tag                string                     `json:"tag,omitempty"`
-	Type               string                     `json:"type,omitempty"`
 	DirectOptions      DirectOutboundOptions      `json:"-"`
 	SocksOptions       SocksOutboundOptions       `json:"-"`
 	HTTPOptions        HTTPOutboundOptions        `json:"-"`