ip.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package service
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/spf13/viper"
  6. "go.uber.org/zap"
  7. "net"
  8. "os"
  9. "os/exec"
  10. "strings"
  11. "sync"
  12. )
  13. type IpService interface {
  14. // AddIp 添加一个IP地址到网络接口并持久化。
  15. AddIp(ctx context.Context, ip string) error
  16. // DeleteIp 从网络接口删除一个IP地址并移除持久化配置。
  17. DeleteIp(ctx context.Context, ip string) error
  18. }
  19. func NewIpService(
  20. service *Service,
  21. viper *viper.Viper,
  22. ) IpService {
  23. return &ipService{
  24. Service: service,
  25. networkInterface: viper.GetString("ip.network_interface"),
  26. ipLabel: viper.GetString("ip.ip_label"),
  27. scriptPath: viper.GetString("ip.script_path"),
  28. }
  29. }
  30. type ipService struct {
  31. *Service
  32. fileMux sync.Mutex
  33. networkInterface string
  34. ipLabel string
  35. scriptPath string
  36. }
  37. // AddIp 负责添加一个IP地址到指定的网络接口,并将其持久化。
  38. func (s *ipService) AddIp(ctx context.Context, ip string) error {
  39. if net.ParseIP(ip) == nil {
  40. return fmt.Errorf("无效的IP地址: %s", ip)
  41. }
  42. fullIp := ip + "/32"
  43. addCmdStr := fmt.Sprintf("sudo ip addr add %s dev %s label \"%s\"", fullIp, s.networkInterface, s.ipLabel)
  44. s.logger.Info("准备执行添加命令: %s\n", zap.String("addCmdStr", addCmdStr))
  45. // 执行实时添加IP的命令
  46. cmd := exec.CommandContext(ctx, "sudo", "ip", "addr", "add", fullIp, "dev", s.networkInterface, "label", s.ipLabel)
  47. output, err := cmd.CombinedOutput()
  48. if err != nil {
  49. s.logger.Error("执行添加命令失败: %s\n", zap.String("addCmdStr", addCmdStr), zap.Error(err))
  50. return fmt.Errorf("执行添加命令失败 '%s': %w. 输出: %s", addCmdStr, err, string(output))
  51. }
  52. // 持久化IP地址到脚本文件
  53. s.logger.Info("尝试将IP持久化到脚本: %s\n", zap.String("scriptPath", s.scriptPath))
  54. if err := s.makePersistent(addCmdStr); err != nil {
  55. return fmt.Errorf("持久化IP失败: %w", err)
  56. }
  57. return nil
  58. }
  59. // DeleteIp 负责从网络接口删除一个IP地址,并从持久化脚本中移除它。
  60. func (s *ipService) DeleteIp(ctx context.Context, ip string) error {
  61. if net.ParseIP(ip) == nil {
  62. return fmt.Errorf("无效的IP地址: %s", ip)
  63. }
  64. fullIp := ip + "/32"
  65. // 这是要执行的删除命令
  66. delCmdStr := fmt.Sprintf("sudo ip addr del %s dev %s", fullIp, s.networkInterface)
  67. // 这是要在脚本文件中查找并删除的添加命令
  68. addCmdStrInScript := fmt.Sprintf("sudo ip addr add %s dev %s label \"%s\"", fullIp, s.networkInterface, s.ipLabel)
  69. s.logger.Info("准备执行删除命令: ", zap.String("delCmdStr", delCmdStr))
  70. // 执行实时删除IP的命令
  71. cmd := exec.CommandContext(ctx, "sudo", "ip", "addr", "del", fullIp, "dev", s.networkInterface)
  72. output, err := cmd.CombinedOutput()
  73. if err != nil {
  74. // 如果错误信息是 "Cannot assign requested address",通常意味着IP已经不存在了。
  75. // 在这种情况下,我们不应该中止,而是应该继续清理脚本。
  76. // 对于其他错误,我们则返回。
  77. if !strings.Contains(string(output), "Cannot assign requested address") {
  78. return fmt.Errorf("执行删除命令失败 '%s': %w. 输出: %s", delCmdStr, err, string(output))
  79. }
  80. s.logger.Warn("警告: IP %s 已不在网络接口上,继续清理持久化脚本。\n", zap.String("ip", ip))
  81. } else {
  82. s.logger.Info("成功从网络接口删除IP地址。")
  83. }
  84. // 从持久化脚本中移除IP
  85. s.logger.Info("尝试从脚本 %s 中移除IP持久化记录\n", zap.String("scriptPath", s.scriptPath))
  86. if err := s.removeFromPersistent(addCmdStrInScript); err != nil {
  87. return fmt.Errorf("从持久化脚本中移除IP失败: %w", err)
  88. }
  89. s.logger.Info("成功从脚本中移除IP持久化记录。")
  90. return nil
  91. }
  92. // makePersistent 将IP添加命令写入到持久化脚本中。
  93. func (s *ipService) makePersistent(cmdToAdd string) error {
  94. s.fileMux.Lock()
  95. defer s.fileMux.Unlock()
  96. content, err := os.ReadFile(s.scriptPath)
  97. if err != nil {
  98. if os.IsNotExist(err) {
  99. return fmt.Errorf("持久化脚本不存在: %s", s.scriptPath)
  100. }
  101. return fmt.Errorf("读取持久化脚本失败 %s: %w", s.scriptPath, err)
  102. }
  103. if strings.Contains(string(content), cmdToAdd) {
  104. // 如果命令已经在脚本中,那么不需要再次添加。
  105. return nil
  106. }
  107. lines := strings.Split(string(content), "\n")
  108. var newLines []string
  109. inserted := false
  110. for _, line := range lines {
  111. if strings.TrimSpace(line) == "exit 0" && !inserted {
  112. newLines = append(newLines, cmdToAdd)
  113. inserted = true
  114. }
  115. newLines = append(newLines, line)
  116. }
  117. if !inserted {
  118. newLines = append(newLines, cmdToAdd)
  119. }
  120. newContent := strings.Join(newLines, "\n")
  121. return os.WriteFile(s.scriptPath, []byte(newContent), 0644)
  122. }
  123. // removeFromPersistent 从持久化脚本中删除指定的命令。
  124. func (s *ipService) removeFromPersistent(cmdToRemove string) error {
  125. s.fileMux.Lock()
  126. defer s.fileMux.Unlock()
  127. content, err := os.ReadFile(s.scriptPath)
  128. if err != nil {
  129. if os.IsNotExist(err) {
  130. // 如果脚本文件不存在,那么命令肯定也不在里面,可以直接返回成功。
  131. return nil
  132. }
  133. return fmt.Errorf("读取持久化脚本失败 %s: %w", s.scriptPath, err)
  134. }
  135. lines := strings.Split(string(content), "\n")
  136. var newLines []string
  137. found := false
  138. for _, line := range lines {
  139. // 完全匹配要删除的行,然后跳过它
  140. if line == cmdToRemove {
  141. found = true
  142. continue
  143. }
  144. newLines = append(newLines, line)
  145. }
  146. if !found {
  147. // 如果未找到要删除的行,那么命令肯定也不在里面,可以直接返回成功。
  148. return nil
  149. }
  150. newContent := strings.Join(newLines, "\n")
  151. return os.WriteFile(s.scriptPath, []byte(newContent), 0644)
  152. }