243 lines
6.4 KiB
Go
243 lines
6.4 KiB
Go
package auth
|
|
|
|
import (
|
|
"container/list"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
|
)
|
|
|
|
const (
|
|
defaultHomeSessionAliasTTL = time.Hour
|
|
homeSessionAliasCleanupOps = 256
|
|
homeSessionAliasSoftLimit = 4096
|
|
)
|
|
|
|
type homeSessionAliasEntry struct {
|
|
canonical string
|
|
expiresAt time.Time
|
|
aliases []string
|
|
}
|
|
|
|
// homeSessionAliasCache reconciles multiple client identifiers for one Home
|
|
// session without changing Home's single-session-ID protocol.
|
|
type homeSessionAliasCache struct {
|
|
mu sync.Mutex
|
|
entries map[string]homeSessionAliasEntry
|
|
groups map[string]homeSessionAliasEntry
|
|
evictionOrder *list.List
|
|
evictionElements map[string]*list.Element
|
|
ops uint64
|
|
}
|
|
|
|
func (c *homeSessionAliasCache) canonical(primary, fallback string, ttl time.Duration, now time.Time) string {
|
|
primary = strings.TrimSpace(primary)
|
|
fallback = strings.TrimSpace(fallback)
|
|
if primary == "" {
|
|
return ""
|
|
}
|
|
if ttl <= 0 {
|
|
ttl = defaultHomeSessionAliasTTL
|
|
}
|
|
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.ensureInitializedLocked()
|
|
c.ops++
|
|
if c.ops%homeSessionAliasCleanupOps == 0 {
|
|
c.cleanupLocked(now)
|
|
}
|
|
|
|
canonical := primary
|
|
aliases := mergeSessionAliases(nil, primary, fallback)
|
|
previousGroups := make(map[string]homeSessionAliasEntry, 2)
|
|
remember := func(entry homeSessionAliasEntry) {
|
|
previousGroups[entry.canonical] = entry
|
|
}
|
|
|
|
primaryFound := false
|
|
canonicalFromLiveAlias := false
|
|
if existing, ok := c.entryLocked(primary, now); ok {
|
|
primaryFound = true
|
|
canonicalFromLiveAlias = true
|
|
canonical = existing.canonical
|
|
remember(existing)
|
|
aliases = mergeSessionAliases(aliases, existing.aliases...)
|
|
}
|
|
if fallback != "" && fallback != primary {
|
|
if existing, ok := c.entryLocked(fallback, now); ok {
|
|
canonicalFromLiveAlias = true
|
|
if !primaryFound {
|
|
canonical = existing.canonical
|
|
}
|
|
remember(existing)
|
|
aliases = mergeSessionAliases(aliases, existing.aliases...)
|
|
}
|
|
}
|
|
if canonicalFromLiveAlias {
|
|
if existing, ok := c.groupLocked(canonical, now); ok {
|
|
remember(existing)
|
|
aliases = mergeSessionAliases(aliases, existing.aliases...)
|
|
}
|
|
}
|
|
if !canonicalFromLiveAlias {
|
|
if _, ok := c.groupLocked(canonical, now); ok {
|
|
return canonical
|
|
}
|
|
}
|
|
aliases = compactHomeSessionAliases(mergeSessionAliases(aliases, canonical))
|
|
for _, previous := range previousGroups {
|
|
c.removeGroupLocked(previous)
|
|
}
|
|
|
|
c.setGroupLocked(homeSessionAliasEntry{
|
|
canonical: canonical,
|
|
expiresAt: now.Add(ttl),
|
|
aliases: aliases,
|
|
})
|
|
c.enforceLimitLocked(homeSessionAliasSoftLimit)
|
|
return canonical
|
|
}
|
|
|
|
func (c *homeSessionAliasCache) ensureInitializedLocked() {
|
|
if c.entries == nil {
|
|
c.entries = make(map[string]homeSessionAliasEntry)
|
|
}
|
|
if c.groups == nil {
|
|
c.groups = make(map[string]homeSessionAliasEntry)
|
|
}
|
|
if c.evictionOrder == nil {
|
|
c.evictionOrder = list.New()
|
|
}
|
|
if c.evictionElements == nil {
|
|
c.evictionElements = make(map[string]*list.Element)
|
|
}
|
|
}
|
|
|
|
func (c *homeSessionAliasCache) entryLocked(alias string, now time.Time) (homeSessionAliasEntry, bool) {
|
|
entry, ok := c.entries[alias]
|
|
if !ok {
|
|
return homeSessionAliasEntry{}, false
|
|
}
|
|
if now.Before(entry.expiresAt) {
|
|
return entry, true
|
|
}
|
|
if group, exists := c.groups[entry.canonical]; exists && sameHomeSessionAliasGroup(group, entry) {
|
|
c.removeGroupLocked(group)
|
|
} else {
|
|
delete(c.entries, alias)
|
|
}
|
|
return homeSessionAliasEntry{}, false
|
|
}
|
|
|
|
func (c *homeSessionAliasCache) groupLocked(canonical string, now time.Time) (homeSessionAliasEntry, bool) {
|
|
entry, ok := c.groups[canonical]
|
|
if !ok {
|
|
return homeSessionAliasEntry{}, false
|
|
}
|
|
if now.Before(entry.expiresAt) {
|
|
return entry, true
|
|
}
|
|
c.removeGroupLocked(entry)
|
|
return homeSessionAliasEntry{}, false
|
|
}
|
|
|
|
func (c *homeSessionAliasCache) setGroupLocked(entry homeSessionAliasEntry) {
|
|
if existing, ok := c.groups[entry.canonical]; ok {
|
|
c.removeGroupLocked(existing)
|
|
}
|
|
entry.aliases = append([]string(nil), entry.aliases...)
|
|
c.groups[entry.canonical] = entry
|
|
for _, alias := range entry.aliases {
|
|
c.entries[alias] = entry
|
|
}
|
|
c.evictionElements[entry.canonical] = c.evictionOrder.PushBack(entry.canonical)
|
|
}
|
|
|
|
func (c *homeSessionAliasCache) removeGroupLocked(entry homeSessionAliasEntry) {
|
|
current, ok := c.groups[entry.canonical]
|
|
if !ok || !sameHomeSessionAliasGroup(current, entry) {
|
|
return
|
|
}
|
|
for _, alias := range current.aliases {
|
|
mapped, exists := c.entries[alias]
|
|
if exists && sameHomeSessionAliasGroup(mapped, current) {
|
|
delete(c.entries, alias)
|
|
}
|
|
}
|
|
delete(c.groups, current.canonical)
|
|
if element, exists := c.evictionElements[current.canonical]; exists {
|
|
c.evictionOrder.Remove(element)
|
|
delete(c.evictionElements, current.canonical)
|
|
}
|
|
}
|
|
|
|
func sameHomeSessionAliasGroup(left, right homeSessionAliasEntry) bool {
|
|
return left.canonical == right.canonical && left.expiresAt.Equal(right.expiresAt) &&
|
|
equalSessionAliases(left.aliases, right.aliases)
|
|
}
|
|
|
|
func (c *homeSessionAliasCache) enforceLimitLocked(limit int) {
|
|
if limit <= 0 {
|
|
return
|
|
}
|
|
for len(c.entries) > limit {
|
|
oldest := c.evictionOrder.Front()
|
|
if oldest == nil {
|
|
return
|
|
}
|
|
canonical, _ := oldest.Value.(string)
|
|
entry, ok := c.groups[canonical]
|
|
if !ok {
|
|
c.evictionOrder.Remove(oldest)
|
|
delete(c.evictionElements, canonical)
|
|
continue
|
|
}
|
|
c.removeGroupLocked(entry)
|
|
}
|
|
}
|
|
|
|
func (c *homeSessionAliasCache) cleanupLocked(now time.Time) {
|
|
for _, entry := range c.groups {
|
|
if !now.Before(entry.expiresAt) {
|
|
c.removeGroupLocked(entry)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *homeSessionAliasCache) clear() {
|
|
c.mu.Lock()
|
|
c.entries = nil
|
|
c.groups = nil
|
|
c.evictionOrder = nil
|
|
c.evictionElements = nil
|
|
c.ops = 0
|
|
c.mu.Unlock()
|
|
}
|
|
|
|
func homeSessionAliasTTL(cfg *internalconfig.Config) time.Duration {
|
|
if cfg == nil {
|
|
return defaultHomeSessionAliasTTL
|
|
}
|
|
raw := strings.TrimSpace(cfg.Routing.SessionAffinityTTL)
|
|
if raw == "" {
|
|
return defaultHomeSessionAliasTTL
|
|
}
|
|
parsed, errParse := time.ParseDuration(raw)
|
|
if errParse != nil || parsed <= 0 {
|
|
return defaultHomeSessionAliasTTL
|
|
}
|
|
return parsed
|
|
}
|
|
|
|
func (m *Manager) homeDispatchSessionID(opts cliproxyexecutor.Options) string {
|
|
primary, fallback := extractSessionIDs(opts.Headers, opts.OriginalRequest, opts.Metadata)
|
|
if primary == "" || m == nil {
|
|
return primary
|
|
}
|
|
cfg, _ := m.runtimeConfig.Load().(*internalconfig.Config)
|
|
return m.homeSessionAliases.canonical(primary, fallback, homeSessionAliasTTL(cfg), time.Now())
|
|
}
|