mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-27 14:50:52 +00:00
186 lines
4.1 KiB
Go
186 lines
4.1 KiB
Go
package tunnel
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"drip/internal/shared/utils"
|
|
"github.com/gorilla/websocket"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Manager manages all active tunnel connections
|
|
type Manager struct {
|
|
tunnels map[string]*Connection // subdomain -> connection
|
|
mu sync.RWMutex
|
|
used map[string]bool // track used subdomains
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewManager creates a new tunnel manager
|
|
func NewManager(logger *zap.Logger) *Manager {
|
|
return &Manager{
|
|
tunnels: make(map[string]*Connection),
|
|
used: make(map[string]bool),
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Register registers a new tunnel connection
|
|
// Returns the assigned subdomain and any error
|
|
func (m *Manager) Register(conn *websocket.Conn, customSubdomain string) (string, error) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
var subdomain string
|
|
|
|
if customSubdomain != "" {
|
|
// Validate custom subdomain
|
|
if !utils.ValidateSubdomain(customSubdomain) {
|
|
return "", ErrInvalidSubdomain
|
|
}
|
|
if utils.IsReserved(customSubdomain) {
|
|
return "", ErrReservedSubdomain
|
|
}
|
|
if m.used[customSubdomain] {
|
|
return "", ErrSubdomainTaken
|
|
}
|
|
subdomain = customSubdomain
|
|
} else {
|
|
// Generate unique random subdomain
|
|
subdomain = m.generateUniqueSubdomain()
|
|
}
|
|
|
|
// Create connection
|
|
tc := NewConnection(subdomain, conn, m.logger)
|
|
m.tunnels[subdomain] = tc
|
|
m.used[subdomain] = true
|
|
|
|
// Start write pump in background
|
|
go tc.StartWritePump()
|
|
|
|
m.logger.Info("Tunnel registered",
|
|
zap.String("subdomain", subdomain),
|
|
zap.Int("total_tunnels", len(m.tunnels)),
|
|
)
|
|
|
|
return subdomain, nil
|
|
}
|
|
|
|
// Unregister removes a tunnel connection
|
|
func (m *Manager) Unregister(subdomain string) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if tc, ok := m.tunnels[subdomain]; ok {
|
|
tc.Close()
|
|
delete(m.tunnels, subdomain)
|
|
delete(m.used, subdomain)
|
|
|
|
m.logger.Info("Tunnel unregistered",
|
|
zap.String("subdomain", subdomain),
|
|
zap.Int("total_tunnels", len(m.tunnels)),
|
|
)
|
|
}
|
|
}
|
|
|
|
// Get retrieves a tunnel connection by subdomain
|
|
func (m *Manager) Get(subdomain string) (*Connection, bool) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
tc, ok := m.tunnels[subdomain]
|
|
return tc, ok
|
|
}
|
|
|
|
// List returns all active tunnel connections
|
|
func (m *Manager) List() []*Connection {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
connections := make([]*Connection, 0, len(m.tunnels))
|
|
for _, tc := range m.tunnels {
|
|
connections = append(connections, tc)
|
|
}
|
|
return connections
|
|
}
|
|
|
|
// Count returns the number of active tunnels
|
|
func (m *Manager) Count() int {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
return len(m.tunnels)
|
|
}
|
|
|
|
// CleanupStale removes stale connections that haven't been active
|
|
func (m *Manager) CleanupStale(timeout time.Duration) int {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
staleSubdomains := []string{}
|
|
|
|
for subdomain, tc := range m.tunnels {
|
|
if !tc.IsAlive(timeout) {
|
|
staleSubdomains = append(staleSubdomains, subdomain)
|
|
}
|
|
}
|
|
|
|
for _, subdomain := range staleSubdomains {
|
|
if tc, ok := m.tunnels[subdomain]; ok {
|
|
tc.Close()
|
|
delete(m.tunnels, subdomain)
|
|
delete(m.used, subdomain)
|
|
}
|
|
}
|
|
|
|
if len(staleSubdomains) > 0 {
|
|
m.logger.Info("Cleaned up stale tunnels",
|
|
zap.Int("count", len(staleSubdomains)),
|
|
)
|
|
}
|
|
|
|
return len(staleSubdomains)
|
|
}
|
|
|
|
// StartCleanupTask starts a background task to clean up stale connections
|
|
func (m *Manager) StartCleanupTask(interval, timeout time.Duration) {
|
|
ticker := time.NewTicker(interval)
|
|
go func() {
|
|
for range ticker.C {
|
|
m.CleanupStale(timeout)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// generateUniqueSubdomain generates a unique random subdomain
|
|
func (m *Manager) generateUniqueSubdomain() string {
|
|
const maxAttempts = 10
|
|
|
|
for i := 0; i < maxAttempts; i++ {
|
|
subdomain := utils.GenerateSubdomain(6)
|
|
if !m.used[subdomain] && !utils.IsReserved(subdomain) {
|
|
return subdomain
|
|
}
|
|
}
|
|
|
|
// Fallback: use longer subdomain if collision persists
|
|
return utils.GenerateSubdomain(8)
|
|
}
|
|
|
|
// Shutdown gracefully shuts down all tunnels
|
|
func (m *Manager) Shutdown() {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
m.logger.Info("Shutting down tunnel manager",
|
|
zap.Int("active_tunnels", len(m.tunnels)),
|
|
)
|
|
|
|
for _, tc := range m.tunnels {
|
|
tc.Close()
|
|
}
|
|
|
|
m.tunnels = make(map[string]*Connection)
|
|
m.used = make(map[string]bool)
|
|
}
|