Kaynağa Gözat

feat(network): 校验路由目标必须为网络地址

后端与客户端同步增加CIDR网络地址校验,修正文档说明并更新版本号
yangkaixiang 1 ay önce
ebeveyn
işleme
40d6245133

+ 1 - 1
docs/03-通信与HTTP_API.md

@@ -327,7 +327,7 @@ X-Admin-Password: hvAC2026#%
 3. `addresses[].prefix` 必填
 4. `dns` 选填
 5. `interface` 必填,必须是有效的真实接口名
-6. `routes[].to` 必须为 `default` 或 IPv4 CIDR
+6. `routes[].to` 必须为 `default` 或 IPv4 CIDR,且 CIDR 地址必须是该前缀下的网络地址,例如 `192.168.50.0/24` 合法,`192.168.50.0/16` 应写为 `192.168.0.0/16`
 7. `routes[].via` 必须为 IPv4 地址,且必须与任一 `addresses` 在同一子网
 8. 若任一 `addresses[].ip` 为 `169.254.x.x`,返回中文警告,不直接报错
 9. 单个接口最多只能配置 1 条 `default` 路由

+ 1 - 0
docs/09-使用说明与故障处理.md

@@ -185,6 +185,7 @@ hvAC2026#%
 5. 默认网关必须与对应网口 IP 在同一网段。
 6. 单个接口最多只能有一条默认路由。
 7. 所有网口中最多只能配置一个默认网关。
+8. 自定义路由目标必须是网络地址,例如 `192.168.50.0/24` 合法,`192.168.50.0/16` 应写为 `192.168.0.0/16`。
 
 ### 3.8 保存配置
 

+ 1 - 1
server/internal/config/config.go

@@ -6,7 +6,7 @@ import (
 	"net"
 )
 
-const ServerVersion = "2026.05.15.1719"
+const ServerVersion = "2026.05.15.1730"
 
 type Config struct {
 	HTTPHost         string

+ 7 - 0
server/internal/network/validator/validator.go

@@ -83,6 +83,13 @@ func (s *Service) Validate(input model.InterfaceConfig) model.ValidateResponse {
 			ip, ipNet, err := net.ParseCIDR(to)
 			if err != nil || ip == nil || ip.To4() == nil || ipNet == nil {
 				resp.Errors = append(resp.Errors, fmt.Sprintf("路由目标格式不正确:%s", to))
+			} else {
+				ipv4 := ip.To4()
+				networkIP := ipv4.Mask(ipNet.Mask)
+				prefix, _ := ipNet.Mask.Size()
+				if !ipv4.Equal(networkIP) {
+					resp.Errors = append(resp.Errors, fmt.Sprintf("路由目标必须是网络地址:当前为 %s,应为 %s/%d", to, networkIP.String(), prefix))
+				}
 			}
 		}
 		gateway := net.ParseIP(via)

+ 33 - 0
windows/NetTool.Client/DeviceDetailsWindow.xaml.cs

@@ -690,6 +690,18 @@ public partial class DeviceDetailsWindow : Window
                     error = $"自定义路由子网掩码格式不正确:{to} {maskText}";
                     return false;
                 }
+                if (!TryGetNetworkAddress(to, prefix, out var networkAddress))
+                {
+                    routes = [];
+                    error = $"自定义路由目标网段格式不正确:{to}";
+                    return false;
+                }
+                if (!string.Equals(to, networkAddress, StringComparison.Ordinal))
+                {
+                    routes = [];
+                    error = $"自定义路由目标必须是网络地址:当前为 {to}/{prefix},应为 {networkAddress}/{prefix}";
+                    return false;
+                }
                 row.Mask = PrefixToMask(prefix);
                 result.Add(new RemoteInterfaceRouteConfig { To = $"{to}/{prefix}", Via = via });
             }
@@ -824,6 +836,27 @@ public partial class DeviceDetailsWindow : Window
         return ip == network || ip == broadcast;
     }
 
+    private static bool TryGetNetworkAddress(string ipText, int prefix, out string networkAddress)
+    {
+        networkAddress = string.Empty;
+        if (prefix < 0 || prefix > 32 || !IPAddress.TryParse(ipText, out var parsed))
+        {
+            return false;
+        }
+
+        var bytes = parsed.GetAddressBytes();
+        if (bytes.Length != 4)
+        {
+            return false;
+        }
+
+        var ip = ((uint)bytes[0] << 24) | ((uint)bytes[1] << 16) | ((uint)bytes[2] << 8) | bytes[3];
+        var mask = prefix == 0 ? 0u : uint.MaxValue << (32 - prefix);
+        var network = ip & mask;
+        networkAddress = string.Join('.', new[] { (network >> 24) & 255, (network >> 16) & 255, (network >> 8) & 255, network & 255 });
+        return true;
+    }
+
     private static bool TryMaskOrPrefixToPrefix(string text, out int prefix)
     {
         if (int.TryParse(text, out prefix) && prefix >= 0 && prefix <= 32)

+ 1 - 1
windows/NetTool.Client/NetTool.Client.csproj

@@ -8,7 +8,7 @@
     <UseWPF>true</UseWPF>
     <AssemblyName>NetTool.Client</AssemblyName>
     <RootNamespace>NetTool.Client</RootNamespace>
-    <InformationalVersion>2026.05.15.1723</InformationalVersion>
+    <InformationalVersion>2026.05.15.1730</InformationalVersion>
   </PropertyGroup>
 
 </Project>