wireguard.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. package vpn
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "path/filepath"
  8. "strconv"
  9. "strings"
  10. "sync"
  11. "time"
  12. "github.com/vishvananda/netlink"
  13. )
  14. const (
  15. WGInterfacesStorePath = "/etc/proxmox-router/vpn/wireguard_interfaces.json"
  16. WGPeersStorePath = "/etc/proxmox-router/vpn/wireguard_peers.json"
  17. )
  18. type WireGuardManager struct {
  19. mu sync.Mutex
  20. interfaces []WireGuardInterface
  21. peers []WireGuardPeer
  22. }
  23. func NewWireGuardManager() *WireGuardManager {
  24. m := &WireGuardManager{
  25. interfaces: make([]WireGuardInterface, 0),
  26. peers: make([]WireGuardPeer, 0),
  27. }
  28. m.load()
  29. return m
  30. }
  31. func (m *WireGuardManager) load() {
  32. if data, err := os.ReadFile(WGInterfacesStorePath); err == nil {
  33. _ = json.Unmarshal(data, &m.interfaces)
  34. }
  35. if data, err := os.ReadFile(WGPeersStorePath); err == nil {
  36. _ = json.Unmarshal(data, &m.peers)
  37. }
  38. if m.interfaces == nil {
  39. m.interfaces = make([]WireGuardInterface, 0)
  40. }
  41. if m.peers == nil {
  42. m.peers = make([]WireGuardPeer, 0)
  43. }
  44. }
  45. func (m *WireGuardManager) save() error {
  46. _ = os.MkdirAll(filepath.Dir(WGInterfacesStorePath), 0755)
  47. if data, err := json.MarshalIndent(m.interfaces, "", " "); err == nil {
  48. _ = os.WriteFile(WGInterfacesStorePath, data, 0600)
  49. }
  50. if data, err := json.MarshalIndent(m.peers, "", " "); err == nil {
  51. _ = os.WriteFile(WGPeersStorePath, data, 0600)
  52. }
  53. return nil
  54. }
  55. func (m *WireGuardManager) GenerateKeyPair() (string, string, error) {
  56. privCmd := exec.Command("wg", "genkey")
  57. privOut, err := privCmd.Output()
  58. if err != nil {
  59. return "", "", fmt.Errorf("failed to generate private key: %w", err)
  60. }
  61. privKey := strings.TrimSpace(string(privOut))
  62. pubCmd := exec.Command("wg", "pubkey")
  63. pubCmd.Stdin = strings.NewReader(privKey)
  64. pubOut, err := pubCmd.Output()
  65. if err != nil {
  66. return "", "", fmt.Errorf("failed to generate public key: %w", err)
  67. }
  68. pubKey := strings.TrimSpace(string(pubOut))
  69. return privKey, pubKey, nil
  70. }
  71. // ListInterfaces returns all WireGuard interfaces with MikroTik running flags
  72. func (m *WireGuardManager) ListInterfaces() []WireGuardInterface {
  73. m.mu.Lock()
  74. defer m.mu.Unlock()
  75. res := make([]WireGuardInterface, len(m.interfaces))
  76. for i, iface := range m.interfaces {
  77. res[i] = iface
  78. // Check running state via netlink
  79. if _, err := netlink.LinkByName(iface.Name); err == nil {
  80. res[i].Running = true
  81. } else {
  82. res[i].Running = false
  83. }
  84. // Count attached peers
  85. pCount := 0
  86. for _, p := range m.peers {
  87. if p.Interface == iface.Name {
  88. pCount++
  89. }
  90. }
  91. res[i].PeersCount = pCount
  92. }
  93. return res
  94. }
  95. func (m *WireGuardManager) SaveInterface(iface WireGuardInterface) error {
  96. m.mu.Lock()
  97. defer m.mu.Unlock()
  98. if iface.Name == "" {
  99. return fmt.Errorf("interface name is required (e.g. wg0)")
  100. }
  101. if iface.ListenPort <= 0 {
  102. iface.ListenPort = 51820
  103. }
  104. if iface.Address == "" {
  105. iface.Address = "10.10.0.1/24"
  106. }
  107. if iface.MTU <= 0 {
  108. iface.MTU = 1420
  109. }
  110. // Auto generate keys if not provided
  111. if iface.PrivateKey == "" {
  112. priv, pub, err := m.GenerateKeyPair()
  113. if err == nil {
  114. iface.PrivateKey = priv
  115. iface.PublicKey = pub
  116. }
  117. }
  118. found := false
  119. for i, existing := range m.interfaces {
  120. if existing.Name == iface.Name {
  121. m.interfaces[i] = iface
  122. found = true
  123. break
  124. }
  125. }
  126. if !found {
  127. m.interfaces = append(m.interfaces, iface)
  128. }
  129. _ = m.save()
  130. _ = m.generateWGConfigFile(iface.Name)
  131. if !iface.Disabled {
  132. go m.startWGInterface(iface.Name)
  133. } else {
  134. go m.stopWGInterface(iface.Name)
  135. }
  136. return nil
  137. }
  138. func (m *WireGuardManager) DeleteInterface(name string) error {
  139. m.mu.Lock()
  140. defer m.mu.Unlock()
  141. _ = m.stopWGInterface(name)
  142. _ = os.Remove(fmt.Sprintf("/etc/wireguard/%s.conf", name))
  143. updatedIfaces := make([]WireGuardInterface, 0)
  144. for _, iface := range m.interfaces {
  145. if iface.Name != name {
  146. updatedIfaces = append(updatedIfaces, iface)
  147. }
  148. }
  149. m.interfaces = updatedIfaces
  150. // Remove associated peers
  151. updatedPeers := make([]WireGuardPeer, 0)
  152. for _, p := range m.peers {
  153. if p.Interface != name {
  154. updatedPeers = append(updatedPeers, p)
  155. }
  156. }
  157. m.peers = updatedPeers
  158. return m.save()
  159. }
  160. func (m *WireGuardManager) SetInterfaceEnabled(name string, enabled bool) error {
  161. m.mu.Lock()
  162. defer m.mu.Unlock()
  163. for i, iface := range m.interfaces {
  164. if iface.Name == name {
  165. m.interfaces[i].Disabled = !enabled
  166. _ = m.save()
  167. if enabled {
  168. go m.startWGInterface(name)
  169. } else {
  170. go m.stopWGInterface(name)
  171. }
  172. return nil
  173. }
  174. }
  175. return fmt.Errorf("interface %q not found", name)
  176. }
  177. func (m *WireGuardManager) StartInterface(name string) error {
  178. m.mu.Lock()
  179. defer m.mu.Unlock()
  180. return m.startWGInterface(name)
  181. }
  182. func (m *WireGuardManager) StopInterface(name string) error {
  183. m.mu.Lock()
  184. defer m.mu.Unlock()
  185. return m.stopWGInterface(name)
  186. }
  187. func (m *WireGuardManager) startWGInterface(name string) error {
  188. _ = m.generateWGConfigFile(name)
  189. _ = exec.Command("wg-quick", "down", name).Run()
  190. cmd := exec.Command("wg-quick", "up", name)
  191. if out, err := cmd.CombinedOutput(); err != nil {
  192. return fmt.Errorf("failed to start %s: %s: %w", name, string(out), err)
  193. }
  194. return nil
  195. }
  196. func (m *WireGuardManager) stopWGInterface(name string) error {
  197. cmd := exec.Command("wg-quick", "down", name)
  198. _ = cmd.Run()
  199. return nil
  200. }
  201. // ----------------- Peers Management (MikroTik style) -----------------
  202. func (m *WireGuardManager) ListPeers(ifaceFilter string) []WireGuardPeer {
  203. m.mu.Lock()
  204. defer m.mu.Unlock()
  205. // Parse runtime dump from `wg show <iface> dump`
  206. dumpMap := make(map[string]map[string]WireGuardPeer) // iface -> pubkey -> peer stats
  207. for _, iface := range m.interfaces {
  208. if _, err := netlink.LinkByName(iface.Name); err == nil {
  209. cmd := exec.Command("wg", "show", iface.Name, "dump")
  210. out, err := cmd.Output()
  211. if err == nil {
  212. dumpMap[iface.Name] = make(map[string]WireGuardPeer)
  213. lines := strings.Split(strings.TrimSpace(string(out)), "\n")
  214. for idx, line := range lines {
  215. if idx == 0 {
  216. continue // skip interface line
  217. }
  218. parts := strings.Fields(line)
  219. if len(parts) >= 8 {
  220. pubKey := parts[0]
  221. endpoint := parts[2]
  222. if endpoint == "(none)" {
  223. endpoint = ""
  224. }
  225. handshakeUnix, _ := strconv.ParseInt(parts[4], 10, 64)
  226. rx, _ := strconv.ParseUint(parts[5], 10, 64)
  227. tx, _ := strconv.ParseUint(parts[6], 10, 64)
  228. handshakeStr := "Never"
  229. if handshakeUnix > 0 {
  230. handshakeStr = time.Since(time.Unix(handshakeUnix, 0)).Round(time.Second).String() + " ago"
  231. }
  232. dumpMap[iface.Name][pubKey] = WireGuardPeer{
  233. EndpointAddress: endpoint,
  234. LatestHandshake: handshakeStr,
  235. TransferRxBytes: rx,
  236. TransferTxBytes: tx,
  237. TransferRxHuman: formatTrafficBytes(rx),
  238. TransferTxHuman: formatTrafficBytes(tx),
  239. }
  240. }
  241. }
  242. }
  243. }
  244. }
  245. result := make([]WireGuardPeer, 0)
  246. for _, p := range m.peers {
  247. if ifaceFilter != "" && p.Interface != ifaceFilter {
  248. continue
  249. }
  250. item := p
  251. // Overlay runtime stats if available
  252. if ifaceDumps, ok := dumpMap[p.Interface]; ok {
  253. if stats, has := ifaceDumps[p.PublicKey]; has {
  254. item.EndpointAddress = stats.EndpointAddress
  255. item.LatestHandshake = stats.LatestHandshake
  256. item.TransferRxBytes = stats.TransferRxBytes
  257. item.TransferTxBytes = stats.TransferTxBytes
  258. item.TransferRxHuman = stats.TransferRxHuman
  259. item.TransferTxHuman = stats.TransferTxHuman
  260. }
  261. }
  262. result = append(result, item)
  263. }
  264. return result
  265. }
  266. func (m *WireGuardManager) SavePeer(peer WireGuardPeer) error {
  267. m.mu.Lock()
  268. defer m.mu.Unlock()
  269. if peer.Interface == "" {
  270. return fmt.Errorf("target WireGuard interface is required")
  271. }
  272. if peer.PublicKey == "" {
  273. return fmt.Errorf("public key is required")
  274. }
  275. found := false
  276. for i, existing := range m.peers {
  277. if existing.PublicKey == peer.PublicKey && existing.Interface == peer.Interface {
  278. m.peers[i] = peer
  279. found = true
  280. break
  281. }
  282. }
  283. if !found {
  284. m.peers = append(m.peers, peer)
  285. }
  286. _ = m.save()
  287. _ = m.generateWGConfigFile(peer.Interface)
  288. // Apply dynamically to kernel if running
  289. if _, err := netlink.LinkByName(peer.Interface); err == nil && !peer.Disabled {
  290. args := []string{"set", peer.Interface, "peer", peer.PublicKey}
  291. if len(peer.AllowedIPs) > 0 {
  292. args = append(args, "allowed-ips", strings.Join(peer.AllowedIPs, ","))
  293. }
  294. if peer.EndpointAddress != "" {
  295. ep := peer.EndpointAddress
  296. if peer.EndpointPort > 0 {
  297. ep = fmt.Sprintf("%s:%d", peer.EndpointAddress, peer.EndpointPort)
  298. }
  299. args = append(args, "endpoint", ep)
  300. }
  301. if peer.PersistentKeepalive > 0 {
  302. args = append(args, "persistent-keepalive", strconv.Itoa(peer.PersistentKeepalive))
  303. }
  304. _ = exec.Command("wg", args...).Run()
  305. }
  306. return nil
  307. }
  308. func (m *WireGuardManager) DeletePeer(ifaceName, pubKey string) error {
  309. m.mu.Lock()
  310. defer m.mu.Unlock()
  311. updated := make([]WireGuardPeer, 0)
  312. for _, p := range m.peers {
  313. if p.Interface == ifaceName && p.PublicKey == pubKey {
  314. continue
  315. }
  316. updated = append(updated, p)
  317. }
  318. m.peers = updated
  319. _ = m.save()
  320. _ = m.generateWGConfigFile(ifaceName)
  321. // Remove from runtime
  322. if _, err := netlink.LinkByName(ifaceName); err == nil {
  323. _ = exec.Command("wg", "set", ifaceName, "peer", pubKey, "remove").Run()
  324. }
  325. return nil
  326. }
  327. func (m *WireGuardManager) generateWGConfigFile(ifaceName string) error {
  328. var targetIface *WireGuardInterface
  329. for _, i := range m.interfaces {
  330. if i.Name == ifaceName {
  331. targetIface = &i
  332. break
  333. }
  334. }
  335. if targetIface == nil {
  336. return nil
  337. }
  338. _ = os.MkdirAll("/etc/wireguard", 0700)
  339. var sb strings.Builder
  340. sb.WriteString(fmt.Sprintf("# MikroTik-Style WireGuard Interface: %s\n", targetIface.Name))
  341. sb.WriteString("[Interface]\n")
  342. sb.WriteString(fmt.Sprintf("PrivateKey = %s\n", targetIface.PrivateKey))
  343. sb.WriteString(fmt.Sprintf("Address = %s\n", targetIface.Address))
  344. sb.WriteString(fmt.Sprintf("ListenPort = %d\n", targetIface.ListenPort))
  345. if targetIface.MTU > 0 {
  346. sb.WriteString(fmt.Sprintf("MTU = %d\n", targetIface.MTU))
  347. }
  348. sb.WriteString("\n")
  349. for _, p := range m.peers {
  350. if p.Interface == ifaceName && !p.Disabled {
  351. sb.WriteString(fmt.Sprintf("# Peer: %s\n", p.Name))
  352. sb.WriteString("[Peer]\n")
  353. sb.WriteString(fmt.Sprintf("PublicKey = %s\n", p.PublicKey))
  354. if len(p.AllowedIPs) > 0 {
  355. sb.WriteString(fmt.Sprintf("AllowedIPs = %s\n", strings.Join(p.AllowedIPs, ", ")))
  356. }
  357. if p.EndpointAddress != "" {
  358. ep := p.EndpointAddress
  359. if p.EndpointPort > 0 {
  360. ep = fmt.Sprintf("%s:%d", p.EndpointAddress, p.EndpointPort)
  361. }
  362. sb.WriteString(fmt.Sprintf("Endpoint = %s\n", ep))
  363. }
  364. if p.PersistentKeepalive > 0 {
  365. sb.WriteString(fmt.Sprintf("PersistentKeepalive = %d\n", p.PersistentKeepalive))
  366. }
  367. if p.PresharedKey != "" {
  368. sb.WriteString(fmt.Sprintf("PresharedKey = %s\n", p.PresharedKey))
  369. }
  370. sb.WriteString("\n")
  371. }
  372. }
  373. path := fmt.Sprintf("/etc/wireguard/%s.conf", ifaceName)
  374. return os.WriteFile(path, []byte(sb.String()), 0600)
  375. }
  376. // GenerateClientConfig creates a ready-to-use client .conf for mobile/desktop
  377. func (m *WireGuardManager) GenerateClientConfig(ifaceName, clientPrivKey, clientAddress, serverEndpoint, dns string) (string, error) {
  378. m.mu.Lock()
  379. defer m.mu.Unlock()
  380. var targetIface *WireGuardInterface
  381. for _, i := range m.interfaces {
  382. if i.Name == ifaceName {
  383. targetIface = &i
  384. break
  385. }
  386. }
  387. if targetIface == nil {
  388. return "", fmt.Errorf("interface %s not found", ifaceName)
  389. }
  390. if dns == "" {
  391. dns = "1.1.1.1, 8.8.8.8"
  392. }
  393. if serverEndpoint == "" {
  394. serverEndpoint = fmt.Sprintf("YOUR_ROUTER_IP:%d", targetIface.ListenPort)
  395. }
  396. var sb strings.Builder
  397. sb.WriteString("[Interface]\n")
  398. sb.WriteString(fmt.Sprintf("PrivateKey = %s\n", clientPrivKey))
  399. sb.WriteString(fmt.Sprintf("Address = %s\n", clientAddress))
  400. sb.WriteString(fmt.Sprintf("DNS = %s\n", dns))
  401. sb.WriteString("\n[Peer]\n")
  402. sb.WriteString(fmt.Sprintf("PublicKey = %s\n", targetIface.PublicKey))
  403. sb.WriteString(fmt.Sprintf("Endpoint = %s\n", serverEndpoint))
  404. sb.WriteString("AllowedIPs = 0.0.0.0/0, ::/0\n")
  405. sb.WriteString("PersistentKeepalive = 25\n")
  406. return sb.String(), nil
  407. }