mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-26 20:29:03 +03:00
Add Snell protocol. Refactor MASQUE HTTP/2, Fair Queue. Update XHTTP, OpenVPN, Sudoku, Fallback. Fixes
This commit is contained in:
@@ -80,6 +80,7 @@ func (h *Inbound) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
|
||||
func (h *Inbound) Close() error {
|
||||
h.conns.Close()
|
||||
errs := make([]error, 0)
|
||||
for _, inbound := range h.inbounds {
|
||||
err := inbound.Close()
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
@@ -31,14 +32,19 @@ type Fallback struct {
|
||||
tags []string
|
||||
outbounds map[string]adapter.Outbound
|
||||
lastUsedOutbound string
|
||||
|
||||
mtx sync.Mutex
|
||||
blacklistTimeout time.Duration
|
||||
blacklist map[string]time.Time
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewFallback(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.FallbackOutboundOptions) (adapter.Outbound, error) {
|
||||
if len(options.Outbounds) == 0 {
|
||||
return nil, E.New("missing tags")
|
||||
}
|
||||
blacklistTimeout := time.Duration(options.BlacklistTimeout)
|
||||
if blacklistTimeout == 0 {
|
||||
blacklistTimeout = time.Minute
|
||||
}
|
||||
outbound := &Fallback{
|
||||
Adapter: outbound.NewAdapter(C.TypeFallback, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds),
|
||||
ctx: ctx,
|
||||
@@ -47,6 +53,8 @@ func NewFallback(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
tags: options.Outbounds,
|
||||
outbounds: make(map[string]adapter.Outbound, len(options.Outbounds)),
|
||||
lastUsedOutbound: options.Outbounds[0],
|
||||
blacklistTimeout: blacklistTimeout,
|
||||
blacklist: make(map[string]time.Time),
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
@@ -73,35 +81,110 @@ func (s *Fallback) All() []string {
|
||||
}
|
||||
|
||||
func (s *Fallback) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
var conn net.Conn
|
||||
s.mtx.Lock()
|
||||
var active, blacklisted []string
|
||||
for _, tag := range s.tags {
|
||||
if s.isBlacklisted(tag) {
|
||||
blacklisted = append(blacklisted, tag)
|
||||
} else {
|
||||
active = append(active, tag)
|
||||
}
|
||||
}
|
||||
s.mtx.Unlock()
|
||||
|
||||
var err error
|
||||
for _, outbound := range s.outbounds {
|
||||
conn, err = outbound.DialContext(ctx, network, destination)
|
||||
for _, tag := range active {
|
||||
var conn net.Conn
|
||||
conn, err = s.outbounds[tag].DialContext(ctx, network, destination)
|
||||
if err != nil {
|
||||
s.logger.InfoContext(ctx, err)
|
||||
s.mtx.Lock()
|
||||
s.addToBlacklist(tag)
|
||||
s.mtx.Unlock()
|
||||
continue
|
||||
}
|
||||
s.mtx.Lock()
|
||||
s.lastUsedOutbound = tag
|
||||
s.mtx.Unlock()
|
||||
return conn, nil
|
||||
}
|
||||
for _, tag := range blacklisted {
|
||||
var conn net.Conn
|
||||
conn, err = s.outbounds[tag].DialContext(ctx, network, destination)
|
||||
if err != nil {
|
||||
s.logger.InfoContext(ctx, err)
|
||||
continue
|
||||
}
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.lastUsedOutbound = outbound.Tag()
|
||||
delete(s.blacklist, tag)
|
||||
s.lastUsedOutbound = tag
|
||||
s.mtx.Unlock()
|
||||
return conn, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *Fallback) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
var conn net.PacketConn
|
||||
s.mtx.Lock()
|
||||
var active, blacklisted []string
|
||||
for _, tag := range s.tags {
|
||||
if s.isBlacklisted(tag) {
|
||||
blacklisted = append(blacklisted, tag)
|
||||
} else {
|
||||
active = append(active, tag)
|
||||
}
|
||||
}
|
||||
s.mtx.Unlock()
|
||||
|
||||
var err error
|
||||
for _, outbound := range s.outbounds {
|
||||
conn, err = outbound.ListenPacket(ctx, destination)
|
||||
for _, tag := range active {
|
||||
var conn net.PacketConn
|
||||
conn, err = s.outbounds[tag].ListenPacket(ctx, destination)
|
||||
if err != nil {
|
||||
s.logger.InfoContext(ctx, err)
|
||||
s.mtx.Lock()
|
||||
s.addToBlacklist(tag)
|
||||
s.mtx.Unlock()
|
||||
continue
|
||||
}
|
||||
s.mtx.Lock()
|
||||
s.lastUsedOutbound = tag
|
||||
s.mtx.Unlock()
|
||||
return conn, nil
|
||||
}
|
||||
for _, tag := range blacklisted {
|
||||
var conn net.PacketConn
|
||||
conn, err = s.outbounds[tag].ListenPacket(ctx, destination)
|
||||
if err != nil {
|
||||
s.logger.InfoContext(ctx, err)
|
||||
continue
|
||||
}
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.lastUsedOutbound = outbound.Tag()
|
||||
delete(s.blacklist, tag)
|
||||
s.lastUsedOutbound = tag
|
||||
s.mtx.Unlock()
|
||||
return conn, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *Fallback) isBlacklisted(tag string) bool {
|
||||
if s.blacklistTimeout == 0 {
|
||||
return false
|
||||
}
|
||||
expiry, ok := s.blacklist[tag]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if time.Now().After(expiry) {
|
||||
delete(s.blacklist, tag)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Fallback) addToBlacklist(tag string) {
|
||||
if s.blacklistTimeout > 0 {
|
||||
s.blacklist[tag] = time.Now().Add(s.blacklistTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package bandwidth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/list"
|
||||
)
|
||||
|
||||
type BandwidthLimiter interface {
|
||||
@@ -14,123 +14,144 @@ type BandwidthLimiter interface {
|
||||
SetSpeed(speed uint64)
|
||||
}
|
||||
|
||||
type FlowKeysLimiter struct {
|
||||
type FairQueueLimiter struct {
|
||||
limiter BandwidthLimiter
|
||||
connIDGetter ConnIDGetter
|
||||
|
||||
waits map[string][]*wait
|
||||
conns map[string]int
|
||||
flows *list.List[*flow]
|
||||
index map[string]*list.Element[*flow]
|
||||
bytes map[string]uint64
|
||||
pool sync.Pool
|
||||
queue chan struct{}
|
||||
reset time.Time
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewFlowKeysLimiter(connIDGetter ConnIDGetter, limiter BandwidthLimiter) *FlowKeysLimiter {
|
||||
return &FlowKeysLimiter{
|
||||
func NewFairQueueLimiter(connIDGetter ConnIDGetter, limiter BandwidthLimiter) *FairQueueLimiter {
|
||||
return &FairQueueLimiter{
|
||||
limiter: limiter,
|
||||
connIDGetter: connIDGetter,
|
||||
waits: make(map[string][]*wait),
|
||||
conns: make(map[string]int),
|
||||
flows: list.New[*flow](),
|
||||
index: make(map[string]*list.Element[*flow]),
|
||||
bytes: make(map[string]uint64),
|
||||
pool: sync.Pool{New: func() any { return list.New[*request]() }},
|
||||
queue: make(chan struct{}, 1),
|
||||
reset: time.Now().Add(time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *FlowKeysLimiter) SetSpeed(speed uint64) {
|
||||
func (l *FairQueueLimiter) SetSpeed(speed uint64) {
|
||||
l.limiter.SetSpeed(speed)
|
||||
}
|
||||
|
||||
func (l *FlowKeysLimiter) WaitN(ctx context.Context, n int) error {
|
||||
func (l *FairQueueLimiter) WaitN(ctx context.Context, n int) error {
|
||||
id, _ := l.connIDGetter(ctx, adapter.ContextFrom(ctx))
|
||||
mainWait := &wait{ctx, make(chan struct{}), n}
|
||||
mainRequest := &request{ctx: ctx, done: make(chan struct{}), n: n}
|
||||
l.mtx.Lock()
|
||||
if waits, ok := l.waits[id]; ok {
|
||||
l.waits[id] = append(waits, mainWait)
|
||||
} else {
|
||||
l.waits[id] = []*wait{mainWait}
|
||||
elem, ok := l.index[id]
|
||||
if !ok {
|
||||
f := &flow{id: id, pending: l.pool.Get().(*list.List[*request])}
|
||||
elem = l.flows.PushFront(f)
|
||||
l.index[id] = elem
|
||||
}
|
||||
mainRequestElem := elem.Value.pending.PushBack(mainRequest)
|
||||
l.reorder(elem)
|
||||
l.mtx.Unlock()
|
||||
select {
|
||||
case l.queue <- struct{}{}:
|
||||
case <-mainWait.finish:
|
||||
case <-mainRequest.done:
|
||||
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.removeRequest(id, mainRequestElem)
|
||||
l.mtx.Unlock()
|
||||
return ctx.Err()
|
||||
}
|
||||
select {
|
||||
case <-mainRequest.done:
|
||||
<-l.queue
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
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.removeRequest(id, mainRequestElem)
|
||||
l.mtx.Unlock()
|
||||
<-l.queue
|
||||
return ctx.Err()
|
||||
}
|
||||
l.mtx.Lock()
|
||||
now := time.Now()
|
||||
if l.reset.Compare(now) == -1 {
|
||||
clear(l.conns)
|
||||
clear(l.bytes)
|
||||
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)
|
||||
flowElem := l.flows.Front()
|
||||
flow := flowElem.Value
|
||||
firstRequestElem := flow.pending.Front()
|
||||
firstRequest := firstRequestElem.Value
|
||||
l.bytes[flow.id] += uint64(firstRequest.n)
|
||||
firstRequestElem.Remove()
|
||||
if flow.pending.Len() == 0 {
|
||||
l.flows.Remove(flowElem)
|
||||
delete(l.index, flow.id)
|
||||
l.pool.Put(flow.pending)
|
||||
} else {
|
||||
l.reorder(flowElem)
|
||||
}
|
||||
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.limiter.WaitN(firstRequest.ctx, firstRequest.n)
|
||||
close(firstRequest.done)
|
||||
if firstRequest == mainRequest {
|
||||
<-l.queue
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type wait struct {
|
||||
ctx context.Context
|
||||
finish chan struct{}
|
||||
n int
|
||||
func (l *FairQueueLimiter) reorder(elem *list.Element[*flow]) {
|
||||
f := elem.Value
|
||||
front := f.pending.Front()
|
||||
if front == nil {
|
||||
return
|
||||
}
|
||||
cost := l.bytes[f.id] + uint64(front.Value.n)
|
||||
for e := l.flows.Front(); e != nil; e = e.Next() {
|
||||
if e == elem {
|
||||
continue
|
||||
}
|
||||
eFront := e.Value.pending.Front()
|
||||
if eFront == nil {
|
||||
continue
|
||||
}
|
||||
if cost < l.bytes[e.Value.id]+uint64(eFront.Value.n) {
|
||||
l.flows.MoveBefore(elem, e)
|
||||
return
|
||||
}
|
||||
}
|
||||
l.flows.MoveToBack(elem)
|
||||
}
|
||||
|
||||
func (l *FairQueueLimiter) removeRequest(id string, elem *list.Element[*request]) {
|
||||
if !elem.Remove() {
|
||||
return
|
||||
}
|
||||
if flowElem, ok := l.index[id]; ok && flowElem.Value.pending.Len() == 0 {
|
||||
l.flows.Remove(flowElem)
|
||||
delete(l.index, id)
|
||||
l.pool.Put(flowElem.Value.pending)
|
||||
}
|
||||
}
|
||||
|
||||
type flow struct {
|
||||
id string
|
||||
pending *list.List[*request]
|
||||
}
|
||||
|
||||
type request struct {
|
||||
ctx context.Context
|
||||
done chan struct{}
|
||||
n int
|
||||
}
|
||||
|
||||
@@ -357,7 +357,7 @@ func createSpeedLimiter(speed uint64, flowKeys []string) (BandwidthLimiter, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limiter = NewFlowKeysLimiter(getter, limiter)
|
||||
limiter = NewFairQueueLimiter(getter, limiter)
|
||||
}
|
||||
return limiter, nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"github.com/sagernet/sing-box/common/cloudflare"
|
||||
"github.com/sagernet/sing-box/common/congestion"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"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/ntp"
|
||||
"github.com/sagernet/sing/service"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
@@ -132,6 +134,15 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
logger.ErrorContext(ctx, err)
|
||||
return
|
||||
}
|
||||
congestionControl, err := congestion.NewCongestionControl(
|
||||
options.CongestionController,
|
||||
options.CWND,
|
||||
ntp.TimeFuncFromContext(ctx),
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, err)
|
||||
return
|
||||
}
|
||||
tunnel, err := masque.NewTunnel(
|
||||
ctx,
|
||||
logger,
|
||||
@@ -156,6 +167,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
UDPKeepalivePeriod: udpKeepalivePeriod,
|
||||
UDPInitialPacketSize: options.UDPInitialPacketSize,
|
||||
ReconnectDelay: options.ReconnectDelay.Build(),
|
||||
CongestionControl: congestionControl,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -104,6 +104,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
AllowedAddress: options.AllowedIPs,
|
||||
ReconnectDelay: time.Duration(options.ReconnectDelay),
|
||||
PingInterval: time.Duration(options.PingInterval),
|
||||
PingRestart: time.Duration(options.PingRestart),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
130
protocol/snell/inbound.go
Normal file
130
protocol/snell/inbound.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package snell
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/common/listener"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/snell"
|
||||
"github.com/sagernet/sing/common"
|
||||
"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"
|
||||
)
|
||||
|
||||
func RegisterInbound(registry *inbound.Registry) {
|
||||
inbound.Register[option.SnellInboundOptions](registry, C.TypeSnell, NewInbound)
|
||||
}
|
||||
|
||||
type Inbound struct {
|
||||
inbound.Adapter
|
||||
router adapter.ConnectionRouterEx
|
||||
logger logger.ContextLogger
|
||||
listener *listener.Listener
|
||||
service *snell.Service
|
||||
}
|
||||
|
||||
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SnellInboundOptions) (adapter.Inbound, error) {
|
||||
if options.PSK == "" {
|
||||
return nil, E.New("snell requires psk")
|
||||
}
|
||||
udpEnabled := common.Contains(options.Network.Build(), N.NetworkUDP)
|
||||
obfsMode := ""
|
||||
if options.Obfs != nil {
|
||||
obfsMode = options.Obfs.Mode
|
||||
}
|
||||
in := &Inbound{
|
||||
Adapter: inbound.NewAdapter(C.TypeSnell, tag),
|
||||
router: router,
|
||||
logger: logger,
|
||||
}
|
||||
service, err := snell.NewService(snell.ServiceOptions{
|
||||
PSK: []byte(options.PSK),
|
||||
Version: options.Version,
|
||||
ObfsMode: obfsMode,
|
||||
UDP: udpEnabled,
|
||||
Logger: logger,
|
||||
Handler: (*inboundHandler)(in),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
in.service = service
|
||||
in.listener = listener.New(listener.Options{
|
||||
Context: ctx,
|
||||
Logger: logger,
|
||||
Network: []string{N.NetworkTCP},
|
||||
Listen: options.ListenOptions,
|
||||
ConnectionHandler: in,
|
||||
})
|
||||
return in, nil
|
||||
}
|
||||
|
||||
func (h *Inbound) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
return h.listener.Start()
|
||||
}
|
||||
|
||||
func (h *Inbound) Close() error {
|
||||
return h.listener.Close()
|
||||
}
|
||||
|
||||
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
err := h.service.NewConnection(ctx, conn, metadata.Source)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
if err != nil {
|
||||
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
|
||||
}
|
||||
}
|
||||
|
||||
var _ adapter.TCPInjectableInbound = (*Inbound)(nil)
|
||||
|
||||
type inboundHandler Inbound
|
||||
|
||||
func (h *inboundHandler) NewConnection(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, clientID string) {
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = h.Type()
|
||||
metadata.InboundDetour = h.listener.ListenOptions().Detour
|
||||
metadata.Source = source
|
||||
metadata.Destination = destination
|
||||
if clientID != "" {
|
||||
metadata.User = clientID
|
||||
h.logger.InfoContext(ctx, "[", clientID, "] inbound connection to ", destination)
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound connection to ", destination)
|
||||
}
|
||||
done := make(chan struct{})
|
||||
h.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(error) {
|
||||
close(done)
|
||||
}))
|
||||
<-done
|
||||
}
|
||||
|
||||
func (h *inboundHandler) NewPacketConnection(ctx context.Context, conn net.PacketConn, source M.Socksaddr, clientID string) {
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Inbound = h.Tag()
|
||||
metadata.InboundType = h.Type()
|
||||
metadata.InboundDetour = h.listener.ListenOptions().Detour
|
||||
metadata.Source = source
|
||||
if clientID != "" {
|
||||
metadata.User = clientID
|
||||
h.logger.InfoContext(ctx, "[", clientID, "] inbound packet connection")
|
||||
} else {
|
||||
h.logger.InfoContext(ctx, "inbound packet connection")
|
||||
}
|
||||
done := make(chan struct{})
|
||||
h.router.RoutePacketConnectionEx(ctx, bufio.NewPacketConn(conn), metadata, N.OnceClose(func(error) {
|
||||
close(done)
|
||||
}))
|
||||
<-done
|
||||
}
|
||||
114
protocol/snell/outbound.go
Normal file
114
protocol/snell/outbound.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package snell
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/snell"
|
||||
"github.com/sagernet/sing/common"
|
||||
"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"
|
||||
)
|
||||
|
||||
func RegisterOutbound(registry *outbound.Registry) {
|
||||
outbound.Register[option.SnellOutboundOptions](registry, C.TypeSnell, NewOutbound)
|
||||
}
|
||||
|
||||
type Outbound struct {
|
||||
outbound.Adapter
|
||||
logger logger.ContextLogger
|
||||
client *snell.Client
|
||||
}
|
||||
|
||||
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SnellOutboundOptions) (adapter.Outbound, error) {
|
||||
if options.PSK == "" {
|
||||
return nil, E.New("snell requires psk")
|
||||
}
|
||||
version := options.Version
|
||||
if version == 0 {
|
||||
version = snell.DefaultSnellVersion
|
||||
}
|
||||
if version == snell.Version5 {
|
||||
version = snell.Version4
|
||||
}
|
||||
udpEnabled := common.Contains(options.Network.Build(), N.NetworkUDP)
|
||||
switch version {
|
||||
case snell.Version1, snell.Version2:
|
||||
if udpEnabled {
|
||||
return nil, fmt.Errorf("snell version %d does not support UDP", version)
|
||||
}
|
||||
case snell.Version3, snell.Version4:
|
||||
default:
|
||||
return nil, fmt.Errorf("snell version error: %d", version)
|
||||
}
|
||||
reuse := version == snell.Version2 || (version == snell.Version4 && options.Reuse)
|
||||
obfsMode := ""
|
||||
obfsHost := "bing.com"
|
||||
if options.Obfs != nil {
|
||||
switch options.Obfs.Mode {
|
||||
case "", "tls", "http":
|
||||
obfsMode = options.Obfs.Mode
|
||||
default:
|
||||
return nil, fmt.Errorf("snell obfs mode error: %s", options.Obfs.Mode)
|
||||
}
|
||||
if options.Obfs.Host != "" {
|
||||
obfsHost = options.Obfs.Host
|
||||
}
|
||||
}
|
||||
outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := snell.NewClient(snell.ClientOptions{
|
||||
Dialer: outboundDialer,
|
||||
Server: options.ServerOptions.Build(),
|
||||
PSK: []byte(options.PSK),
|
||||
Version: version,
|
||||
Reuse: reuse,
|
||||
ObfsMode: obfsMode,
|
||||
ObfsHost: obfsHost,
|
||||
})
|
||||
return &Outbound{
|
||||
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSnell, tag, options.Network.Build(), options.DialerOptions),
|
||||
logger: logger,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.Outbound = h.Tag()
|
||||
metadata.Destination = destination
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkTCP:
|
||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||
return h.client.DialContext(ctx, destination)
|
||||
case N.NetworkUDP:
|
||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||
conn, err := h.client.ListenPacket(ctx, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bufio.NewBindPacketConn(conn, destination), nil
|
||||
default:
|
||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.Outbound = h.Tag()
|
||||
metadata.Destination = destination
|
||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||
return h.client.ListenPacket(ctx, destination)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/sagernet/quic-go/http3"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/common/congestion"
|
||||
"github.com/sagernet/sing-box/common/listener"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@@ -136,10 +137,9 @@ func (h *Inbound) Start(stage adapter.StartStage) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
congestionControlFactory, err := trusttunnel.NewCongestionControl(
|
||||
congestionControlFactory, err := congestion.NewCongestionControl(
|
||||
h.options.CongestionController,
|
||||
h.options.CWND,
|
||||
h.options.BBRProfile,
|
||||
ntp.TimeFuncFromContext(h.ctx),
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -53,7 +53,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
QUIC: options.QUIC,
|
||||
CongestionControl: options.CongestionController,
|
||||
CWND: options.CWND,
|
||||
BBRProfile: options.BBRProfile,
|
||||
Logger: logger,
|
||||
HealthCheck: options.HealthCheck,
|
||||
}
|
||||
var client trusttunnel.Dialer
|
||||
|
||||
Reference in New Issue
Block a user