Forráskód Böngészése

refactor(network): 移除维护地址自动配置,支持动态链路本地发现

yangkaixiang 1 hónapja
szülő
commit
6bdb9f5df1

+ 0 - 5
server/cmd/networktool-server/main.go

@@ -39,11 +39,6 @@ func main() {
 	taskSvc := tasks.New()
 	systemSvc := systemaction.New()
 
-	if err := interfaceSvc.EnsureMaintenanceAddress(); err != nil {
-		log.Error("failed to ensure maintenance address", "error", err.Error())
-		return
-	}
-
 	httpSrv := httpserver.New(cfg, log, deviceSvc, interfaceSvc, configSvc, validatorSvc, netplanSvc, applySvc, taskSvc, systemSvc)
 	udpSrv := discovery.New(cfg, log, deviceSvc)
 

+ 11 - 8
server/internal/config/config.go

@@ -6,7 +6,7 @@ import (
 	"net"
 )
 
-const ServerVersion = "20260512120500"
+const ServerVersion = "20260512145500"
 
 type Config struct {
 	HTTPHost         string
@@ -26,24 +26,27 @@ func Load(args []string) Config {
 		HTTPPort:         48888,
 		UDPHost:          "0.0.0.0",
 		UDPPort:          50000,
-		MaintenanceIP:    "169.254.100.2",
-		MaintenanceCIDR:  "169.254.100.2/16",
+		MaintenanceIP:    "",
+		MaintenanceCIDR:  "",
 		AdminPassword:    "Dt123$",
 		ServerVersion:    ServerVersion,
 		DeviceIDFallback: "networktool-device",
 	}
 
 	fs := flag.NewFlagSet("networktool-server", flag.ContinueOnError)
-	fs.StringVar(&cfg.MaintenanceIP, "ip", cfg.MaintenanceIP, "maintenance IPv4 address")
+	fs.StringVar(&cfg.MaintenanceIP, "ip", cfg.MaintenanceIP, "maintenance IPv4 address, must be in 169.254.0.0/16 when set")
 	fs.IntVar(&cfg.HTTPPort, "port", cfg.HTTPPort, "HTTP listen port")
 	fs.StringVar(&cfg.AdminPassword, "password", cfg.AdminPassword, "admin password")
 	_ = fs.Parse(args)
 
-	if parsed := net.ParseIP(cfg.MaintenanceIP); parsed == nil || parsed.To4() == nil {
-		panic(fmt.Sprintf("invalid maintenance ip: %s", cfg.MaintenanceIP))
+	if cfg.MaintenanceIP != "" {
+		parsed := net.ParseIP(cfg.MaintenanceIP)
+		ipv4 := parsed.To4()
+		if parsed == nil || ipv4 == nil || ipv4[0] != 169 || ipv4[1] != 254 {
+			panic(fmt.Sprintf("invalid maintenance ip: %s", cfg.MaintenanceIP))
+		}
+		cfg.MaintenanceCIDR = fmt.Sprintf("%s/16", cfg.MaintenanceIP)
 	}
 
-	cfg.MaintenanceCIDR = fmt.Sprintf("%s/16", cfg.MaintenanceIP)
-
 	return cfg
 }

+ 57 - 10
server/internal/discovery/discovery.go

@@ -55,6 +55,12 @@ func (s *Server) Run(ctx context.Context) error {
 			continue
 		}
 
+		lan2IP, mac := s.maintenanceEndpoint()
+		if lan2IP == "" {
+			s.log.Warn("skip discovery response because no 169.254 maintenance address was found")
+			continue
+		}
+
 		device := s.deviceSvc.Get()
 		resp := model.DiscoverResponse{
 			ProtocolVersion: 1,
@@ -63,8 +69,8 @@ func (s *Server) Run(ctx context.Context) error {
 			DeviceID:        device.DeviceID,
 			Hostname:        device.Hostname,
 			ServerVersion:   device.ServerVersion,
-			MAC:             findMACByIP(s.cfg.MaintenanceIP),
-			LAN2IP:          s.cfg.MaintenanceIP,
+			MAC:             mac,
+			LAN2IP:          lan2IP,
 			HTTPPort:        s.cfg.HTTPPort,
 			AuthRequired:    true,
 		}
@@ -73,6 +79,40 @@ func (s *Server) Run(ctx context.Context) error {
 	}
 }
 
+func (s *Server) maintenanceEndpoint() (string, string) {
+	if s.cfg.MaintenanceIP != "" {
+		mac := findMACByIP(s.cfg.MaintenanceIP)
+		if mac != "" {
+			return s.cfg.MaintenanceIP, mac
+		}
+	}
+	return findFirstLinkLocalEndpoint()
+}
+
+func findFirstLinkLocalEndpoint() (string, string) {
+	ifaces, err := net.Interfaces()
+	if err != nil {
+		return "", ""
+	}
+	for _, iface := range ifaces {
+		if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 || len(iface.HardwareAddr) == 0 {
+			continue
+		}
+		addrs, err := iface.Addrs()
+		if err != nil {
+			continue
+		}
+		for _, addr := range addrs {
+			current := ipv4FromAddr(addr)
+			if current == nil || !strings.HasPrefix(current.String(), "169.254.") {
+				continue
+			}
+			return current.String(), iface.HardwareAddr.String()
+		}
+	}
+	return "", ""
+}
+
 func findMACByIP(ip string) string {
 	ifaces, err := net.Interfaces()
 	if err != nil {
@@ -88,14 +128,7 @@ func findMACByIP(ip string) string {
 			continue
 		}
 		for _, addr := range addrs {
-			var current net.IP
-			switch value := addr.(type) {
-			case *net.IPNet:
-				current = value.IP
-			case *net.IPAddr:
-				current = value.IP
-			}
-			current = current.To4()
+			current := ipv4FromAddr(addr)
 			if current == nil {
 				continue
 			}
@@ -109,3 +142,17 @@ func findMACByIP(ip string) string {
 	}
 	return fallback
 }
+
+func ipv4FromAddr(addr net.Addr) net.IP {
+	var current net.IP
+	switch value := addr.(type) {
+	case *net.IPNet:
+		current = value.IP
+	case *net.IPAddr:
+		current = value.IP
+	}
+	if current == nil {
+		return nil
+	}
+	return current.To4()
+}

+ 0 - 3
server/internal/httpserver/server.go

@@ -418,7 +418,6 @@ func (s *Server) runApplyTask(taskID string, input model.InterfaceConfig, manage
 		s.log.Error("netplan apply failed, restoring netplan file", "task_id", taskID, "file", filePath, "error", err.Error())
 		_ = s.netplanSvc.Restore(filePath, backupPath)
 		_ = s.applySvc.Apply()
-		_ = s.interfaceSvc.EnsureMaintenanceAddress()
 		s.logNetplanFile(taskID, filePath, "netplan restored after apply failure")
 		s.taskSvc.Update(taskID, "rolled_back", "rolling_back", fmt.Sprintf("应用 netplan 失败,已自动回滚:%v", err), true)
 		return
@@ -476,7 +475,6 @@ func (s *Server) runApplyAllTask(taskID string, inputs []model.InterfaceConfig,
 		s.log.Error("netplan apply failed, restoring netplan file", "task_id", taskID, "file", filePath, "error", err.Error())
 		_ = s.netplanSvc.Restore(filePath, backupPath)
 		_ = s.applySvc.Apply()
-		_ = s.interfaceSvc.EnsureMaintenanceAddress()
 		s.logNetplanFile(taskID, filePath, "netplan restored after apply failure")
 		s.taskSvc.Update(taskID, "rolled_back", "rolling_back", fmt.Sprintf("应用 netplan 失败,已自动回滚:%v", err), true)
 		return
@@ -598,7 +596,6 @@ func (s *Server) rollbackAppliedConfig(taskID string, filePath string, backupPat
 	s.log.Warn("apply confirmation failed, restoring netplan file", "task_id", taskID, "file", filePath, "reason", reason)
 	_ = s.netplanSvc.Restore(filePath, backupPath)
 	_ = s.applySvc.Apply()
-	_ = s.interfaceSvc.EnsureMaintenanceAddress()
 	s.logNetplanFile(taskID, filePath, "netplan restored after confirmation failure")
 	s.taskSvc.Update(taskID, "rolled_back", "rolling_back", fmt.Sprintf("%s,已自动回滚。", reason), true)
 }

+ 5 - 41
server/internal/network/interfaces/interfaces.go

@@ -1,10 +1,8 @@
 package interfaces
 
 import (
-	"fmt"
 	"net"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -51,43 +49,6 @@ func (s *Service) List() (model.InterfacesResponse, error) {
 	}, nil
 }
 
-func (s *Service) EnsureMaintenanceAddress() error {
-	items, err := s.listPhysicalInterfaces()
-	if err != nil {
-		return err
-	}
-	if len(items) == 0 {
-		return fmt.Errorf("no physical ethernet interfaces found")
-	}
-
-	management := s.detectManagement(items)
-	if management == "" {
-		return fmt.Errorf("failed to detect management interface")
-	}
-
-	for _, item := range items {
-		if item.SystemName != management {
-			continue
-		}
-		for _, addr := range item.IPv4 {
-			if addr.Address == s.cfg.MaintenanceIP {
-				return nil
-			}
-		}
-		break
-	}
-
-	cmd := exec.Command("ip", "addr", "add", s.cfg.MaintenanceCIDR, "dev", management)
-	output, err := cmd.CombinedOutput()
-	if err != nil {
-		if strings.Contains(string(output), "File exists") {
-			return nil
-		}
-		return fmt.Errorf("ip addr add failed on %s: %s", management, strings.TrimSpace(string(output)))
-	}
-	return nil
-}
-
 func (s *Service) listPhysicalInterfaces() ([]model.NetworkInterface, error) {
 	ifaces, err := net.Interfaces()
 	if err != nil {
@@ -118,7 +79,10 @@ func (s *Service) listPhysicalInterfaces() ([]model.NetworkInterface, error) {
 func (s *Service) detectManagement(items []model.NetworkInterface) string {
 	for _, item := range items {
 		for _, addr := range item.IPv4 {
-			if addr.Address == s.cfg.MaintenanceIP {
+			if s.cfg.MaintenanceIP != "" && addr.Address == s.cfg.MaintenanceIP {
+				return item.SystemName
+			}
+			if s.cfg.MaintenanceIP == "" && strings.HasPrefix(addr.Address, "169.254.") {
 				return item.SystemName
 			}
 		}
@@ -158,7 +122,7 @@ func readIPv4(iface net.Interface) []model.IPv4Address {
 		}
 		prefix, _ := ipNet.Mask.Size()
 		source := "unknown"
-		if ipNet.IP.String() == "169.254.100.2" {
+		if strings.HasPrefix(ipNet.IP.String(), "169.254.") {
 			source = "static"
 		}
 		result = append(result, model.IPv4Address{Address: ipNet.IP.String(), Prefix: prefix, Source: source})

+ 32 - 4
windows/NetworkTool.Client/Services/DiscoveryService.cs

@@ -92,8 +92,7 @@ public sealed class DiscoveryService
                 var response = JsonSerializer.Deserialize<DiscoveryResponse>(result.Buffer);
                 if (response is null
                     || response.MessageType != "discover_response"
-                    || string.IsNullOrWhiteSpace(response.Lan2Ip)
-                    || !response.Lan2Ip.StartsWith("169.254.", StringComparison.Ordinal))
+                    || !TryGetDiscoveredLinkLocalIp(response.Lan2Ip, result.RemoteEndPoint.Address, out var discoveredIp))
                 {
                     continue;
                 }
@@ -110,12 +109,12 @@ public sealed class DiscoveryService
                     Hostname = response.Hostname ?? string.Empty,
                     ServerVersion = response.ServerVersion ?? string.Empty,
                     Mac = mac ?? string.Empty,
-                    Lan2Ip = response.Lan2Ip,
+                    Lan2Ip = discoveredIp,
                     HttpPort = response.HttpPort,
                     AuthRequired = response.AuthRequired,
                 };
 
-                devicesByIp[response.Lan2Ip] = device;
+                devicesByIp[discoveredIp] = device;
                 onDeviceDiscovered?.Invoke(device);
             }
             catch (OperationCanceledException)
@@ -127,6 +126,35 @@ public sealed class DiscoveryService
         return devicesByIp.Values.OrderBy(device => device.Lan2Ip).ToList();
     }
 
+    private static bool TryGetDiscoveredLinkLocalIp(string? lan2Ip, IPAddress remoteAddress, out string discoveredIp)
+    {
+        if (IsLinkLocalIPv4(lan2Ip))
+        {
+            discoveredIp = lan2Ip!.Trim();
+            return true;
+        }
+
+        if (IsLinkLocalIPv4(remoteAddress))
+        {
+            discoveredIp = remoteAddress.ToString();
+            return true;
+        }
+
+        discoveredIp = string.Empty;
+        return false;
+    }
+
+    private static bool IsLinkLocalIPv4(string? ipAddress)
+    {
+        return IPAddress.TryParse(ipAddress, out var parsed) && IsLinkLocalIPv4(parsed);
+    }
+
+    private static bool IsLinkLocalIPv4(IPAddress ipAddress)
+    {
+        var bytes = ipAddress.GetAddressBytes();
+        return ipAddress.AddressFamily == AddressFamily.InterNetwork && bytes[0] == 169 && bytes[1] == 254;
+    }
+
     private static string ResolveMacAddress(IPAddress ipAddress)
     {
         if (ipAddress.AddressFamily != AddressFamily.InterNetwork)