wireguard.go 18 KB

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