manager.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. package aerotun
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "os"
  7. "path/filepath"
  8. "sort"
  9. "sync"
  10. "time"
  11. )
  12. type Manager struct {
  13. mu sync.RWMutex
  14. instances map[string]TunnelInstance
  15. runners map[string]*TunnelRunner
  16. errors map[string]string
  17. filePath string
  18. health *healthMonitor
  19. supervisorStop chan struct{}
  20. }
  21. func NewManager() *Manager { return NewManagerWithPath(StoragePath) }
  22. func NewManagerWithPath(path string) *Manager {
  23. m := &Manager{
  24. instances: make(map[string]TunnelInstance),
  25. runners: make(map[string]*TunnelRunner),
  26. errors: make(map[string]string),
  27. filePath: path,
  28. }
  29. _ = os.MkdirAll(filepath.Dir(m.filePath), 0755)
  30. if err := m.load(); err != nil {
  31. log.Printf("[AeroTUN] Error loading saved instances: %v", err)
  32. }
  33. // Auto-start enabled tunnels
  34. for id, inst := range m.instances {
  35. if !inst.Disabled {
  36. if err := m.startRunnerLocked(id); err != nil {
  37. m.errors[id] = err.Error()
  38. log.Printf("[AeroTUN] Failed to auto-start tunnel %s (%s): %v", inst.Name, id, err)
  39. }
  40. }
  41. }
  42. m.health = newHealthMonitor(filepath.Join(filepath.Dir(path), "aerotun_health.json"))
  43. go m.monitorHealth()
  44. m.supervisorStop = make(chan struct{})
  45. go m.supervisorLoop()
  46. return m
  47. }
  48. func (m *Manager) load() error {
  49. m.mu.Lock()
  50. defer m.mu.Unlock()
  51. data, err := os.ReadFile(m.filePath)
  52. if err != nil {
  53. if os.IsNotExist(err) {
  54. return nil
  55. }
  56. return err
  57. }
  58. var list []TunnelInstance
  59. if err := json.Unmarshal(data, &list); err != nil {
  60. return err
  61. }
  62. for _, inst := range list {
  63. m.instances[inst.ID] = inst
  64. }
  65. return nil
  66. }
  67. func (m *Manager) saveLocked() error {
  68. list := make([]TunnelInstance, 0, len(m.instances))
  69. for _, inst := range m.instances {
  70. // Do not persist volatile runtime counters
  71. copyInst := inst
  72. copyInst.Running = false
  73. copyInst.Handshakes = 0
  74. copyInst.Backend = ""
  75. copyInst.ConfiguredMTU = 0
  76. copyInst.DropBreakdown = nil
  77. copyInst.HasPSK = false
  78. copyInst.HasPrivateKey = false
  79. copyInst.Connected = false
  80. copyInst.LastError = ""
  81. copyInst.DroppedPackets = 0
  82. copyInst.AuthFailures = 0
  83. copyInst.ReplayDrops = 0
  84. copyInst.ActivePeerEndpoint = ""
  85. copyInst.LastHandshake = ""
  86. copyInst.RxBytes = 0
  87. copyInst.TxBytes = 0
  88. copyInst.RawBytesIn = 0
  89. copyInst.RawBytesOut = 0
  90. copyInst.WireBytesIn = 0
  91. copyInst.WireBytesOut = 0
  92. copyInst.SavedBytes = 0
  93. copyInst.CompressionRatio = ""
  94. copyInst.PacketsRx = 0
  95. copyInst.PacketsTx = 0
  96. copyInst.CompressedPackets = 0
  97. copyInst.RawPackets = 0
  98. list = append(list, copyInst)
  99. }
  100. data, err := json.MarshalIndent(list, "", " ")
  101. if err != nil {
  102. return err
  103. }
  104. f, err := os.CreateTemp(filepath.Dir(m.filePath), ".aerotun-*.json")
  105. if err != nil {
  106. return err
  107. }
  108. defer os.Remove(f.Name())
  109. if err = f.Chmod(0600); err != nil {
  110. f.Close()
  111. return err
  112. }
  113. if _, err = f.Write(data); err != nil {
  114. f.Close()
  115. return err
  116. }
  117. if err = f.Sync(); err != nil {
  118. f.Close()
  119. return err
  120. }
  121. if err = f.Close(); err != nil {
  122. return err
  123. }
  124. return os.Rename(f.Name(), m.filePath)
  125. }
  126. func (m *Manager) List() []TunnelInstance {
  127. m.mu.RLock()
  128. defer m.mu.RUnlock()
  129. result := make([]TunnelInstance, 0, len(m.instances))
  130. for id, inst := range m.instances {
  131. if runner, ok := m.runners[id]; ok && runner != nil {
  132. tele := runner.GetTelemetry()
  133. tele.Disabled = inst.Disabled
  134. tele.Comment = inst.Comment
  135. result = append(result, tele)
  136. } else {
  137. inst.Running = false
  138. inst.Connected = false
  139. inst.LastError = m.errors[id]
  140. result = append(result, inst)
  141. }
  142. }
  143. sort.Slice(result, func(i, j int) bool {
  144. return result[i].Name < result[j].Name
  145. })
  146. return result
  147. }
  148. func (m *Manager) Get(id string) (TunnelInstance, bool) {
  149. m.mu.RLock()
  150. defer m.mu.RUnlock()
  151. inst, exists := m.instances[id]
  152. if !exists {
  153. return TunnelInstance{}, false
  154. }
  155. if runner, ok := m.runners[id]; ok && runner != nil {
  156. tele := runner.GetTelemetry()
  157. tele.Disabled = inst.Disabled
  158. tele.Comment = inst.Comment
  159. return tele, true
  160. }
  161. inst.Running = false
  162. inst.Connected = false
  163. inst.LastError = m.errors[id]
  164. return inst, true
  165. }
  166. func (m *Manager) Save(inst TunnelInstance) (TunnelInstance, error) {
  167. m.mu.Lock()
  168. defer m.mu.Unlock()
  169. if inst.ID == "" {
  170. inst.ID = fmt.Sprintf("aerotun-%d", time.Now().UnixNano())
  171. }
  172. if inst.Name == "" {
  173. inst.Name = fmt.Sprintf("aerotun%d", len(m.instances))
  174. }
  175. if inst.MTU == 0 {
  176. inst.MTU = DefaultMTU
  177. }
  178. if inst.Mode == "server" && inst.ListenPort == 0 {
  179. inst.ListenPort = DefaultPort
  180. }
  181. if inst.CreatedAt.IsZero() {
  182. inst.CreatedAt = time.Now()
  183. }
  184. if old, ok := m.instances[inst.ID]; ok {
  185. if inst.PSK == "" {
  186. inst.PSK = old.PSK
  187. }
  188. if inst.PrivateKey == "" {
  189. inst.PrivateKey = old.PrivateKey
  190. inst.PublicKey = old.PublicKey
  191. }
  192. }
  193. if inst.Protocol == 3 && inst.PrivateKey == "" {
  194. var err error
  195. inst.PrivateKey, inst.PublicKey, err = GenerateIdentity()
  196. if err != nil {
  197. return inst, err
  198. }
  199. }
  200. if err := validateConfig(&inst); err != nil {
  201. return inst, err
  202. }
  203. for id, existing := range m.instances {
  204. if id != inst.ID && existing.Name == inst.Name {
  205. return inst, fmt.Errorf("interface name already configured: %s", inst.Name)
  206. }
  207. }
  208. previous, existed := m.instances[inst.ID]
  209. m.instances[inst.ID] = inst
  210. if err := m.saveLocked(); err != nil {
  211. if existed {
  212. m.instances[inst.ID] = previous
  213. } else {
  214. delete(m.instances, inst.ID)
  215. }
  216. return inst, fmt.Errorf("failed to save configuration: %w", err)
  217. }
  218. if runner := m.runners[inst.ID]; runner != nil {
  219. runner.Stop()
  220. delete(m.runners, inst.ID)
  221. }
  222. delete(m.errors, inst.ID)
  223. // Restart if not disabled
  224. if !inst.Disabled {
  225. if err := m.startRunnerLocked(inst.ID); err != nil {
  226. m.errors[inst.ID] = err.Error()
  227. return inst, fmt.Errorf("configuration saved but tunnel failed to start: %w", err)
  228. }
  229. }
  230. if runner, ok := m.runners[inst.ID]; ok && runner != nil {
  231. return runner.GetTelemetry(), nil
  232. }
  233. return inst, nil
  234. }
  235. func (m *Manager) Delete(id string) error {
  236. m.mu.Lock()
  237. defer m.mu.Unlock()
  238. if runner, exists := m.runners[id]; exists && runner != nil {
  239. runner.Stop()
  240. delete(m.runners, id)
  241. }
  242. delete(m.instances, id)
  243. delete(m.errors, id)
  244. return m.saveLocked()
  245. }
  246. func (m *Manager) StartTunnel(id string) error {
  247. m.mu.Lock()
  248. defer m.mu.Unlock()
  249. return m.toggleStateLocked(id, false)
  250. }
  251. func (m *Manager) StopTunnel(id string) error {
  252. m.mu.Lock()
  253. defer m.mu.Unlock()
  254. if runner, exists := m.runners[id]; exists && runner != nil {
  255. runner.Stop()
  256. delete(m.runners, id)
  257. }
  258. return nil
  259. }
  260. func (m *Manager) ToggleState(id string, disabled bool) error {
  261. m.mu.Lock()
  262. defer m.mu.Unlock()
  263. return m.toggleStateLocked(id, disabled)
  264. }
  265. func (m *Manager) toggleStateLocked(id string, disabled bool) error {
  266. inst, exists := m.instances[id]
  267. if !exists {
  268. return fmt.Errorf("tunnel not found: %s", id)
  269. }
  270. previous := inst
  271. inst.Disabled = disabled
  272. m.instances[id] = inst
  273. if err := m.saveLocked(); err != nil {
  274. m.instances[id] = previous
  275. return err
  276. }
  277. if disabled {
  278. if runner := m.runners[id]; runner != nil {
  279. runner.Stop()
  280. delete(m.runners, id)
  281. }
  282. delete(m.errors, id)
  283. return nil
  284. }
  285. return m.startRunnerLocked(id)
  286. }
  287. func (m *Manager) startRunnerLocked(id string) error {
  288. inst, exists := m.instances[id]
  289. if !exists {
  290. return fmt.Errorf("tunnel not found: %s", id)
  291. }
  292. if runner, exists := m.runners[id]; exists && runner != nil {
  293. runner.Stop()
  294. delete(m.runners, id)
  295. }
  296. delete(m.errors, id)
  297. runner, err := NewTunnelRunner(inst)
  298. if err != nil {
  299. m.errors[id] = err.Error()
  300. return err
  301. }
  302. if err := runner.Start(); err != nil {
  303. runner.Stop()
  304. m.errors[id] = err.Error()
  305. return err
  306. }
  307. m.runners[id] = runner
  308. return nil
  309. }
  310. func (m *Manager) StopAll() {
  311. if m.supervisorStop != nil {
  312. select {
  313. case <-m.supervisorStop:
  314. default:
  315. close(m.supervisorStop)
  316. }
  317. }
  318. if m.health != nil {
  319. m.health.stop()
  320. }
  321. m.mu.Lock()
  322. defer m.mu.Unlock()
  323. for id, runner := range m.runners {
  324. if runner != nil {
  325. runner.Stop()
  326. }
  327. delete(m.runners, id)
  328. }
  329. }
  330. func (m *Manager) supervisorLoop() {
  331. ticker := time.NewTicker(3 * time.Second)
  332. defer ticker.Stop()
  333. for {
  334. select {
  335. case <-m.supervisorStop:
  336. return
  337. case <-ticker.C:
  338. m.mu.Lock()
  339. for id, inst := range m.instances {
  340. if !inst.Disabled {
  341. runner := m.runners[id]
  342. if runner == nil || !runner.IsRunning() {
  343. if err := m.startRunnerLocked(id); err != nil {
  344. m.errors[id] = err.Error()
  345. } else {
  346. delete(m.errors, id)
  347. log.Printf("[AeroTUN] Auto-started tunnel %s (%s)", inst.Name, id)
  348. }
  349. }
  350. }
  351. }
  352. m.mu.Unlock()
  353. }
  354. }
  355. }
  356. func (m *Manager) GetSummaryStats() SummaryStats {
  357. tunnels := m.List()
  358. stats := SummaryStats{
  359. TotalTunnels: len(tunnels),
  360. AlgorithmCrypto: "XChaCha20-Poly1305 AEAD (v3: Noise IKpsk2)",
  361. AlgorithmCompress: "Adaptive LZ4 (Dynamic Threshold)",
  362. }
  363. var totalRawOut, totalWireOut uint64
  364. for _, t := range tunnels {
  365. if t.Connected {
  366. stats.ActiveTunnels++
  367. }
  368. stats.TotalRxBytes += t.RxBytes
  369. stats.TotalTxBytes += t.TxBytes
  370. stats.TotalSavedBytes += t.SavedBytes
  371. totalRawOut += t.RawBytesOut
  372. totalWireOut += t.WireBytesOut
  373. }
  374. if totalRawOut > 0 && totalWireOut > 0 {
  375. ratio := float64(totalRawOut) / float64(totalWireOut)
  376. if ratio > 1.0 {
  377. savedPct := (1.0 - (float64(totalWireOut) / float64(totalRawOut))) * 100.0
  378. stats.AverageRatio = fmt.Sprintf("%.2fx (%.1f%% saved)", ratio, savedPct)
  379. } else {
  380. stats.AverageRatio = "1.00x (0% saved)"
  381. }
  382. } else {
  383. stats.AverageRatio = "1.00x (0% saved)"
  384. }
  385. return stats
  386. }