manager.go 8.3 KB

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