Add new admin panel, failover, dns fallback, providers, limiters. Update XHTTP

This commit is contained in:
Sergei Maklagin
2026-05-11 00:59:35 +03:00
parent 652e0baf57
commit 3bd162ed6f
241 changed files with 36409 additions and 4086 deletions

View File

@@ -0,0 +1,146 @@
package traffic
import (
"context"
"net"
)
type connWithTrafficLimiter struct {
net.Conn
ctx context.Context
limiter TrafficLimiter
}
func newConnWithDownloadTrafficLimiter(ctx context.Context, conn net.Conn, limiter TrafficLimiter) net.Conn {
return &connWithTrafficLimiter{Conn: conn, ctx: ctx, limiter: limiter}
}
func newConnWithUploadTrafficLimiter(ctx context.Context, conn net.Conn, limiter TrafficLimiter) net.Conn {
return &connWithUploadTrafficLimiter{Conn: conn, ctx: ctx, limiter: limiter}
}
func (conn *connWithTrafficLimiter) Write(p []byte) (int, error) {
err := conn.limiter.Can(uint64(len(p)))
if err != nil {
return 0, err
}
n, err := conn.Conn.Write(p)
if err != nil {
return 0, err
}
err = conn.limiter.Add(uint64(n))
if err != nil {
return 0, err
}
return n, nil
}
type connWithUploadTrafficLimiter struct {
net.Conn
ctx context.Context
limiter TrafficLimiter
}
func (conn *connWithUploadTrafficLimiter) Read(p []byte) (int, error) {
err := conn.limiter.Can(1)
if err != nil {
return 0, err
}
n, err := conn.Conn.Read(p)
if err != nil {
return 0, err
}
err = conn.limiter.Add(uint64(n))
if err != nil {
return 0, err
}
return n, nil
}
type packetConnWithTrafficLimiter struct {
net.PacketConn
ctx context.Context
limiter TrafficLimiter
}
func newPacketConnWithDownloadTrafficLimiter(ctx context.Context, conn net.PacketConn, limiter TrafficLimiter) net.PacketConn {
return &packetConnWithTrafficLimiter{PacketConn: conn, ctx: ctx, limiter: limiter}
}
func newPacketConnWithUploadTrafficLimiter(ctx context.Context, conn net.PacketConn, limiter TrafficLimiter) net.PacketConn {
return &packetConnWithUploadTrafficLimiter{PacketConn: conn, ctx: ctx, limiter: limiter}
}
func (conn *packetConnWithTrafficLimiter) WriteTo(p []byte, addr net.Addr) (int, error) {
err := conn.limiter.Can(uint64(len(p)))
if err != nil {
return 0, err
}
n, err := conn.PacketConn.WriteTo(p, addr)
if err != nil {
return 0, err
}
err = conn.limiter.Add(uint64(n))
if err != nil {
return 0, err
}
return n, nil
}
type packetConnWithUploadTrafficLimiter struct {
net.PacketConn
ctx context.Context
limiter TrafficLimiter
}
func (conn *packetConnWithUploadTrafficLimiter) ReadFrom(p []byte) (int, net.Addr, error) {
err := conn.limiter.Can(1)
if err != nil {
return 0, nil, err
}
n, addr, err := conn.PacketConn.ReadFrom(p)
if err != nil {
return n, nil, err
}
err = conn.limiter.Add(uint64(n))
if err != nil {
return 0, nil, err
}
return n, addr, nil
}
func connWithDownloadTrafficWrapper(ctx context.Context, conn net.Conn, limiter TrafficLimiter, reverse bool) net.Conn {
if reverse {
return newConnWithUploadTrafficLimiter(ctx, conn, limiter)
}
return newConnWithDownloadTrafficLimiter(ctx, conn, limiter)
}
func connWithUploadTrafficWrapper(ctx context.Context, conn net.Conn, limiter TrafficLimiter, reverse bool) net.Conn {
if reverse {
return newConnWithDownloadTrafficLimiter(ctx, conn, limiter)
}
return newConnWithUploadTrafficLimiter(ctx, conn, limiter)
}
func connWithBidirectionalTrafficWrapper(ctx context.Context, conn net.Conn, limiter TrafficLimiter, reverse bool) net.Conn {
return newConnWithUploadTrafficLimiter(ctx, newConnWithDownloadTrafficLimiter(ctx, conn, limiter), limiter)
}
func packetConnWithDownloadTrafficWrapper(ctx context.Context, conn net.PacketConn, limiter TrafficLimiter, reverse bool) net.PacketConn {
if reverse {
return newPacketConnWithUploadTrafficLimiter(ctx, conn, limiter)
}
return newPacketConnWithDownloadTrafficLimiter(ctx, conn, limiter)
}
func packetConnWithUploadTrafficWrapper(ctx context.Context, conn net.PacketConn, limiter TrafficLimiter, reverse bool) net.PacketConn {
if reverse {
return newPacketConnWithDownloadTrafficLimiter(ctx, conn, limiter)
}
return newPacketConnWithUploadTrafficLimiter(ctx, conn, limiter)
}
func packetConnWithBidirectionalTrafficWrapper(ctx context.Context, conn net.PacketConn, limiter TrafficLimiter, reverse bool) net.PacketConn {
return newPacketConnWithUploadTrafficLimiter(ctx, newPacketConnWithDownloadTrafficLimiter(ctx, conn, limiter), limiter)
}

View File

@@ -0,0 +1,6 @@
package traffic
type TrafficLimiter interface {
Can(n uint64) error
Add(n uint64) error
}

View File

@@ -0,0 +1,126 @@
package traffic
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.TrafficLimiterOutboundOptions](registry, C.TypeTrafficLimiter, NewOutbound)
}
type Outbound struct {
outbound.Adapter
ctx context.Context
outbound adapter.OutboundManager
connection adapter.ConnectionManager
logger logger.ContextLogger
strategy TrafficStrategy
outboundTag string
detour adapter.Outbound
router *route.Router
}
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrafficLimiterOutboundOptions) (adapter.Outbound, error) {
if options.Route.Final == "" {
return nil, E.New("missing final outbound")
}
strategy := NewManagerTrafficStrategy()
logFactory := service.FromContext[log.Factory](ctx)
r := route.NewRouter(ctx, logFactory, options.Route, option.DNSOptions{})
err := r.Initialize(options.Route.Rules, options.Route.RuleSet)
if err != nil {
return nil, err
}
outbound := &Outbound{
Adapter: outbound.NewAdapter(C.TypeTrafficLimiter, tag, nil, []string{}),
ctx: ctx,
outbound: service.FromContext[adapter.OutboundManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx),
logger: logger,
strategy: strategy,
outboundTag: options.Route.Final,
router: r,
}
return outbound, nil
}
func (h *Outbound) Network() []string {
return []string{N.NetworkTCP, N.NetworkUDP}
}
func (h *Outbound) Start() error {
detour, loaded := h.outbound.Outbound(h.outboundTag)
if !loaded {
return E.New("outbound not found: ", h.outboundTag)
}
h.detour = detour
for _, stage := range []adapter.StartStage{adapter.StartStateStart, adapter.StartStatePostStart, adapter.StartStateStarted} {
err := h.router.Start(stage)
if err != nil {
return err
}
}
return nil
}
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
conn, err := h.detour.DialContext(ctx, network, destination)
if err != nil {
return nil, err
}
return h.strategy.wrapConn(ctx, conn, adapter.ContextFrom(ctx), true)
}
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
conn, err := h.detour.ListenPacket(ctx, destination)
if err != nil {
return nil, err
}
return h.strategy.wrapPacketConn(ctx, conn, adapter.ContextFrom(ctx), true)
}
func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
conn, err := h.strategy.wrapConn(ctx, conn, &metadata, false)
if err != nil {
if err.Error() != "traffic limit exceeded" {
h.logger.ErrorContext(ctx, err)
}
N.CloseOnHandshakeFailure(conn, onClose, err)
return
}
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}
func (h *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
packetConn, err := h.strategy.wrapPacketConn(ctx, bufio.NewNetPacketConn(conn), &metadata, false)
if err != nil {
if err.Error() != "traffic limit exceeded" {
h.logger.ErrorContext(ctx, err)
}
N.CloseOnHandshakeFailure(conn, onClose, err)
return
}
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
h.router.RoutePacketConnectionEx(ctx, bufio.NewPacketConn(packetConn), metadata, onClose)
}
func (h *Outbound) GetStrategy() TrafficStrategy {
return h.strategy
}

View File

@@ -0,0 +1,164 @@
package traffic
import (
"context"
"net"
"sync"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
)
type (
CloseHandlerFunc = func()
ConnWrapper = func(ctx context.Context, conn net.Conn, limiter TrafficLimiter, reverse bool) net.Conn
PacketConnWrapper = func(ctx context.Context, conn net.PacketConn, limiter TrafficLimiter, reverse bool) net.PacketConn
)
type TrafficStrategy interface {
wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error)
wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error)
}
type TrafficLimiterStrategy interface {
getLimiter(ctx context.Context, metadata *adapter.InboundContext) (TrafficLimiter, error)
}
type DefaultWrapStrategy struct {
limiterStrategy TrafficLimiterStrategy
connWrapper ConnWrapper
packetConnWrapper PacketConnWrapper
}
func NewDefaultWrapStrategy(limiterStrategy TrafficLimiterStrategy, connWrapper ConnWrapper, packetConnWrapper PacketConnWrapper) *DefaultWrapStrategy {
return &DefaultWrapStrategy{limiterStrategy, connWrapper, packetConnWrapper}
}
func (s *DefaultWrapStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
limiter, err := s.limiterStrategy.getLimiter(ctx, metadata)
if err != nil {
return conn, err
}
err = limiter.Can(1)
if err != nil {
return conn, err
}
return s.connWrapper(ctx, conn, limiter, reverse), nil
}
func (s *DefaultWrapStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
limiter, err := s.limiterStrategy.getLimiter(ctx, metadata)
if err != nil {
return conn, err
}
err = limiter.Can(1)
if err != nil {
return conn, err
}
return s.packetConnWrapper(ctx, conn, limiter, reverse), nil
}
type GlobalTrafficStrategy struct {
limiter TrafficLimiter
}
func NewGlobalTrafficStrategy(limiter TrafficLimiter) *GlobalTrafficStrategy {
return &GlobalTrafficStrategy{
limiter: limiter,
}
}
func (s *GlobalTrafficStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (TrafficLimiter, error) {
return s.limiter, nil
}
type ManagerTrafficStrategy struct {
strategies map[string]TrafficStrategy
mtx sync.Mutex
}
func NewManagerTrafficStrategy() *ManagerTrafficStrategy {
return &ManagerTrafficStrategy{}
}
func (s *ManagerTrafficStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
strategy, err := s.getStrategy(ctx, metadata)
if err != nil {
return nil, err
}
return strategy.wrapConn(ctx, conn, metadata, reverse)
}
func (s *ManagerTrafficStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
strategy, err := s.getStrategy(ctx, metadata)
if err != nil {
return nil, err
}
return strategy.wrapPacketConn(ctx, conn, metadata, reverse)
}
func (s *ManagerTrafficStrategy) getStrategy(ctx context.Context, metadata *adapter.InboundContext) (TrafficStrategy, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
var user string
if metadata != nil {
user = metadata.User
}
strategy, ok := s.strategies[user]
if ok {
return strategy, nil
}
return nil, E.New("user strategy not found: ", user)
}
func (s *ManagerTrafficStrategy) UpdateStrategies(strategies map[string]TrafficStrategy) {
s.mtx.Lock()
defer s.mtx.Unlock()
s.strategies = strategies
}
type BypassTrafficStrategy struct{}
func NewBypassTrafficStrategy() *BypassTrafficStrategy {
return &BypassTrafficStrategy{}
}
func (s *BypassTrafficStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
return conn, nil
}
func (s *BypassTrafficStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
return conn, nil
}
func CreateStrategy(limiter TrafficLimiter, strategy string, mode string) (TrafficStrategy, error) {
switch strategy {
case "bypass":
return NewBypassTrafficStrategy(), nil
case "global", "":
var (
connWrapper ConnWrapper
packetConnWrapper PacketConnWrapper
)
switch mode {
case "download":
connWrapper = connWithDownloadTrafficWrapper
packetConnWrapper = packetConnWithDownloadTrafficWrapper
case "upload":
connWrapper = connWithUploadTrafficWrapper
packetConnWrapper = packetConnWithUploadTrafficWrapper
case "bidirectional":
connWrapper = connWithBidirectionalTrafficWrapper
packetConnWrapper = packetConnWithBidirectionalTrafficWrapper
default:
return nil, E.New("mode not found: ", mode)
}
return NewDefaultWrapStrategy(
NewGlobalTrafficStrategy(limiter),
connWrapper,
packetConnWrapper,
), nil
default:
return nil, E.New("strategy not found: ", strategy)
}
}