Files
sing-box-extended/protocol/failover/inbound.go

137 lines
3.7 KiB
Go

package failover
import (
"context"
"errors"
"net"
"sync"
"github.com/gofrs/uuid/v5"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/kmutex"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
func RegisterInbound(registry *inbound.Registry) {
inbound.Register[option.FailoverInboundOptions](registry, C.TypeFailover, NewInbound)
}
type Inbound struct {
inbound.Adapter
logger logger.ContextLogger
router adapter.ConnectionRouterEx
inbounds []adapter.Inbound
conns map[uuid.UUID]*failoverConn
sessionMtx *kmutex.Kmutex[uuid.UUID]
mtx sync.RWMutex
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.FailoverInboundOptions) (adapter.Inbound, error) {
if len(options.Inbounds) == 0 {
return nil, E.New("missing inbounds")
}
inbound := &Inbound{
Adapter: inbound.NewAdapter(C.TypeFailover, tag),
logger: logger,
router: uot.NewRouter(router, logger),
conns: make(map[uuid.UUID]*failoverConn),
sessionMtx: kmutex.New[uuid.UUID](),
}
router = NewRouter(router, logger, inbound.connHandler)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
inbounds := make([]adapter.Inbound, len(options.Inbounds))
for i, inboundOptions := range options.Inbounds {
inbound, err := inboundRegistry.UnsafeCreate(ctx, router, logger, inboundOptions.Tag, inboundOptions.Type, inboundOptions.Options)
if err != nil {
return nil, err
}
inbounds[i] = inbound
}
inbound.inbounds = inbounds
return inbound, nil
}
func (h *Inbound) Start(stage adapter.StartStage) error {
for _, inbound := range h.inbounds {
err := inbound.Start(stage)
if err != nil {
return err
}
}
return nil
}
func (h *Inbound) Close() error {
errs := make([]error, 0)
for _, inbound := range h.inbounds {
err := inbound.Close()
if err != nil {
errs = append(errs, err)
}
}
if len(errs) != 0 {
return errors.Join(errs...)
}
return nil
}
func (h *Inbound) connHandler(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
if metadata.Destination != Destination {
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
return nil
}
request, err := ReadRequest(conn)
if err != nil {
return err
}
sessionUUID := request.UUID
h.sessionMtx.Lock(sessionUUID)
if request.Command == CommandTCP {
failoverConn := NewFailoverConn(ctx, conn, nil, func() {
h.sessionMtx.Lock(sessionUUID)
h.mtx.Lock()
defer h.sessionMtx.Unlock(sessionUUID)
defer h.mtx.Unlock()
delete(h.conns, sessionUUID)
})
h.mtx.Lock()
h.conns[sessionUUID] = failoverConn
h.mtx.Unlock()
metadata.Inbound = h.Tag()
metadata.InboundType = C.TypeFailover
metadata.Destination = request.Destination
h.sessionMtx.Unlock(sessionUUID)
h.router.RouteConnectionEx(ctx, failoverConn, metadata, onClose)
return nil
}
if request.Command == CommandReconnect {
h.mtx.RLock()
serverConn, ok := h.conns[sessionUUID]
h.mtx.RUnlock()
if !ok {
_, err := conn.Write([]byte{StatusSessionNotFound})
if err != nil {
return err
}
return SessionNotFound
}
_, err = conn.Write([]byte{StatusOK})
if err != nil {
return err
}
err := serverConn.RestoreConn(conn)
h.sessionMtx.Unlock(sessionUUID)
return err
}
return E.New("command ", request.Command, " not found")
}