wireguard.go 18 KB

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