mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-23 01:27:04 +00:00
https://github.com/XTLS/Xray-core/pull/5414#issuecomment-3796734827 https://github.com/XTLS/Xray-core/pull/5581#issuecomment-3797134147 https://github.com/XTLS/Xray-core/pull/5645#issuecomment-3899873945 https://github.com/XTLS/Xray-core/pull/6745#issuecomment-5627420177 https://github.com/XTLS/Xray-core/pull/6748#issuecomment-5740443122 --------- Co-authored-by: Risaro <62798663+Risaro@users.noreply.github.com>
309 lines
6.9 KiB
Go
309 lines
6.9 KiB
Go
package xdrive
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/xtls/xray-core/common"
|
|
"github.com/xtls/xray-core/common/errors"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/transport/internet"
|
|
"github.com/xtls/xray-core/transport/internet/stat"
|
|
)
|
|
|
|
const (
|
|
protocolName = "xdrive"
|
|
sessionsDir = "sessions"
|
|
streamsDir = "streams"
|
|
uplinkDir = "c2s"
|
|
downlinkDir = "s2c"
|
|
)
|
|
|
|
func init() {
|
|
common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {
|
|
return new(Config)
|
|
}))
|
|
common.Must(internet.RegisterTransportDialer(protocolName, Dial))
|
|
common.Must(internet.RegisterTransportListener(protocolName, Serve))
|
|
}
|
|
|
|
func newSessionID() (string, error) {
|
|
buf := make([]byte, 16)
|
|
if _, err := rand.Read(buf); err != nil {
|
|
return "", errors.New("failed to generate session id").Base(err)
|
|
}
|
|
return hex.EncodeToString(buf), nil
|
|
}
|
|
|
|
func announceName(session string, at time.Time) string {
|
|
return fmt.Sprintf("%s/%d-%s", sessionsDir, at.UnixNano(), session)
|
|
}
|
|
|
|
func parseAnnounce(entry string) (string, time.Time, bool) {
|
|
dash := strings.IndexByte(entry, '-')
|
|
if dash <= 0 || dash == len(entry)-1 {
|
|
return "", time.Time{}, false
|
|
}
|
|
nanos, err := strconv.ParseInt(entry[:dash], 10, 64)
|
|
if err != nil {
|
|
return "", time.Time{}, false
|
|
}
|
|
return entry[dash+1:], time.Unix(0, nanos), true
|
|
}
|
|
|
|
func sessionPrefix(session string) string {
|
|
return streamsDir + "/" + session
|
|
}
|
|
|
|
func uplinkPrefix(session string) string {
|
|
return sessionPrefix(session) + "/" + uplinkDir
|
|
}
|
|
|
|
func downlinkPrefix(session string) string {
|
|
return sessionPrefix(session) + "/" + downlinkDir
|
|
}
|
|
|
|
func streamConfig(streamSettings *internet.MemoryStreamConfig) (*Config, error) {
|
|
config, ok := streamSettings.ProtocolSettings.(*Config)
|
|
if !ok || config == nil {
|
|
return nil, errors.New("invalid protocol settings")
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
|
|
config, err := streamConfig(streamSettings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
storage, err := newStorage(streamSettings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
session, err := newSessionID()
|
|
if err != nil {
|
|
storage.Close()
|
|
return nil, err
|
|
}
|
|
|
|
if err := storage.Put(ctx, announceName(session, time.Now()), nil); err != nil {
|
|
storage.Close()
|
|
return nil, errors.New("failed to announce session ", session).Base(err)
|
|
}
|
|
|
|
errors.LogInfo(ctx, "opened session ", session)
|
|
|
|
return newConn(context.Background(), storage,
|
|
uplinkPrefix(session), downlinkPrefix(session), paramsFromConfig(config), func() {
|
|
storage.Close()
|
|
}), nil
|
|
}
|
|
|
|
type Listener struct {
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
storage Storage
|
|
addConn internet.ConnHandler
|
|
params
|
|
|
|
mu sync.Mutex
|
|
active map[string]bool
|
|
handled map[string]time.Time
|
|
idleSince map[string]time.Time
|
|
}
|
|
|
|
func Serve(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {
|
|
config, err := streamConfig(streamSettings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
storage, err := newStorage(streamSettings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
listenerCtx, cancel := context.WithCancel(context.Background())
|
|
listener := &Listener{
|
|
ctx: listenerCtx,
|
|
cancel: cancel,
|
|
storage: storage,
|
|
addConn: addConn,
|
|
params: paramsFromConfig(config),
|
|
active: make(map[string]bool),
|
|
handled: make(map[string]time.Time),
|
|
idleSince: make(map[string]time.Time),
|
|
}
|
|
|
|
go listener.acceptLoop(ctx)
|
|
go listener.collectLoop(ctx)
|
|
|
|
return listener, nil
|
|
}
|
|
|
|
func (l *Listener) acceptLoop(logCtx context.Context) {
|
|
delay := l.minPollInterval
|
|
active := time.Now()
|
|
for {
|
|
accepted, err := l.acceptPending(logCtx)
|
|
if err != nil {
|
|
errors.LogWarningInner(logCtx, err, "failed to list sessions")
|
|
}
|
|
|
|
switch {
|
|
case accepted:
|
|
active = time.Now()
|
|
delay = l.minPollInterval
|
|
case time.Since(active) < l.eagerWindow:
|
|
delay = l.minPollInterval
|
|
default:
|
|
delay *= 2
|
|
if delay > l.maxPollInterval {
|
|
delay = l.maxPollInterval
|
|
}
|
|
}
|
|
|
|
select {
|
|
case <-l.ctx.Done():
|
|
return
|
|
case <-time.After(delay):
|
|
}
|
|
}
|
|
}
|
|
|
|
func (l *Listener) acceptPending(logCtx context.Context) (bool, error) {
|
|
sessions, err := l.storage.List(l.ctx, sessionsDir)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
accepted := false
|
|
for _, listed := range sessions {
|
|
entry := listed.Name
|
|
full := sessionsDir + "/" + entry
|
|
|
|
session, at, ok := parseAnnounce(entry)
|
|
if !ok {
|
|
go l.drop(full)
|
|
continue
|
|
}
|
|
if time.Since(at) > l.sessionTTL {
|
|
errors.LogInfo(logCtx, "dropping the stale announcement of session ", session)
|
|
go l.drop(full)
|
|
go l.drop(sessionPrefix(session))
|
|
continue
|
|
}
|
|
if !l.claim(session) {
|
|
continue
|
|
}
|
|
go l.drop(full)
|
|
errors.LogInfo(logCtx, "accepted session ", session)
|
|
accepted = true
|
|
l.addConn(l.newSessionConn(session))
|
|
}
|
|
return accepted, nil
|
|
}
|
|
|
|
func (l *Listener) drop(name string) {
|
|
l.storage.Delete(l.ctx, name)
|
|
}
|
|
|
|
func (l *Listener) claim(session string) bool {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
if l.active[session] || !l.handled[session].IsZero() {
|
|
return false
|
|
}
|
|
l.active[session] = true
|
|
l.handled[session] = time.Now()
|
|
return true
|
|
}
|
|
|
|
func (l *Listener) newSessionConn(session string) *Conn {
|
|
return newConn(l.ctx, l.storage,
|
|
downlinkPrefix(session), uplinkPrefix(session), l.params, func() {
|
|
l.mu.Lock()
|
|
delete(l.active, session)
|
|
l.mu.Unlock()
|
|
})
|
|
}
|
|
|
|
func (l *Listener) collectLoop(logCtx context.Context) {
|
|
interval := l.sessionTTL / 2
|
|
for {
|
|
select {
|
|
case <-l.ctx.Done():
|
|
return
|
|
case <-time.After(interval):
|
|
}
|
|
if err := l.collect(); err != nil {
|
|
errors.LogWarningInner(logCtx, err, "failed to collect abandoned sessions")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (l *Listener) collect() error {
|
|
sessions, err := l.storage.List(l.ctx, streamsDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
now := time.Now()
|
|
var expired []string
|
|
|
|
l.mu.Lock()
|
|
present := make(map[string]bool, len(sessions))
|
|
for _, listed := range sessions {
|
|
session := listed.Name
|
|
present[session] = true
|
|
if l.active[session] {
|
|
delete(l.idleSince, session)
|
|
continue
|
|
}
|
|
since, seen := l.idleSince[session]
|
|
if !seen {
|
|
l.idleSince[session] = now
|
|
continue
|
|
}
|
|
if now.Sub(since) >= l.sessionTTL {
|
|
expired = append(expired, session)
|
|
delete(l.idleSince, session)
|
|
}
|
|
}
|
|
for session := range l.idleSince {
|
|
if !present[session] {
|
|
delete(l.idleSince, session)
|
|
}
|
|
}
|
|
for session, at := range l.handled {
|
|
if !l.active[session] && now.Sub(at) >= l.sessionTTL {
|
|
delete(l.handled, session)
|
|
}
|
|
}
|
|
l.mu.Unlock()
|
|
|
|
for _, session := range expired {
|
|
if err := l.storage.Delete(l.ctx, sessionPrefix(session)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Listener) Addr() net.Addr {
|
|
return placeholderAddr
|
|
}
|
|
|
|
func (l *Listener) Close() error {
|
|
l.cancel()
|
|
return l.storage.Close()
|
|
}
|