123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- package service
- import (
- "context"
- "fmt"
- "github.com/spf13/viper"
- "go.uber.org/zap"
- "net"
- "os"
- "os/exec"
- "strings"
- "sync"
- )
- type IpService interface {
- // AddIp 添加一个IP地址到网络接口并持久化。
- AddIp(ctx context.Context, ip string) error
- // DeleteIp 从网络接口删除一个IP地址并移除持久化配置。
- DeleteIp(ctx context.Context, ip string) error
- }
- func NewIpService(
- service *Service,
- viper *viper.Viper,
- ) IpService {
- return &ipService{
- Service: service,
- networkInterface: viper.GetString("ip.network_interface"),
- ipLabel: viper.GetString("ip.ip_label"),
- scriptPath: viper.GetString("ip.script_path"),
- }
- }
- type ipService struct {
- *Service
- fileMux sync.Mutex
- networkInterface string
- ipLabel string
- scriptPath string
- }
- // AddIp 负责添加一个IP地址到指定的网络接口,并将其持久化。
- func (s *ipService) AddIp(ctx context.Context, ip string) error {
- if net.ParseIP(ip) == nil {
- return fmt.Errorf("无效的IP地址: %s", ip)
- }
- fullIp := ip + "/32"
- addCmdStr := fmt.Sprintf("sudo ip addr add %s dev %s label \"%s\"", fullIp, s.networkInterface, s.ipLabel)
- s.logger.Info("准备执行添加命令: %s\n", zap.String("addCmdStr", addCmdStr))
- // 执行实时添加IP的命令
- cmd := exec.CommandContext(ctx, "sudo", "ip", "addr", "add", fullIp, "dev", s.networkInterface, "label", s.ipLabel)
- output, err := cmd.CombinedOutput()
- if err != nil {
- s.logger.Error("执行添加命令失败: %s\n", zap.String("addCmdStr", addCmdStr), zap.Error(err))
- return fmt.Errorf("执行添加命令失败 '%s': %w. 输出: %s", addCmdStr, err, string(output))
- }
- // 持久化IP地址到脚本文件
- s.logger.Info("尝试将IP持久化到脚本: %s\n", zap.String("scriptPath", s.scriptPath))
- if err := s.makePersistent(addCmdStr); err != nil {
- return fmt.Errorf("持久化IP失败: %w", err)
- }
- return nil
- }
- // DeleteIp 负责从网络接口删除一个IP地址,并从持久化脚本中移除它。
- func (s *ipService) DeleteIp(ctx context.Context, ip string) error {
- if net.ParseIP(ip) == nil {
- return fmt.Errorf("无效的IP地址: %s", ip)
- }
- fullIp := ip + "/32"
- // 这是要执行的删除命令
- delCmdStr := fmt.Sprintf("sudo ip addr del %s dev %s", fullIp, s.networkInterface)
- // 这是要在脚本文件中查找并删除的添加命令
- addCmdStrInScript := fmt.Sprintf("sudo ip addr add %s dev %s label \"%s\"", fullIp, s.networkInterface, s.ipLabel)
- s.logger.Info("准备执行删除命令: ", zap.String("delCmdStr", delCmdStr))
- // 执行实时删除IP的命令
- cmd := exec.CommandContext(ctx, "sudo", "ip", "addr", "del", fullIp, "dev", s.networkInterface)
- output, err := cmd.CombinedOutput()
- if err != nil {
- // 如果错误信息是 "Cannot assign requested address",通常意味着IP已经不存在了。
- // 在这种情况下,我们不应该中止,而是应该继续清理脚本。
- // 对于其他错误,我们则返回。
- if !strings.Contains(string(output), "Cannot assign requested address") {
- return fmt.Errorf("执行删除命令失败 '%s': %w. 输出: %s", delCmdStr, err, string(output))
- }
- s.logger.Warn("警告: IP %s 已不在网络接口上,继续清理持久化脚本。\n", zap.String("ip", ip))
- } else {
- s.logger.Info("成功从网络接口删除IP地址。")
- }
- // 从持久化脚本中移除IP
- s.logger.Info("尝试从脚本 %s 中移除IP持久化记录\n", zap.String("scriptPath", s.scriptPath))
- if err := s.removeFromPersistent(addCmdStrInScript); err != nil {
- return fmt.Errorf("从持久化脚本中移除IP失败: %w", err)
- }
- s.logger.Info("成功从脚本中移除IP持久化记录。")
- return nil
- }
- // makePersistent 将IP添加命令写入到持久化脚本中。
- func (s *ipService) makePersistent(cmdToAdd string) error {
- s.fileMux.Lock()
- defer s.fileMux.Unlock()
- content, err := os.ReadFile(s.scriptPath)
- if err != nil {
- if os.IsNotExist(err) {
- return fmt.Errorf("持久化脚本不存在: %s", s.scriptPath)
- }
- return fmt.Errorf("读取持久化脚本失败 %s: %w", s.scriptPath, err)
- }
- if strings.Contains(string(content), cmdToAdd) {
- // 如果命令已经在脚本中,那么不需要再次添加。
- return nil
- }
- lines := strings.Split(string(content), "\n")
- var newLines []string
- inserted := false
- for _, line := range lines {
- if strings.TrimSpace(line) == "exit 0" && !inserted {
- newLines = append(newLines, cmdToAdd)
- inserted = true
- }
- newLines = append(newLines, line)
- }
- if !inserted {
- newLines = append(newLines, cmdToAdd)
- }
- newContent := strings.Join(newLines, "\n")
- return os.WriteFile(s.scriptPath, []byte(newContent), 0644)
- }
- // removeFromPersistent 从持久化脚本中删除指定的命令。
- func (s *ipService) removeFromPersistent(cmdToRemove string) error {
- s.fileMux.Lock()
- defer s.fileMux.Unlock()
- content, err := os.ReadFile(s.scriptPath)
- if err != nil {
- if os.IsNotExist(err) {
- // 如果脚本文件不存在,那么命令肯定也不在里面,可以直接返回成功。
- return nil
- }
- return fmt.Errorf("读取持久化脚本失败 %s: %w", s.scriptPath, err)
- }
- lines := strings.Split(string(content), "\n")
- var newLines []string
- found := false
- for _, line := range lines {
- // 完全匹配要删除的行,然后跳过它
- if line == cmdToRemove {
- found = true
- continue
- }
- newLines = append(newLines, line)
- }
- if !found {
- // 如果未找到要删除的行,那么命令肯定也不在里面,可以直接返回成功。
- return nil
- }
- newContent := strings.Join(newLines, "\n")
- return os.WriteFile(s.scriptPath, []byte(newContent), 0644)
- }
|