mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-14 00:51:12 +03:00
Add new admin panel, failover, dns fallback, providers, limiters. Update XHTTP
This commit is contained in:
@@ -59,7 +59,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
|
||||
service, err := anytls.NewService(anytls.ServiceConfig{
|
||||
Users: common.Map(options.Users, func(it option.AnyTLSUser) anytls.User {
|
||||
return (anytls.User)(it)
|
||||
return anytls.User(it)
|
||||
}),
|
||||
PaddingScheme: paddingScheme,
|
||||
Handler: (*inboundHandler)(inbound),
|
||||
|
||||
@@ -83,7 +83,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
outbound.client = client
|
||||
|
||||
outbound.uotClient = &uot.Client{
|
||||
Dialer: (anytlsDialer)(client.CreateProxy),
|
||||
Dialer: anytlsDialer(client.CreateProxy),
|
||||
Version: uot.Version,
|
||||
}
|
||||
return outbound, nil
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/patrickmn/go-cache/v2"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/common/kmutex"
|
||||
@@ -19,6 +18,7 @@ import (
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/shtorm-7/go-cache/v2"
|
||||
)
|
||||
|
||||
func RegisterInbound(registry *inbound.Registry) {
|
||||
|
||||
232
protocol/failover/conn.go
Normal file
232
protocol/failover/conn.go
Normal file
@@ -0,0 +1,232 @@
|
||||
package failover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
type dial func() (net.Conn, error)
|
||||
|
||||
type failoverConn struct {
|
||||
net.Conn
|
||||
ctx context.Context
|
||||
dial dial
|
||||
onClose func()
|
||||
|
||||
readIndex uint32
|
||||
readBuffer *bytes.Buffer
|
||||
writeIndex uint32
|
||||
writeBuffers [BufferSize][]byte
|
||||
|
||||
await chan struct{}
|
||||
awaitMtx sync.Mutex
|
||||
|
||||
err error
|
||||
|
||||
once sync.Once
|
||||
mtx sync.RWMutex
|
||||
}
|
||||
|
||||
func NewFailoverConn(ctx context.Context, conn net.Conn, dial dial, onClose func()) *failoverConn {
|
||||
var writeBuffers [BufferSize][]byte
|
||||
for i := range BufferSize {
|
||||
writeBuffers[i] = make([]byte, 0, 1000)
|
||||
}
|
||||
return &failoverConn{
|
||||
Conn: conn,
|
||||
ctx: ctx,
|
||||
dial: dial,
|
||||
readBuffer: bytes.NewBuffer(make([]byte, 0, 1000)),
|
||||
writeBuffers: writeBuffers,
|
||||
onClose: onClose,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *failoverConn) Read(b []byte) (int, error) {
|
||||
for {
|
||||
c.mtx.RLock()
|
||||
conn := c.Conn
|
||||
n, err := c.read(conn, b)
|
||||
if err != nil {
|
||||
if err == SessionClosed {
|
||||
c.err = io.EOF
|
||||
conn.Close()
|
||||
c.mtx.RUnlock()
|
||||
return 0, c.err
|
||||
}
|
||||
c.mtx.RUnlock()
|
||||
err = c.awaitConn(conn)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
c.readIndex++
|
||||
c.mtx.RUnlock()
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *failoverConn) Write(b []byte) (int, error) {
|
||||
for {
|
||||
c.mtx.RLock()
|
||||
conn := c.Conn
|
||||
n, err := c.write(conn, b)
|
||||
if err != nil {
|
||||
c.mtx.RUnlock()
|
||||
err = c.awaitConn(conn)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
writeIndex := c.writeIndex % BufferSize
|
||||
c.writeBuffers[writeIndex] = append(c.writeBuffers[writeIndex][:0], b...)
|
||||
c.writeIndex++
|
||||
c.mtx.RUnlock()
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *failoverConn) RestoreConn(conn net.Conn) error {
|
||||
c.Conn.Close()
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
_, err := conn.Write([]byte{
|
||||
byte(c.readIndex >> 24),
|
||||
byte(c.readIndex >> 16),
|
||||
byte(c.readIndex >> 8),
|
||||
byte(c.readIndex),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var data [4]byte
|
||||
_, err = io.ReadFull(conn, data[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeIndex := binary.BigEndian.Uint32(data[:])
|
||||
buffers := make([][]byte, 0, BufferSize)
|
||||
for writeIndex != c.writeIndex {
|
||||
if len(buffers) == BufferSize {
|
||||
return SessionBroken
|
||||
}
|
||||
buffers = append(buffers, c.writeBuffers[writeIndex%BufferSize])
|
||||
writeIndex++
|
||||
}
|
||||
for _, buffer := range buffers {
|
||||
_, err = c.write(conn, buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.Conn = conn
|
||||
if c.await != nil {
|
||||
close(c.await)
|
||||
c.await = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *failoverConn) Close() error {
|
||||
c.once.Do(func() {
|
||||
c.mtx.RLock()
|
||||
if c.onClose != nil {
|
||||
c.onClose()
|
||||
}
|
||||
c.err = io.EOF
|
||||
c.mtx.RUnlock()
|
||||
c.Write([]byte{})
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *failoverConn) read(conn net.Conn, b []byte) (int, error) {
|
||||
if c.readBuffer.Len() == 0 {
|
||||
c.readBuffer.Reset()
|
||||
var data [2]byte
|
||||
_, err := io.ReadFull(conn, data[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n := binary.BigEndian.Uint16(data[:])
|
||||
if n == 0 {
|
||||
return 0, SessionClosed
|
||||
}
|
||||
_, err = io.CopyN(c.readBuffer, conn, int64(n))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return c.readBuffer.Read(b)
|
||||
}
|
||||
|
||||
func (c *failoverConn) write(conn net.Conn, b []byte) (int, error) {
|
||||
buffer := make([]byte, 2+len(b))
|
||||
binary.BigEndian.PutUint16(buffer, uint16(len(b)))
|
||||
copy(buffer[2:], b)
|
||||
n, err := conn.Write(buffer)
|
||||
return n - 2, err
|
||||
}
|
||||
|
||||
func (c *failoverConn) awaitConn(oldConn net.Conn) error {
|
||||
c.awaitMtx.Lock()
|
||||
defer c.awaitMtx.Unlock()
|
||||
if c.err != nil {
|
||||
return c.err
|
||||
}
|
||||
if c.Conn != oldConn {
|
||||
return c.ctx.Err()
|
||||
}
|
||||
oldConn.Close()
|
||||
timer := time.NewTimer(C.TCPConnectTimeout)
|
||||
defer timer.Stop()
|
||||
if c.dial != nil {
|
||||
for {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return c.ctx.Err()
|
||||
case <-timer.C:
|
||||
c.err = SessionExpired
|
||||
return c.err
|
||||
default:
|
||||
}
|
||||
conn, err := c.dial()
|
||||
if err != nil {
|
||||
if err == SessionNotFound {
|
||||
c.err = err
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
err = c.RestoreConn(conn)
|
||||
if err != nil {
|
||||
if err == SessionBroken {
|
||||
c.err = err
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
c.await = make(chan struct{})
|
||||
select {
|
||||
case <-c.await:
|
||||
case <-timer.C:
|
||||
c.err = SessionExpired
|
||||
return c.err
|
||||
case <-c.ctx.Done():
|
||||
return c.ctx.Err()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
136
protocol/failover/inbound.go
Normal file
136
protocol/failover/inbound.go
Normal file
@@ -0,0 +1,136 @@
|
||||
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")
|
||||
}
|
||||
109
protocol/failover/outbound.go
Normal file
109
protocol/failover/outbound.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package failover
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"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"
|
||||
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/common/uot"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func RegisterOutbound(registry *outbound.Registry) {
|
||||
outbound.Register[option.FailoverOutboundOptions](registry, C.TypeFailover, NewFailover)
|
||||
}
|
||||
|
||||
type Failover struct {
|
||||
outbound.Adapter
|
||||
ctx context.Context
|
||||
outbound adapter.OutboundManager
|
||||
logger logger.ContextLogger
|
||||
dial DialStrategy
|
||||
uotClient *uot.Client
|
||||
}
|
||||
|
||||
func NewFailover(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.FailoverOutboundOptions) (adapter.Outbound, error) {
|
||||
if len(options.Outbounds) == 0 {
|
||||
return nil, E.New("missing outbounds")
|
||||
}
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
outbounds := make([]adapter.Outbound, len(options.Outbounds))
|
||||
for i, outboundOptions := range options.Outbounds {
|
||||
outbound, err := outboundRegistry.UnsafeCreateOutbound(ctx, router, logger, outboundOptions.Tag, outboundOptions.Type, outboundOptions.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outbounds[i] = outbound
|
||||
}
|
||||
dial, err := CreateStrategy(options.Strategy, outbounds, logger, options.Delay.Build())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outbound := &Failover{
|
||||
Adapter: outbound.NewAdapter(C.TypeFailover, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}),
|
||||
ctx: ctx,
|
||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||
logger: logger,
|
||||
dial: dial,
|
||||
}
|
||||
outbound.uotClient = &uot.Client{
|
||||
Dialer: outbound,
|
||||
Version: uot.Version,
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (f *Failover) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
if N.NetworkName(network) == N.NetworkUDP {
|
||||
return f.uotClient.DialContext(ctx, network, destination)
|
||||
}
|
||||
conn, err := f.dial(ctx, network, Destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessionUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = WriteRequest(conn, &Request{Command: CommandTCP, UUID: sessionUUID, Destination: destination})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFailoverConn(ctx, conn, func() (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, C.TCPConnectTimeout)
|
||||
defer cancel()
|
||||
conn, err := f.dial(ctx, network, Destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = WriteRequest(conn, &Request{Command: CommandReconnect, UUID: sessionUUID, Destination: destination})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data [1]byte
|
||||
_, err = io.ReadFull(conn, data[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var status uint8 = data[0]
|
||||
if status == StatusSessionNotFound {
|
||||
conn.Close()
|
||||
return nil, SessionNotFound
|
||||
}
|
||||
return conn, nil
|
||||
}, nil), nil
|
||||
}
|
||||
|
||||
func (f *Failover) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
return f.uotClient.ListenPacket(ctx, destination)
|
||||
}
|
||||
97
protocol/failover/protocol.go
Normal file
97
protocol/failover/protocol.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package failover
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = 0
|
||||
BufferSize = 10
|
||||
)
|
||||
|
||||
const (
|
||||
CommandTCP = 1
|
||||
CommandReconnect = 2
|
||||
)
|
||||
|
||||
const (
|
||||
StatusOK uint8 = iota + 1
|
||||
StatusSessionNotFound
|
||||
)
|
||||
|
||||
var (
|
||||
SessionClosed = E.New("session closed")
|
||||
SessionNotFound = E.New("session not found")
|
||||
SessionExpired = E.New("session expired")
|
||||
SessionBroken = E.New("session broken")
|
||||
)
|
||||
|
||||
var Destination = M.Socksaddr{
|
||||
Fqdn: "sp.failover.sing-box.arpa",
|
||||
Port: 444,
|
||||
}
|
||||
|
||||
var AddressSerializer = M.NewSerializer(
|
||||
M.AddressFamilyByte(0x01, M.AddressFamilyIPv4),
|
||||
M.AddressFamilyByte(0x03, M.AddressFamilyIPv6),
|
||||
M.AddressFamilyByte(0x02, M.AddressFamilyFqdn),
|
||||
M.PortThenAddress(),
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
UUID uuid.UUID
|
||||
Command byte
|
||||
Destination M.Socksaddr
|
||||
}
|
||||
|
||||
func ReadRequest(reader io.Reader) (*Request, error) {
|
||||
var request Request
|
||||
var version uint8
|
||||
err := binary.Read(reader, binary.BigEndian, &version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != Version {
|
||||
return nil, E.New("unknown version: ", version)
|
||||
}
|
||||
_, err = io.ReadFull(reader, request.UUID[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &request.Command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Destination, err = AddressSerializer.ReadAddrPort(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &request, nil
|
||||
}
|
||||
|
||||
func WriteRequest(writer io.Writer, request *Request) error {
|
||||
var requestLen int
|
||||
requestLen += 1 // version
|
||||
requestLen += 1 // command
|
||||
requestLen += 16 // UUID
|
||||
requestLen += AddressSerializer.AddrPortLen(request.Destination)
|
||||
buffer := buf.NewSize(requestLen)
|
||||
defer buffer.Release()
|
||||
common.Must(
|
||||
buffer.WriteByte(Version),
|
||||
common.Error(buffer.Write(request.UUID[:])),
|
||||
buffer.WriteByte(request.Command),
|
||||
)
|
||||
err := AddressSerializer.WriteAddrPort(buffer, request.Destination)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return common.Error(writer.Write(buffer.Bytes()))
|
||||
}
|
||||
55
protocol/failover/router.go
Normal file
55
protocol/failover/router.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package failover
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
adapter.Router
|
||||
logger logger.ContextLogger
|
||||
handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error
|
||||
}
|
||||
|
||||
func NewRouter(router adapter.Router, logger logger.ContextLogger, handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error) *Router {
|
||||
return &Router{Router: router, logger: logger, handler: handler}
|
||||
}
|
||||
|
||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
if metadata.Destination != Destination {
|
||||
return r.Router.RouteConnection(ctx, conn, metadata)
|
||||
}
|
||||
return r.handler(ctx, conn, metadata, func(error) {})
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
if metadata.Destination != Destination {
|
||||
return r.Router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
if metadata.Destination != Destination {
|
||||
r.Router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||
return
|
||||
}
|
||||
if err := r.handler(ctx, conn, metadata, onClose); err != nil {
|
||||
r.logger.ErrorContext(ctx, err)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
if metadata.Destination != Destination {
|
||||
r.Router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
return
|
||||
}
|
||||
r.logger.ErrorContext(ctx, os.ErrInvalid)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||
}
|
||||
70
protocol/failover/strategy.go
Normal file
70
protocol/failover/strategy.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package failover
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
type DialStrategy = func(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error)
|
||||
|
||||
func cycleStrategy(outbounds []adapter.Outbound, logger logger.ContextLogger, delay time.Duration) DialStrategy {
|
||||
return func(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
for {
|
||||
for _, outbound := range outbounds {
|
||||
conn, err := outbound.DialContext(ctx, network, destination)
|
||||
if err != nil {
|
||||
logger.InfoContext(ctx, err)
|
||||
if delay > 0 {
|
||||
select {
|
||||
case <-time.After(delay):
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sequentialStrategy(outbounds []adapter.Outbound, logger logger.ContextLogger, delay time.Duration) DialStrategy {
|
||||
return func(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
var err error
|
||||
for _, outbound := range outbounds {
|
||||
var conn net.Conn
|
||||
conn, err = outbound.DialContext(ctx, network, destination)
|
||||
if err != nil {
|
||||
logger.InfoContext(ctx, err)
|
||||
if delay > 0 {
|
||||
select {
|
||||
case <-time.After(delay):
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func CreateStrategy(strategy string, outbounds []adapter.Outbound, logger logger.ContextLogger, delay time.Duration) (DialStrategy, error) {
|
||||
switch strategy {
|
||||
case "cycle":
|
||||
return cycleStrategy(outbounds, logger, delay), nil
|
||||
case "sequential", "":
|
||||
return sequentialStrategy(outbounds, logger, delay), nil
|
||||
default:
|
||||
return nil, E.New("strategy not found: ", strategy)
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,7 @@ func RegisterFallback(registry *outbound.Registry) {
|
||||
outbound.Register[option.FallbackOutboundOptions](registry, C.TypeFallback, NewFallback)
|
||||
}
|
||||
|
||||
var (
|
||||
_ adapter.OutboundGroup = (*Fallback)(nil)
|
||||
)
|
||||
var _ adapter.OutboundGroup = (*Fallback)(nil)
|
||||
|
||||
type Fallback struct {
|
||||
outbound.Adapter
|
||||
@@ -80,7 +78,7 @@ func (s *Fallback) DialContext(ctx context.Context, network string, destination
|
||||
for _, outbound := range s.outbounds {
|
||||
conn, err = outbound.DialContext(ctx, network, destination)
|
||||
if err != nil {
|
||||
s.logger.ErrorContext(ctx, err)
|
||||
s.logger.InfoContext(ctx, err)
|
||||
continue
|
||||
}
|
||||
s.mtx.Lock()
|
||||
@@ -97,7 +95,7 @@ func (s *Fallback) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
||||
for _, outbound := range s.outbounds {
|
||||
conn, err = outbound.ListenPacket(ctx, destination)
|
||||
if err != nil {
|
||||
s.logger.ErrorContext(ctx, err)
|
||||
s.logger.InfoContext(ctx, err)
|
||||
continue
|
||||
}
|
||||
s.mtx.Lock()
|
||||
|
||||
@@ -3,8 +3,6 @@ package bandwidth
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type connWithDownloadBandwidthLimiter struct {
|
||||
@@ -13,7 +11,7 @@ type connWithDownloadBandwidthLimiter struct {
|
||||
limiter Limiter
|
||||
}
|
||||
|
||||
func NewConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter *rate.Limiter) *connWithDownloadBandwidthLimiter {
|
||||
func NewConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter Limiter) *connWithDownloadBandwidthLimiter {
|
||||
return &connWithDownloadBandwidthLimiter{conn, ctx, limiter}
|
||||
}
|
||||
|
||||
@@ -28,12 +26,11 @@ func (conn *connWithDownloadBandwidthLimiter) Write(p []byte) (n int, err error)
|
||||
type connWithUploadBandwidthLimiter struct {
|
||||
net.Conn
|
||||
ctx context.Context
|
||||
limiter *rate.Limiter
|
||||
burst int
|
||||
limiter Limiter
|
||||
}
|
||||
|
||||
func NewConnWithUploadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter *rate.Limiter) *connWithUploadBandwidthLimiter {
|
||||
return &connWithUploadBandwidthLimiter{conn, ctx, limiter, limiter.Burst()}
|
||||
func NewConnWithUploadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter Limiter) *connWithUploadBandwidthLimiter {
|
||||
return &connWithUploadBandwidthLimiter{conn, ctx, limiter}
|
||||
}
|
||||
|
||||
func (conn *connWithUploadBandwidthLimiter) Read(p []byte) (n int, err error) {
|
||||
@@ -65,12 +62,11 @@ func (conn *connWithCloseHandler) Close() error {
|
||||
type packetConnWithDownloadBandwidthLimiter struct {
|
||||
net.PacketConn
|
||||
ctx context.Context
|
||||
limiter *rate.Limiter
|
||||
burst int
|
||||
limiter Limiter
|
||||
}
|
||||
|
||||
func NewPacketConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter) *packetConnWithDownloadBandwidthLimiter {
|
||||
return &packetConnWithDownloadBandwidthLimiter{conn, ctx, limiter, limiter.Burst()}
|
||||
func NewPacketConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter Limiter) *packetConnWithDownloadBandwidthLimiter {
|
||||
return &packetConnWithDownloadBandwidthLimiter{conn, ctx, limiter}
|
||||
}
|
||||
|
||||
func (conn *packetConnWithDownloadBandwidthLimiter) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
@@ -85,11 +81,10 @@ type packetConnWithUploadBandwidthLimiter struct {
|
||||
net.PacketConn
|
||||
ctx context.Context
|
||||
limiter Limiter
|
||||
burst int
|
||||
}
|
||||
|
||||
func NewPacketConnWithUploadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter) *packetConnWithUploadBandwidthLimiter {
|
||||
return &packetConnWithUploadBandwidthLimiter{conn, ctx, limiter, limiter.Burst()}
|
||||
func NewPacketConnWithUploadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter Limiter) *packetConnWithUploadBandwidthLimiter {
|
||||
return &packetConnWithUploadBandwidthLimiter{conn, ctx, limiter}
|
||||
}
|
||||
|
||||
func (conn *packetConnWithUploadBandwidthLimiter) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||
@@ -117,3 +112,39 @@ func (conn *packetConnWithCloseHandler) Close() error {
|
||||
conn.onClose()
|
||||
return conn.PacketConn.Close()
|
||||
}
|
||||
|
||||
func connWithDownloadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter Limiter, reverse bool) net.Conn {
|
||||
if reverse {
|
||||
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
|
||||
func connWithUploadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter Limiter, reverse bool) net.Conn {
|
||||
if reverse {
|
||||
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
|
||||
func connWithBidirectionalBandwidthWrapper(ctx context.Context, conn net.Conn, limiter Limiter, reverse bool) net.Conn {
|
||||
return NewConnWithUploadBandwidthLimiter(ctx, NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
||||
}
|
||||
|
||||
func packetConnWithDownloadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter Limiter, reverse bool) net.PacketConn {
|
||||
if reverse {
|
||||
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
|
||||
func packetConnWithUploadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter Limiter, reverse bool) net.PacketConn {
|
||||
if reverse {
|
||||
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
|
||||
func packetConnWithBidirectionalBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter Limiter, reverse bool) net.PacketConn {
|
||||
return NewPacketConnWithUploadBandwidthLimiter(ctx, NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,130 @@ package bandwidth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
)
|
||||
|
||||
type Limiter interface {
|
||||
WaitN(ctx context.Context, n int) (err error)
|
||||
}
|
||||
|
||||
type FlowKeysLimiter struct {
|
||||
limiter Limiter
|
||||
connIDGetter ConnIDGetter
|
||||
|
||||
waits map[string][]*wait
|
||||
conns map[string]int
|
||||
queue chan struct{}
|
||||
reset time.Time
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewFlowKeysLimiter(connIDGetter ConnIDGetter, limiter Limiter) *FlowKeysLimiter {
|
||||
return &FlowKeysLimiter{
|
||||
limiter: limiter,
|
||||
connIDGetter: connIDGetter,
|
||||
waits: make(map[string][]*wait),
|
||||
conns: make(map[string]int),
|
||||
queue: make(chan struct{}, 1),
|
||||
reset: time.Now().Add(time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *FlowKeysLimiter) WaitN(ctx context.Context, n int) error {
|
||||
id, _ := l.connIDGetter(ctx, adapter.ContextFrom(ctx))
|
||||
mainWait := &wait{ctx, make(chan struct{}), n}
|
||||
l.mtx.Lock()
|
||||
if waits, ok := l.waits[id]; ok {
|
||||
l.waits[id] = append(waits, mainWait)
|
||||
} else {
|
||||
l.waits[id] = []*wait{mainWait}
|
||||
}
|
||||
l.mtx.Unlock()
|
||||
select {
|
||||
case l.queue <- struct{}{}:
|
||||
case <-mainWait.finish:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
l.mtx.Lock()
|
||||
for i, wait := range l.waits[id] {
|
||||
if wait == mainWait {
|
||||
l.waits[id] = slices.Delete(l.waits[id], i, i+1)
|
||||
close(wait.finish)
|
||||
break
|
||||
}
|
||||
}
|
||||
l.mtx.Unlock()
|
||||
return ctx.Err()
|
||||
}
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
l.mtx.Lock()
|
||||
for i, wait := range l.waits[id] {
|
||||
if wait == mainWait {
|
||||
l.waits[id] = slices.Delete(l.waits[id], i, i+1)
|
||||
close(wait.finish)
|
||||
break
|
||||
}
|
||||
}
|
||||
l.mtx.Unlock()
|
||||
<-l.queue
|
||||
return ctx.Err()
|
||||
}
|
||||
now := time.Now()
|
||||
if l.reset.Compare(now) == -1 {
|
||||
clear(l.conns)
|
||||
l.reset = now.Add(time.Second)
|
||||
}
|
||||
l.mtx.Lock()
|
||||
var minConnId string
|
||||
var minN int
|
||||
for connID, waits := range l.waits {
|
||||
if len(waits) == 0 {
|
||||
continue
|
||||
}
|
||||
if n, ok := l.conns[connID]; ok {
|
||||
if minConnId == "" {
|
||||
minConnId = connID
|
||||
minN = n
|
||||
continue
|
||||
}
|
||||
if n+waits[0].n < minN {
|
||||
minConnId = connID
|
||||
minN = n
|
||||
}
|
||||
} else {
|
||||
l.conns[connID] = 0
|
||||
minConnId = connID
|
||||
break
|
||||
}
|
||||
}
|
||||
minWait := l.waits[minConnId][0]
|
||||
l.waits[minConnId][0] = nil
|
||||
l.waits[minConnId] = l.waits[minConnId][1:]
|
||||
if len(l.waits) == 0 {
|
||||
delete(l.waits, minConnId)
|
||||
}
|
||||
l.mtx.Unlock()
|
||||
err := l.limiter.WaitN(ctx, minWait.n)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
l.conns[minConnId] = l.conns[minConnId] + minWait.n
|
||||
close(minWait.finish)
|
||||
if minWait == mainWait {
|
||||
<-l.queue
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type wait struct {
|
||||
ctx context.Context
|
||||
finish chan struct{}
|
||||
n int
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
case "users":
|
||||
usersStrategies := make(map[string]BandwidthStrategy, len(options.Users))
|
||||
for _, user := range options.Users {
|
||||
userStrategy, err := CreateStrategy(user.Strategy, user.Mode, user.ConnectionType, options.Speed.Value())
|
||||
userStrategy, err := CreateStrategy(user.Strategy, user.Mode, user.ConnectionType, options.Speed.Value(), options.FlowKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
case "manager":
|
||||
strategy = NewManagerBandwidthStrategy()
|
||||
default:
|
||||
strategy, err = CreateStrategy(options.Strategy, options.Mode, options.ConnectionType, options.Speed.Value())
|
||||
strategy, err = CreateStrategy(options.Strategy, options.Mode, options.ConnectionType, options.Speed.Value(), options.FlowKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -118,9 +118,11 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
||||
}
|
||||
|
||||
func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
conn, err := h.strategy.wrapConn(ctx, conn, &metadata, false)
|
||||
if err != nil {
|
||||
h.logger.ErrorContext(ctx, err)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
return
|
||||
}
|
||||
metadata.Inbound = h.Tag()
|
||||
@@ -130,9 +132,11 @@ func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata
|
||||
}
|
||||
|
||||
func (h *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
packetConn, err := h.strategy.wrapPacketConn(ctx, bufio.NewNetPacketConn(conn), &metadata, false)
|
||||
if err != nil {
|
||||
h.logger.ErrorContext(ctx, err)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
return
|
||||
}
|
||||
metadata.Inbound = h.Tag()
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
type (
|
||||
CloseHandlerFunc = func()
|
||||
ConnIDGetter = func(context.Context, *adapter.InboundContext) (string, bool)
|
||||
ConnWrapper = func(ctx context.Context, conn net.Conn, limiter *rate.Limiter, reverse bool) net.Conn
|
||||
PacketConnWrapper = func(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter, reverse bool) net.PacketConn
|
||||
ConnWrapper = func(ctx context.Context, conn net.Conn, limiter Limiter, reverse bool) net.Conn
|
||||
PacketConnWrapper = func(ctx context.Context, conn net.PacketConn, limiter Limiter, reverse bool) net.PacketConn
|
||||
)
|
||||
|
||||
type BandwidthStrategy interface {
|
||||
@@ -25,7 +25,7 @@ type BandwidthStrategy interface {
|
||||
}
|
||||
|
||||
type BandwidthLimiterStrategy interface {
|
||||
getLimiter(ctx context.Context, metadata *adapter.InboundContext) (*rate.Limiter, CloseHandlerFunc, error)
|
||||
getLimiter(ctx context.Context, metadata *adapter.InboundContext) (Limiter, CloseHandlerFunc, error)
|
||||
}
|
||||
|
||||
type DefaultWrapStrategy struct {
|
||||
@@ -55,21 +55,25 @@ func (s *DefaultWrapStrategy) wrapPacketConn(ctx context.Context, conn net.Packe
|
||||
}
|
||||
|
||||
type GlobalBandwidthStrategy struct {
|
||||
limiter *rate.Limiter
|
||||
limiter Limiter
|
||||
}
|
||||
|
||||
func NewGlobalBandwidthStrategy(speed uint64) *GlobalBandwidthStrategy {
|
||||
return &GlobalBandwidthStrategy{
|
||||
limiter: createSpeedLimiter(speed),
|
||||
func NewGlobalBandwidthStrategy(speed uint64, flowKeys []string) (*GlobalBandwidthStrategy, error) {
|
||||
limiter, err := createSpeedLimiter(speed, flowKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GlobalBandwidthStrategy{
|
||||
limiter: limiter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *GlobalBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (*rate.Limiter, CloseHandlerFunc, error) {
|
||||
func (s *GlobalBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (Limiter, CloseHandlerFunc, error) {
|
||||
return s.limiter, func() {}, nil
|
||||
}
|
||||
|
||||
type idBandwidthLimiter struct {
|
||||
limiter *rate.Limiter
|
||||
limiter Limiter
|
||||
handles uint32
|
||||
}
|
||||
|
||||
@@ -77,18 +81,20 @@ type ConnectionBandwidthStrategy struct {
|
||||
limiters map[string]*idBandwidthLimiter
|
||||
connIDGetter ConnIDGetter
|
||||
speed uint64
|
||||
flowKeys []string
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewConnectionBandwidthStrategy(connIDGetter ConnIDGetter, speed uint64) *ConnectionBandwidthStrategy {
|
||||
func NewConnectionBandwidthStrategy(connIDGetter ConnIDGetter, speed uint64, flowKeys []string) *ConnectionBandwidthStrategy {
|
||||
return &ConnectionBandwidthStrategy{
|
||||
limiters: make(map[string]*idBandwidthLimiter),
|
||||
connIDGetter: connIDGetter,
|
||||
speed: speed,
|
||||
flowKeys: flowKeys,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ConnectionBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (*rate.Limiter, CloseHandlerFunc, error) {
|
||||
func (s *ConnectionBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (Limiter, CloseHandlerFunc, error) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
id, ok := s.connIDGetter(ctx, metadata)
|
||||
@@ -97,8 +103,12 @@ func (s *ConnectionBandwidthStrategy) getLimiter(ctx context.Context, metadata *
|
||||
}
|
||||
limiter, ok := s.limiters[id]
|
||||
if !ok {
|
||||
newLimiter, err := createSpeedLimiter(s.speed, s.flowKeys)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
limiter = &idBandwidthLimiter{
|
||||
limiter: createSpeedLimiter(s.speed),
|
||||
limiter: newLimiter,
|
||||
}
|
||||
s.limiters[id] = limiter
|
||||
}
|
||||
@@ -173,14 +183,37 @@ func (s *ManagerBandwidthStrategy) UpdateStrategies(strategies map[string]Bandwi
|
||||
s.strategies = strategies
|
||||
}
|
||||
|
||||
func CreateStrategy(strategy string, mode string, connectionType string, speed uint64) (BandwidthStrategy, error) {
|
||||
type BypassBandwidthStrategy struct{}
|
||||
|
||||
func NewBypassBandwidthStrategy() *BypassBandwidthStrategy {
|
||||
return &BypassBandwidthStrategy{}
|
||||
}
|
||||
|
||||
func (s *BypassBandwidthStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (s *BypassBandwidthStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func CreateStrategy(strategy string, mode string, connectionType string, speed uint64, flowKeys []string) (BandwidthStrategy, error) {
|
||||
var limiterStrategy BandwidthLimiterStrategy
|
||||
switch strategy {
|
||||
case "global":
|
||||
limiterStrategy = NewGlobalBandwidthStrategy(speed)
|
||||
var err error
|
||||
limiterStrategy, err = NewGlobalBandwidthStrategy(speed, flowKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "connection":
|
||||
var connIDGetter ConnIDGetter
|
||||
switch connectionType {
|
||||
case "hwid":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := ctx.Value("hwid").(string)
|
||||
return id, ok
|
||||
}
|
||||
case "mux":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := log.MuxIDFromContext(ctx)
|
||||
@@ -189,19 +222,24 @@ func CreateStrategy(strategy string, mode string, connectionType string, speed u
|
||||
}
|
||||
return strconv.FormatUint(uint64(id.ID), 10), ok
|
||||
}
|
||||
case "hwid":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := ctx.Value("hwid").(string)
|
||||
return id, ok
|
||||
}
|
||||
case "ip":
|
||||
case "source_ip":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
return metadata.Source.IPAddr().String(), true
|
||||
}
|
||||
case "default", "":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := log.IDFromContext(ctx)
|
||||
if !ok {
|
||||
return "", ok
|
||||
}
|
||||
return strconv.FormatUint(uint64(id.ID), 10), ok
|
||||
}
|
||||
default:
|
||||
return nil, E.New("connection type not found: ", connectionType)
|
||||
}
|
||||
limiterStrategy = NewConnectionBandwidthStrategy(connIDGetter, speed)
|
||||
limiterStrategy = NewConnectionBandwidthStrategy(connIDGetter, speed, flowKeys)
|
||||
case "bypass":
|
||||
return NewBypassBandwidthStrategy(), nil
|
||||
default:
|
||||
return nil, E.New("strategy not found: ", strategy)
|
||||
}
|
||||
@@ -216,51 +254,55 @@ func CreateStrategy(strategy string, mode string, connectionType string, speed u
|
||||
case "upload":
|
||||
connWrapper = connWithUploadBandwidthWrapper
|
||||
packetConnWrapper = packetConnWithUploadBandwidthWrapper
|
||||
case "duplex":
|
||||
connWrapper = connWithDuplexBandwidthWrapper
|
||||
packetConnWrapper = packetConnWithDuplexBandwidthWrapper
|
||||
case "bidirectional":
|
||||
connWrapper = connWithBidirectionalBandwidthWrapper
|
||||
packetConnWrapper = packetConnWithBidirectionalBandwidthWrapper
|
||||
default:
|
||||
return nil, E.New("mode not found: ", mode)
|
||||
}
|
||||
return NewDefaultWrapStrategy(limiterStrategy, connWrapper, packetConnWrapper), nil
|
||||
}
|
||||
|
||||
func createSpeedLimiter(speed uint64) *rate.Limiter {
|
||||
return rate.NewLimiter(rate.Limit(float64(speed)), 65536)
|
||||
}
|
||||
|
||||
func connWithDownloadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter *rate.Limiter, reverse bool) net.Conn {
|
||||
if reverse {
|
||||
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||
func createSpeedLimiter(speed uint64, flowKeys []string) (Limiter, error) {
|
||||
var limiter Limiter = rate.NewLimiter(rate.Limit(float64(speed)), 65536)
|
||||
for i := len(flowKeys) - 1; i >= 0; i-- {
|
||||
getter, err := flowKeysConnIDGetter(flowKeys[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limiter = NewFlowKeysLimiter(getter, limiter)
|
||||
}
|
||||
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||
return limiter, nil
|
||||
}
|
||||
|
||||
func connWithUploadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter *rate.Limiter, reverse bool) net.Conn {
|
||||
if reverse {
|
||||
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||
func flowKeysConnIDGetter(name string) (ConnIDGetter, error) {
|
||||
switch name {
|
||||
case "user":
|
||||
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
return metadata.User, true
|
||||
}, nil
|
||||
case "destination":
|
||||
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
return metadata.Destination.String(), true
|
||||
}, nil
|
||||
case "source_ip":
|
||||
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
return metadata.Source.IPAddr().String(), true
|
||||
}, nil
|
||||
case "hwid":
|
||||
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := ctx.Value("hwid").(string)
|
||||
return id, ok
|
||||
}, nil
|
||||
case "mux":
|
||||
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := log.MuxIDFromContext(ctx)
|
||||
if !ok {
|
||||
return "", ok
|
||||
}
|
||||
return strconv.FormatUint(uint64(id.ID), 10), ok
|
||||
}, nil
|
||||
default:
|
||||
return nil, E.New("flow key not found: ", name)
|
||||
}
|
||||
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
|
||||
func connWithDuplexBandwidthWrapper(ctx context.Context, conn net.Conn, limiter *rate.Limiter, reverse bool) net.Conn {
|
||||
return NewConnWithUploadBandwidthLimiter(ctx, NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
||||
}
|
||||
|
||||
func packetConnWithDownloadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter, reverse bool) net.PacketConn {
|
||||
if reverse {
|
||||
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
|
||||
func packetConnWithUploadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter, reverse bool) net.PacketConn {
|
||||
if reverse {
|
||||
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||
}
|
||||
|
||||
func packetConnWithDuplexBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter, reverse bool) net.PacketConn {
|
||||
return NewPacketConnWithUploadBandwidthLimiter(ctx, NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata
|
||||
limiterOnClose, lockCtx, err := h.strategy.request(ctx, &metadata)
|
||||
if err != nil {
|
||||
h.logger.ErrorContext(ctx, err)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
return
|
||||
}
|
||||
conn = newConnWithCloseHandlerFunc(conn, limiterOnClose)
|
||||
@@ -154,6 +155,7 @@ func (h *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
||||
limiterOnClose, lockCtx, err := h.strategy.request(ctx, &metadata)
|
||||
if err != nil {
|
||||
h.logger.ErrorContext(ctx, err)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
return
|
||||
}
|
||||
conn = bufio.NewPacketConn(newPacketConnWithCloseHandlerFunc(bufio.NewNetPacketConn(conn), limiterOnClose))
|
||||
|
||||
@@ -87,11 +87,26 @@ func (s *ManagerConnectionStrategy) UpdateStrategies(strategies map[string]Conne
|
||||
s.strategies = strategies
|
||||
}
|
||||
|
||||
type BypassConnectionStrategy struct{}
|
||||
|
||||
func NewBypassConnectionStrategy() *BypassConnectionStrategy {
|
||||
return &BypassConnectionStrategy{}
|
||||
}
|
||||
|
||||
func (s *BypassConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (CloseHandlerFunc, context.Context, error) {
|
||||
return func() {}, nil, nil
|
||||
}
|
||||
|
||||
func CreateStrategy(strategy string, connectionType string, lockIDGetter LockIDGetter) (ConnectionStrategy, error) {
|
||||
switch strategy {
|
||||
case "connection":
|
||||
var connIDGetter ConnIDGetter
|
||||
switch connectionType {
|
||||
case "hwid":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := ctx.Value("hwid").(string)
|
||||
return id, ok
|
||||
}
|
||||
case "mux":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := log.MuxIDFromContext(ctx)
|
||||
@@ -100,19 +115,24 @@ func CreateStrategy(strategy string, connectionType string, lockIDGetter LockIDG
|
||||
}
|
||||
return strconv.FormatUint(uint64(id.ID), 10), ok
|
||||
}
|
||||
case "hwid":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := ctx.Value("hwid").(string)
|
||||
return id, ok
|
||||
}
|
||||
case "ip":
|
||||
case "source_ip":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
return metadata.Source.IPAddr().String(), true
|
||||
}
|
||||
case "default", "":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := log.IDFromContext(ctx)
|
||||
if !ok {
|
||||
return "", ok
|
||||
}
|
||||
return strconv.FormatUint(uint64(id.ID), 10), ok
|
||||
}
|
||||
default:
|
||||
return nil, E.New("connection type not found: ", connectionType)
|
||||
}
|
||||
return NewDefaultConnectionStrategy(connIDGetter, lockIDGetter), nil
|
||||
case "bypass":
|
||||
return NewBypassConnectionStrategy(), nil
|
||||
default:
|
||||
return nil, E.New("strategy not found: ", strategy)
|
||||
}
|
||||
|
||||
140
protocol/limiter/rate/outbound.go
Normal file
140
protocol/limiter/rate/outbound.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package rate
|
||||
|
||||
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"
|
||||
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.RateLimiterOutboundOptions](registry, C.TypeRateLimiter, NewOutbound)
|
||||
}
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
ctx context.Context
|
||||
outbound adapter.OutboundManager
|
||||
connection adapter.ConnectionManager
|
||||
logger logger.ContextLogger
|
||||
strategy RateStrategy
|
||||
outboundTag string
|
||||
detour adapter.Outbound
|
||||
router *route.Router
|
||||
}
|
||||
|
||||
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RateLimiterOutboundOptions) (adapter.Outbound, error) {
|
||||
if options.Strategy == "" {
|
||||
return nil, E.New("missing strategy")
|
||||
}
|
||||
if options.Route.Final == "" {
|
||||
return nil, E.New("missing final outbound")
|
||||
}
|
||||
var strategy RateStrategy
|
||||
var err error
|
||||
switch options.Strategy {
|
||||
case "users":
|
||||
usersStrategies := make(map[string]RateStrategy, len(options.Users))
|
||||
for _, user := range options.Users {
|
||||
userStrategy, err := CreateStrategy(user.Strategy, user.ConnectionType, int(user.Count), user.Interval.Build())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usersStrategies[user.Name] = userStrategy
|
||||
}
|
||||
strategy = NewUsersRateStrategy(usersStrategies)
|
||||
case "manager":
|
||||
strategy = NewManagerRateStrategy()
|
||||
default:
|
||||
strategy, err = CreateStrategy(options.Strategy, options.ConnectionType, int(options.Count), options.Interval.Build())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
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.TypeRateLimiter, tag, nil, []string{}),
|
||||
ctx: ctx,
|
||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||
connection: service.FromContext[adapter.ConnectionManager](ctx),
|
||||
logger: logger,
|
||||
outboundTag: options.Route.Final,
|
||||
strategy: strategy,
|
||||
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) {
|
||||
if err := h.strategy.request(ctx, adapter.ContextFrom(ctx)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.detour.DialContext(ctx, network, destination)
|
||||
}
|
||||
|
||||
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if err := h.strategy.request(ctx, adapter.ContextFrom(ctx)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.detour.ListenPacket(ctx, destination)
|
||||
}
|
||||
|
||||
func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
if err := h.strategy.request(ctx, &metadata); err != nil {
|
||||
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) {
|
||||
if err := h.strategy.request(ctx, &metadata); err != nil {
|
||||
h.logger.ErrorContext(ctx, err)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
return
|
||||
}
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = h.Type()
|
||||
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (h *Outbound) GetStrategy() RateStrategy {
|
||||
return h.strategy
|
||||
}
|
||||
196
protocol/limiter/rate/strategy.go
Normal file
196
protocol/limiter/rate/strategy.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package rate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AliRizaAynaci/gorl/v2"
|
||||
"github.com/AliRizaAynaci/gorl/v2/core"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/shtorm-7/go-cache/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
ConnIDGetter = func(context.Context, *adapter.InboundContext) (string, bool)
|
||||
RateGetter = func(id string) error
|
||||
|
||||
RateStrategy interface {
|
||||
request(ctx context.Context, metadata *adapter.InboundContext) error
|
||||
}
|
||||
)
|
||||
|
||||
type DefaultRateStrategy struct {
|
||||
limiter core.Limiter
|
||||
queue chan struct{}
|
||||
}
|
||||
|
||||
func NewDefaultRateStrategy(strategy string, count int, interval time.Duration) (*DefaultRateStrategy, error) {
|
||||
limiter, err := gorl.New(core.Config{
|
||||
Strategy: core.StrategyType(strings.ReplaceAll(strategy, "-", "_")),
|
||||
Limit: count,
|
||||
Window: interval,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DefaultRateStrategy{limiter: limiter, queue: make(chan struct{}, 1)}, nil
|
||||
}
|
||||
|
||||
func (s *DefaultRateStrategy) request(ctx context.Context, metadata *adapter.InboundContext) error {
|
||||
select {
|
||||
case s.queue <- struct{}{}:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
defer func() {
|
||||
<-s.queue
|
||||
}()
|
||||
r, err := s.limiter.Allow(ctx, metadata.Destination.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.Allowed {
|
||||
select {
|
||||
case <-time.After(r.RetryAfter):
|
||||
_, err = s.limiter.Allow(ctx, metadata.Destination.String())
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DefaultRateStrategy) close() error {
|
||||
return s.limiter.Close()
|
||||
}
|
||||
|
||||
type ConnectionRateStrategy struct {
|
||||
connIDGetter ConnIDGetter
|
||||
createStrategy func() (*DefaultRateStrategy, error)
|
||||
limiters *cache.Cache[string, *DefaultRateStrategy]
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewConnectionRateStrategy(connIDGetter ConnIDGetter, strategy string, count int, interval time.Duration) (*ConnectionRateStrategy, error) {
|
||||
limiters := cache.New[string, *DefaultRateStrategy](interval, time.Second)
|
||||
limiters.OnEvicted(func(s string, strategy *DefaultRateStrategy) {
|
||||
strategy.close()
|
||||
})
|
||||
return &ConnectionRateStrategy{
|
||||
connIDGetter: connIDGetter,
|
||||
createStrategy: func() (*DefaultRateStrategy, error) {
|
||||
return NewDefaultRateStrategy(strategy, count, interval)
|
||||
},
|
||||
limiters: limiters,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *ConnectionRateStrategy) request(ctx context.Context, metadata *adapter.InboundContext) error {
|
||||
id, ok := s.connIDGetter(ctx, metadata)
|
||||
if !ok {
|
||||
return E.New("id not found")
|
||||
}
|
||||
s.mtx.Lock()
|
||||
strategy, ok := s.limiters.Get(id)
|
||||
if !ok {
|
||||
newStrategy, err := s.createStrategy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.limiters.SetDefault(id, newStrategy)
|
||||
strategy = newStrategy
|
||||
} else {
|
||||
s.limiters.UpdateExpirationDefault(id)
|
||||
}
|
||||
s.mtx.Unlock()
|
||||
return strategy.request(ctx, metadata)
|
||||
}
|
||||
|
||||
type UsersRateStrategy struct {
|
||||
strategies map[string]RateStrategy
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewUsersRateStrategy(strategies map[string]RateStrategy) *UsersRateStrategy {
|
||||
return &UsersRateStrategy{
|
||||
strategies: strategies,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UsersRateStrategy) request(ctx context.Context, metadata *adapter.InboundContext) 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.request(ctx, metadata)
|
||||
}
|
||||
return E.New("user strategy not found: ", user)
|
||||
}
|
||||
|
||||
type ManagerRateStrategy struct {
|
||||
*UsersRateStrategy
|
||||
}
|
||||
|
||||
func NewManagerRateStrategy() *ManagerRateStrategy {
|
||||
return &ManagerRateStrategy{
|
||||
UsersRateStrategy: NewUsersRateStrategy(map[string]RateStrategy{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ManagerRateStrategy) UpdateStrategies(strategies map[string]RateStrategy) {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.strategies = strategies
|
||||
}
|
||||
|
||||
type BypassRateStrategy struct{}
|
||||
|
||||
func NewBypassRateStrategy() *BypassRateStrategy {
|
||||
return &BypassRateStrategy{}
|
||||
}
|
||||
|
||||
func (s *BypassRateStrategy) request(ctx context.Context, metadata *adapter.InboundContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateStrategy(strategy string, connectionType string, count int, interval time.Duration) (RateStrategy, error) {
|
||||
if strategy == "bypass" {
|
||||
return NewBypassRateStrategy(), nil
|
||||
}
|
||||
var connIDGetter ConnIDGetter
|
||||
switch connectionType {
|
||||
case "hwid":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := ctx.Value("hwid").(string)
|
||||
return id, ok
|
||||
}
|
||||
case "mux":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
id, ok := log.MuxIDFromContext(ctx)
|
||||
if !ok {
|
||||
return "", ok
|
||||
}
|
||||
return strconv.FormatUint(uint64(id.ID), 10), ok
|
||||
}
|
||||
case "source_ip":
|
||||
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||
return metadata.Source.IPAddr().String(), true
|
||||
}
|
||||
case "default", "":
|
||||
return NewDefaultRateStrategy(strategy, count, interval)
|
||||
default:
|
||||
return nil, E.New("connection type not found: ", connectionType)
|
||||
}
|
||||
return NewConnectionRateStrategy(connIDGetter, strategy, count, interval)
|
||||
}
|
||||
146
protocol/limiter/traffic/conn.go
Normal file
146
protocol/limiter/traffic/conn.go
Normal 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)
|
||||
}
|
||||
6
protocol/limiter/traffic/limiter.go
Normal file
6
protocol/limiter/traffic/limiter.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package traffic
|
||||
|
||||
type TrafficLimiter interface {
|
||||
Can(n uint64) error
|
||||
Add(n uint64) error
|
||||
}
|
||||
126
protocol/limiter/traffic/outbound.go
Normal file
126
protocol/limiter/traffic/outbound.go
Normal 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
|
||||
}
|
||||
164
protocol/limiter/traffic/strategy.go
Normal file
164
protocol/limiter/traffic/strategy.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
UDPKeepalivePeriod: udpKeepalivePeriod,
|
||||
UDPInitialPacketSize: options.UDPInitialPacketSize,
|
||||
ReconnectDelay: options.ReconnectDelay.Build(),
|
||||
})
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, err)
|
||||
return
|
||||
|
||||
@@ -71,7 +71,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
Version: options.Version,
|
||||
Password: options.Password,
|
||||
Users: common.Map(options.Users, func(it option.ShadowTLSUser) shadowtls.User {
|
||||
return (shadowtls.User)(it)
|
||||
return shadowtls.User(it)
|
||||
}),
|
||||
Handshake: shadowtls.HandshakeConfig{
|
||||
Server: options.Handshake.ServerOptions.Build(),
|
||||
|
||||
@@ -83,7 +83,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
var nfsKey []byte
|
||||
var lastCTR cipher.Stream
|
||||
for j, k := range i.NfsPKeys {
|
||||
var index = 32
|
||||
index := 32
|
||||
if k, ok := k.(*ecdh.PublicKey); ok {
|
||||
privateKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
copy(relays, privateKey.PublicKey().Bytes())
|
||||
|
||||
@@ -143,7 +143,7 @@ func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn
|
||||
if lastCTR != nil {
|
||||
lastCTR.XORKeyStream(relays, relays[:32]) // recover this relay
|
||||
}
|
||||
var index = 32
|
||||
index := 32
|
||||
if _, ok := k.(*mlkem.DecapsulationKey768); ok {
|
||||
index = 1088
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user