manager.go 8.6 KB

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