wireguard.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  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. pubKey, err := m.CalcPublicKey(privKey)
  63. if err != nil {
  64. return "", "", err
  65. }
  66. return privKey, pubKey, nil
  67. }
  68. func (m *WireGuardManager) CalcPublicKey(privateKey string) (string, error) {
  69. pubCmd := exec.Command("wg", "pubkey")
  70. pubCmd.Stdin = strings.NewReader(strings.TrimSpace(privateKey))
  71. pubOut, err := pubCmd.Output()
  72. if err != nil {
  73. return "", fmt.Errorf("failed to derive public key: %w", err)
  74. }
  75. return strings.TrimSpace(string(pubOut)), nil
  76. }
  77. // ListInterfaces returns all WireGuard interfaces with MikroTik running flags
  78. func (m *WireGuardManager) ListInterfaces() []WireGuardInterface {
  79. m.mu.Lock()
  80. defer m.mu.Unlock()
  81. res := make([]WireGuardInterface, len(m.interfaces))
  82. for i, iface := range m.interfaces {
  83. res[i] = iface
  84. // Check running state via netlink
  85. if _, err := netlink.LinkByName(iface.Name); err == nil {
  86. res[i].Running = true
  87. } else {
  88. res[i].Running = false
  89. }
  90. // Count attached peers
  91. pCount := 0
  92. for _, p := range m.peers {
  93. if p.Interface == iface.Name {
  94. pCount++
  95. }
  96. }
  97. res[i].PeersCount = pCount
  98. }
  99. return res
  100. }
  101. func (m *WireGuardManager) SaveInterface(req SaveWireGuardInterfaceRequest) error {
  102. m.mu.Lock()
  103. defer m.mu.Unlock()
  104. if req.Name == "" {
  105. return fmt.Errorf("interface name is required (e.g. wg0)")
  106. }
  107. if req.ListenPort <= 0 {
  108. req.ListenPort = 51820
  109. }
  110. if req.MTU <= 0 {
  111. req.MTU = 1420
  112. }
  113. // Auto generate or derive public key
  114. if req.PrivateKey != "" {
  115. pub, err := m.CalcPublicKey(req.PrivateKey)
  116. if err == nil && pub != "" {
  117. req.PublicKey = pub
  118. }
  119. } else {
  120. priv, pub, err := m.GenerateKeyPair()
  121. if err == nil {
  122. req.PrivateKey = priv
  123. req.PublicKey = pub
  124. }
  125. }
  126. // Support interface renaming: clean up old interface if renamed
  127. if req.OrigName != "" && req.OrigName != req.Name {
  128. _ = m.stopWGInterface(req.OrigName)
  129. _ = os.Remove(fmt.Sprintf("/etc/wireguard/%s.conf", req.OrigName))
  130. // Update all peers associated with OrigName
  131. for i := range m.peers {
  132. if m.peers[i].Interface == req.OrigName {
  133. m.peers[i].Interface = req.Name
  134. }
  135. }
  136. // Remove old interface from slice
  137. updated := make([]WireGuardInterface, 0)
  138. for _, iface := range m.interfaces {
  139. if iface.Name != req.OrigName {
  140. updated = append(updated, iface)
  141. }
  142. }
  143. m.interfaces = updated
  144. }
  145. found := false
  146. for i, existing := range m.interfaces {
  147. if existing.Name == req.Name {
  148. m.interfaces[i] = req.WireGuardInterface
  149. found = true
  150. break
  151. }
  152. }
  153. if !found {
  154. m.interfaces = append(m.interfaces, req.WireGuardInterface)
  155. }
  156. _ = m.save()
  157. _ = m.generateWGConfigFile(req.Name)
  158. if !req.Disabled {
  159. go m.startWGInterface(req.Name)
  160. } else {
  161. go m.stopWGInterface(req.Name)
  162. }
  163. return nil
  164. }
  165. func (m *WireGuardManager) DeleteInterface(name string) error {
  166. m.mu.Lock()
  167. defer m.mu.Unlock()
  168. _ = m.stopWGInterface(name)
  169. _ = os.Remove(fmt.Sprintf("/etc/wireguard/%s.conf", name))
  170. updatedIfaces := make([]WireGuardInterface, 0)
  171. for _, iface := range m.interfaces {
  172. if iface.Name != name {
  173. updatedIfaces = append(updatedIfaces, iface)
  174. }
  175. }
  176. m.interfaces = updatedIfaces
  177. // Remove associated peers
  178. updatedPeers := make([]WireGuardPeer, 0)
  179. for _, p := range m.peers {
  180. if p.Interface != name {
  181. updatedPeers = append(updatedPeers, p)
  182. }
  183. }
  184. m.peers = updatedPeers
  185. return m.save()
  186. }
  187. func (m *WireGuardManager) SetInterfaceEnabled(name string, enabled bool) error {
  188. m.mu.Lock()
  189. defer m.mu.Unlock()
  190. for i, iface := range m.interfaces {
  191. if iface.Name == name {
  192. m.interfaces[i].Disabled = !enabled
  193. _ = m.save()
  194. if enabled {
  195. go m.startWGInterface(name)
  196. } else {
  197. go m.stopWGInterface(name)
  198. }
  199. return nil
  200. }
  201. }
  202. return fmt.Errorf("interface %q not found", name)
  203. }
  204. func (m *WireGuardManager) StartInterface(name string) error {
  205. m.mu.Lock()
  206. defer m.mu.Unlock()
  207. return m.startWGInterface(name)
  208. }
  209. func (m *WireGuardManager) StopInterface(name string) error {
  210. m.mu.Lock()
  211. defer m.mu.Unlock()
  212. return m.stopWGInterface(name)
  213. }
  214. func (m *WireGuardManager) startWGInterface(name string) error {
  215. _ = m.generateWGConfigFile(name)
  216. _ = exec.Command("wg-quick", "down", name).Run()
  217. cmd := exec.Command("wg-quick", "up", name)
  218. if out, err := cmd.CombinedOutput(); err != nil {
  219. return fmt.Errorf("failed to start %s: %s: %w", name, string(out), err)
  220. }
  221. return nil
  222. }
  223. func (m *WireGuardManager) stopWGInterface(name string) error {
  224. cmd := exec.Command("wg-quick", "down", name)
  225. _ = cmd.Run()
  226. return nil
  227. }
  228. // ----------------- Peers Management (MikroTik style) -----------------
  229. func (m *WireGuardManager) ListPeers(ifaceFilter string) []WireGuardPeer {
  230. m.mu.Lock()
  231. defer m.mu.Unlock()
  232. // Parse runtime dump from `wg show <iface> dump`
  233. dumpMap := make(map[string]map[string]WireGuardPeer) // iface -> pubkey -> peer stats
  234. for _, iface := range m.interfaces {
  235. if _, err := netlink.LinkByName(iface.Name); err == nil {
  236. cmd := exec.Command("wg", "show", iface.Name, "dump")
  237. out, err := cmd.Output()
  238. if err == nil {
  239. dumpMap[iface.Name] = make(map[string]WireGuardPeer)
  240. lines := strings.Split(strings.TrimSpace(string(out)), "\n")
  241. for idx, line := range lines {
  242. if idx == 0 {
  243. continue // skip interface line
  244. }
  245. parts := strings.Fields(line)
  246. if len(parts) >= 8 {
  247. pubKey := parts[0]
  248. endpoint := parts[2]
  249. if endpoint == "(none)" {
  250. endpoint = ""
  251. }
  252. handshakeUnix, _ := strconv.ParseInt(parts[4], 10, 64)
  253. rx, _ := strconv.ParseUint(parts[5], 10, 64)
  254. tx, _ := strconv.ParseUint(parts[6], 10, 64)
  255. handshakeStr := "Never"
  256. if handshakeUnix > 0 {
  257. handshakeStr = time.Since(time.Unix(handshakeUnix, 0)).Round(time.Second).String() + " ago"
  258. }
  259. dumpMap[iface.Name][pubKey] = WireGuardPeer{
  260. EndpointAddress: endpoint,
  261. LatestHandshake: handshakeStr,
  262. TransferRxBytes: rx,
  263. TransferTxBytes: tx,
  264. TransferRxHuman: formatTrafficBytes(rx),
  265. TransferTxHuman: formatTrafficBytes(tx),
  266. }
  267. }
  268. }
  269. }
  270. }
  271. }
  272. result := make([]WireGuardPeer, 0)
  273. for _, p := range m.peers {
  274. if ifaceFilter != "" && p.Interface != ifaceFilter {
  275. continue
  276. }
  277. item := p
  278. // Overlay runtime stats if available
  279. if ifaceDumps, ok := dumpMap[p.Interface]; ok {
  280. if stats, has := ifaceDumps[p.PublicKey]; has {
  281. item.EndpointAddress = stats.EndpointAddress
  282. item.LatestHandshake = stats.LatestHandshake
  283. item.TransferRxBytes = stats.TransferRxBytes
  284. item.TransferTxBytes = stats.TransferTxBytes
  285. item.TransferRxHuman = stats.TransferRxHuman
  286. item.TransferTxHuman = stats.TransferTxHuman
  287. }
  288. }
  289. result = append(result, item)
  290. }
  291. return result
  292. }
  293. func (m *WireGuardManager) SavePeer(req SaveWireGuardPeerRequest) error {
  294. m.mu.Lock()
  295. defer m.mu.Unlock()
  296. if req.Interface == "" {
  297. return fmt.Errorf("target WireGuard interface is required")
  298. }
  299. if strings.TrimSpace(req.PublicKey) == "" {
  300. return fmt.Errorf("peer public key is required")
  301. }
  302. req.PublicKey = strings.TrimSpace(req.PublicKey)
  303. // Validate that peer public key is not the same as the interface's own public key
  304. for _, iface := range m.interfaces {
  305. if iface.Name == req.Interface && iface.PublicKey == req.PublicKey {
  306. return fmt.Errorf("peer public key cannot be identical to interface %s public key", iface.Name)
  307. }
  308. }
  309. // Handle modifying an existing peer's public key or moving to a different interface
  310. if req.OrigPublicKey != "" && (req.OrigPublicKey != req.PublicKey || (req.OrigInterface != "" && req.OrigInterface != req.Interface)) {
  311. oldIface := req.OrigInterface
  312. if oldIface == "" {
  313. oldIface = req.Interface
  314. }
  315. // Remove old peer from runtime
  316. if _, err := netlink.LinkByName(oldIface); err == nil {
  317. _ = exec.Command("wg", "set", oldIface, "peer", req.OrigPublicKey, "remove").Run()
  318. }
  319. // Remove old peer entry
  320. updated := make([]WireGuardPeer, 0)
  321. for _, p := range m.peers {
  322. if !(p.Interface == oldIface && p.PublicKey == req.OrigPublicKey) {
  323. updated = append(updated, p)
  324. }
  325. }
  326. m.peers = updated
  327. if oldIface != req.Interface {
  328. _ = m.generateWGConfigFile(oldIface)
  329. }
  330. }
  331. found := false
  332. for i, existing := range m.peers {
  333. if existing.PublicKey == req.PublicKey && existing.Interface == req.Interface {
  334. m.peers[i] = req.WireGuardPeer
  335. found = true
  336. break
  337. }
  338. }
  339. if !found {
  340. m.peers = append(m.peers, req.WireGuardPeer)
  341. }
  342. _ = m.save()
  343. _ = m.generateWGConfigFile(req.Interface)
  344. // Apply dynamically to kernel if running
  345. if _, err := netlink.LinkByName(req.Interface); err == nil {
  346. if req.Disabled {
  347. _ = exec.Command("wg", "set", req.Interface, "peer", req.PublicKey, "remove").Run()
  348. } else {
  349. args := []string{"set", req.Interface, "peer", req.PublicKey}
  350. if len(req.AllowedIPs) > 0 {
  351. args = append(args, "allowed-ips", strings.Join(req.AllowedIPs, ","))
  352. }
  353. if req.EndpointAddress != "" {
  354. ep := req.EndpointAddress
  355. if req.EndpointPort > 0 {
  356. ep = fmt.Sprintf("%s:%d", req.EndpointAddress, req.EndpointPort)
  357. }
  358. args = append(args, "endpoint", ep)
  359. }
  360. if req.PersistentKeepalive > 0 {
  361. args = append(args, "persistent-keepalive", strconv.Itoa(req.PersistentKeepalive))
  362. }
  363. if req.PresharedKey != "" {
  364. pskFile, err := os.CreateTemp("", "wg-psk-*")
  365. if err == nil {
  366. _, _ = pskFile.WriteString(req.PresharedKey)
  367. _ = pskFile.Close()
  368. args = append(args, "preshared-key", pskFile.Name())
  369. defer os.Remove(pskFile.Name())
  370. }
  371. }
  372. _ = exec.Command("wg", args...).Run()
  373. }
  374. }
  375. return nil
  376. }
  377. func (m *WireGuardManager) SetPeerEnabled(ifaceName, pubKey string, enabled bool) error {
  378. m.mu.Lock()
  379. defer m.mu.Unlock()
  380. found := false
  381. for i, p := range m.peers {
  382. if p.Interface == ifaceName && p.PublicKey == pubKey {
  383. m.peers[i].Disabled = !enabled
  384. found = true
  385. break
  386. }
  387. }
  388. if !found {
  389. return fmt.Errorf("peer with public key %s not found on interface %s", pubKey, ifaceName)
  390. }
  391. _ = m.save()
  392. _ = m.generateWGConfigFile(ifaceName)
  393. if _, err := netlink.LinkByName(ifaceName); err == nil {
  394. if !enabled {
  395. _ = exec.Command("wg", "set", ifaceName, "peer", pubKey, "remove").Run()
  396. } else {
  397. for _, p := range m.peers {
  398. if p.Interface == ifaceName && p.PublicKey == pubKey {
  399. args := []string{"set", ifaceName, "peer", pubKey}
  400. if len(p.AllowedIPs) > 0 {
  401. args = append(args, "allowed-ips", strings.Join(p.AllowedIPs, ","))
  402. }
  403. if p.EndpointAddress != "" {
  404. ep := p.EndpointAddress
  405. if p.EndpointPort > 0 {
  406. ep = fmt.Sprintf("%s:%d", p.EndpointAddress, p.EndpointPort)
  407. }
  408. args = append(args, "endpoint", ep)
  409. }
  410. if p.PersistentKeepalive > 0 {
  411. args = append(args, "persistent-keepalive", strconv.Itoa(p.PersistentKeepalive))
  412. }
  413. if p.PresharedKey != "" {
  414. pskFile, err := os.CreateTemp("", "wg-psk-*")
  415. if err == nil {
  416. _, _ = pskFile.WriteString(p.PresharedKey)
  417. _ = pskFile.Close()
  418. args = append(args, "preshared-key", pskFile.Name())
  419. defer os.Remove(pskFile.Name())
  420. }
  421. }
  422. _ = exec.Command("wg", args...).Run()
  423. break
  424. }
  425. }
  426. }
  427. }
  428. return nil
  429. }
  430. func (m *WireGuardManager) DeletePeer(ifaceName, pubKey string) error {
  431. m.mu.Lock()
  432. defer m.mu.Unlock()
  433. updated := make([]WireGuardPeer, 0)
  434. for _, p := range m.peers {
  435. if p.Interface == ifaceName && p.PublicKey == pubKey {
  436. continue
  437. }
  438. updated = append(updated, p)
  439. }
  440. m.peers = updated
  441. _ = m.save()
  442. _ = m.generateWGConfigFile(ifaceName)
  443. // Remove from runtime
  444. if _, err := netlink.LinkByName(ifaceName); err == nil {
  445. _ = exec.Command("wg", "set", ifaceName, "peer", pubKey, "remove").Run()
  446. }
  447. return nil
  448. }
  449. func (m *WireGuardManager) generateWGConfigFile(ifaceName string) error {
  450. var targetIface *WireGuardInterface
  451. for _, i := range m.interfaces {
  452. if i.Name == ifaceName {
  453. targetIface = &i
  454. break
  455. }
  456. }
  457. if targetIface == nil {
  458. return nil
  459. }
  460. _ = os.MkdirAll("/etc/wireguard", 0700)
  461. var sb strings.Builder
  462. sb.WriteString(fmt.Sprintf("# MikroTik-Style WireGuard Interface: %s\n", targetIface.Name))
  463. if targetIface.Comment != "" {
  464. sb.WriteString(fmt.Sprintf("# Comment: %s\n", targetIface.Comment))
  465. }
  466. sb.WriteString("[Interface]\n")
  467. sb.WriteString(fmt.Sprintf("PrivateKey = %s\n", targetIface.PrivateKey))
  468. if targetIface.Address != "" && targetIface.Address != "0.0.0.0/0" {
  469. sb.WriteString(fmt.Sprintf("Address = %s\n", targetIface.Address))
  470. }
  471. sb.WriteString(fmt.Sprintf("ListenPort = %d\n", targetIface.ListenPort))
  472. if targetIface.MTU > 0 {
  473. sb.WriteString(fmt.Sprintf("MTU = %d\n", targetIface.MTU))
  474. }
  475. sb.WriteString("\n")
  476. for _, p := range m.peers {
  477. if p.Interface == ifaceName && !p.Disabled {
  478. sb.WriteString(fmt.Sprintf("# Peer: %s\n", p.Name))
  479. sb.WriteString("[Peer]\n")
  480. sb.WriteString(fmt.Sprintf("PublicKey = %s\n", p.PublicKey))
  481. if len(p.AllowedIPs) > 0 {
  482. sb.WriteString(fmt.Sprintf("AllowedIPs = %s\n", strings.Join(p.AllowedIPs, ", ")))
  483. }
  484. if p.EndpointAddress != "" {
  485. ep := p.EndpointAddress
  486. if p.EndpointPort > 0 {
  487. ep = fmt.Sprintf("%s:%d", p.EndpointAddress, p.EndpointPort)
  488. }
  489. sb.WriteString(fmt.Sprintf("Endpoint = %s\n", ep))
  490. }
  491. if p.PersistentKeepalive > 0 {
  492. sb.WriteString(fmt.Sprintf("PersistentKeepalive = %d\n", p.PersistentKeepalive))
  493. }
  494. if p.PresharedKey != "" {
  495. sb.WriteString(fmt.Sprintf("PresharedKey = %s\n", p.PresharedKey))
  496. }
  497. sb.WriteString("\n")
  498. }
  499. }
  500. path := fmt.Sprintf("/etc/wireguard/%s.conf", ifaceName)
  501. return os.WriteFile(path, []byte(sb.String()), 0600)
  502. }
  503. // GenerateClientConfig creates a ready-to-use client .conf for mobile/desktop
  504. func (m *WireGuardManager) GenerateClientConfig(ifaceName, clientPrivKey, clientAddress, serverEndpoint, dns string) (string, error) {
  505. m.mu.Lock()
  506. defer m.mu.Unlock()
  507. var targetIface *WireGuardInterface
  508. for _, i := range m.interfaces {
  509. if i.Name == ifaceName {
  510. targetIface = &i
  511. break
  512. }
  513. }
  514. if targetIface == nil {
  515. return "", fmt.Errorf("interface %s not found", ifaceName)
  516. }
  517. if dns == "" {
  518. dns = "1.1.1.1, 8.8.8.8"
  519. }
  520. if serverEndpoint == "" {
  521. serverEndpoint = fmt.Sprintf("YOUR_ROUTER_IP:%d", targetIface.ListenPort)
  522. }
  523. var sb strings.Builder
  524. sb.WriteString("[Interface]\n")
  525. sb.WriteString(fmt.Sprintf("PrivateKey = %s\n", clientPrivKey))
  526. sb.WriteString(fmt.Sprintf("Address = %s\n", clientAddress))
  527. sb.WriteString(fmt.Sprintf("DNS = %s\n", dns))
  528. sb.WriteString("\n[Peer]\n")
  529. sb.WriteString(fmt.Sprintf("PublicKey = %s\n", targetIface.PublicKey))
  530. sb.WriteString(fmt.Sprintf("Endpoint = %s\n", serverEndpoint))
  531. sb.WriteString("AllowedIPs = 0.0.0.0/0, ::/0\n")
  532. sb.WriteString("PersistentKeepalive = 25\n")
  533. return sb.String(), nil
  534. }