diff --git a/src/main/java/com/cisd/tms/modules/device/controller/IpWhitelistController.java b/src/main/java/com/cisd/tms/modules/device/controller/IpWhitelistController.java index 789da7f..cc08dde 100644 --- a/src/main/java/com/cisd/tms/modules/device/controller/IpWhitelistController.java +++ b/src/main/java/com/cisd/tms/modules/device/controller/IpWhitelistController.java @@ -23,33 +23,34 @@ public class IpWhitelistController { @PostMapping("/add") @Operation(summary = "添加IP白名单", description = "新增一条IP白名单记录") @ReplayProtected - public ApiResponse addWhitelist(@RequestBody IpWhitelistRequest req) { + public ApiResponse addWhitelist(@RequestBody IpWhitelistRequest req) { ipWhitelistService.addWhitelist(req); - return ApiResponse.success("添加成功"); + return ApiResponse.success(); } @PostMapping("/update") @Operation(summary = "更新IP白名单", description = "根据ID更新IP白名单信息") @ReplayProtected - public ApiResponse updateWhitelist(@RequestBody IpWhitelistRequest req) { + public ApiResponse updateWhitelist(@RequestBody IpWhitelistRequest req) { ipWhitelistService.updateWhitelist(req); - return ApiResponse.success("更新成功"); + return ApiResponse.success(); } @PostMapping("/delete/{id}") @Operation(summary = "删除IP白名单", description = "根据ID删除指定IP白名单") @ReplayProtected - public ApiResponse deleteWhitelist(@PathVariable Long id) { + public ApiResponse deleteWhitelist(@PathVariable Long id) { ipWhitelistService.deleteWhitelist(id); - return ApiResponse.success("删除成功"); + return ApiResponse.success(); } - //todo 要么分页写到@RequestParam,要么改用post + @GetMapping("/page") @Operation(summary = "分页查询IP白名单", description = "支持分页查询IP白名单列表") - public ApiResponse> getWhitelistPage(@RequestBody IpWhitelistRequest req) { - IPage page = ipWhitelistService.getWhitelistPage(req); + public ApiResponse> getWhitelistPage(@RequestParam int pageNum, + @RequestParam int pageSize) { + IPage page = ipWhitelistService.getWhitelistPage(pageNum, pageSize); return ApiResponse.success(page); } } diff --git a/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java b/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java index 4b36294..bd1789c 100644 --- a/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java +++ b/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java @@ -38,9 +38,9 @@ public class NetworkConfigController { @Operation(summary = "设置IPv4配置", description = "为指定网络接口配置IPv4地址、掩码、网关等信息") @PostMapping("/ipv4-config/set") @ReplayProtected - public ApiResponse setIpv4Config(@RequestBody Ipv4ConfigRequest req) { - String resultMessage = networkConfigService.setIpv4Config(req); - return ApiResponse.success(resultMessage); + public ApiResponse setIpv4Config(@RequestBody Ipv4ConfigRequest req) { + networkConfigService.setIpv4Config(req); + return ApiResponse.success(); } @Operation(summary = "获取IPv6配置信息", description = "根据设备名称获取指定网络接口的IPv6配置详情") @@ -53,9 +53,9 @@ public class NetworkConfigController { @Operation(summary = "设置IPv6配置", description = "为指定网络接口配置IPv6地址、前缀长度、网关等信息") @PostMapping("/ipv6-config/set") @ReplayProtected - public ApiResponse setIpv6Config(@RequestBody Ipv6ConfigRequest req) { - String resultMessage = networkConfigService.setIpv6Config(req); - return ApiResponse.success(resultMessage); + public ApiResponse setIpv6Config(@RequestBody Ipv6ConfigRequest req) { + networkConfigService.setIpv6Config(req); + return ApiResponse.success(); } @Operation(summary = "获取所有Bond名称", description = "返回当前系统中所有已创建的Bond接口名称") @@ -67,26 +67,26 @@ public class NetworkConfigController { @Operation(summary = "创建Bond", description = "创建一个新的Bond聚合接口,需指定名称、模式和从属接口") @PostMapping("/bond/create") @ReplayProtected - public ApiResponse createBond(@RequestBody BondCreateRequest req) { - String resultMessage = networkConfigService.createBond(req); - return ApiResponse.success(resultMessage); + public ApiResponse createBond(@RequestBody BondCreateRequest req) { + networkConfigService.createBond(req); + return ApiResponse.success(); } @Operation(summary = "删除Bond", description = "根据Bond名称删除指定的聚合接口") @PostMapping("/bond/delete/{bondName}") @ReplayProtected - public ApiResponse deleteBond(@PathVariable("bondName") String bondName){ - String resultMessage = networkConfigService.deleteBond(bondName); - return ApiResponse.success(resultMessage); + public ApiResponse deleteBond(@PathVariable("bondName") String bondName){ + networkConfigService.deleteBond(bondName); + return ApiResponse.success(); } @Operation(summary = "添加从属接口到Bond", description = "向指定Bond中添加一个或多个从属网络接口") @PostMapping("/bond-slave/add") @ReplayProtected - public ApiResponse addSlavesTOBond(@RequestBody BondAddSlavesRequest req) { - String resultMessage = networkConfigService.addSlavesTOBond(req); - return ApiResponse.success(resultMessage); + public ApiResponse addSlavesTOBond(@RequestBody BondAddSlavesRequest req) { + networkConfigService.addSlavesTOBond(req); + return ApiResponse.success(); } @Operation(summary = "从Bond中移除从属接口", description = "从指定Bond中移除一个或多个从属网络接口") @@ -120,10 +120,9 @@ public class NetworkConfigController { @Operation(summary = "设置Bond模式", description = "修改指定Bond的绑定模式") @PostMapping("/bond/mode/set") @ReplayProtected - public ApiResponse setBondMode(@RequestBody BondModifyModeRequest req) { - - String resultMessage = networkConfigService.setBondMode(req); - return ApiResponse.success(resultMessage); + public ApiResponse setBondMode(@RequestBody BondModifyModeRequest req) { + networkConfigService.setBondMode(req); + return ApiResponse.success(); } @Operation(summary = "获取路由表", description = "返回当前系统的IPv4/IPv6路由表信息") @@ -136,32 +135,32 @@ public class NetworkConfigController { @Operation(summary = "设置默认路由", description = "配置或修改系统的默认网关路由") @PostMapping("/routes/default/set") @ReplayProtected - public ApiResponse setDefaultRoute(@RequestBody SetDefaultRouteRequest req) { - String resultMessage = networkConfigService.setDefaultRoute(req); - return ApiResponse.success(resultMessage); + public ApiResponse setDefaultRoute(@RequestBody SetDefaultRouteRequest req) { + networkConfigService.setDefaultRoute(req); + return ApiResponse.success(); } @Operation(summary = "添加静态路由", description = "新增一条静态路由规则") @PostMapping("/routes/static/add") @ReplayProtected - public ApiResponse addStaticRoute(@RequestBody AddStaticRouteRequest req) { - String resultMessage = networkConfigService.addStaticRoute(req); - return ApiResponse.success(resultMessage); + public ApiResponse addStaticRoute(@RequestBody AddStaticRouteRequest req) { + networkConfigService.addStaticRoute(req); + return ApiResponse.success(); } @Operation(summary = "删除默认路由", description = "删除指定的默认路由") @PostMapping("/routes/default/delete") @ReplayProtected - public ApiResponse deleteDefaultRoute(@RequestBody DeleteDefaultRouteRequest req) { - String resultMessage = networkConfigService.deleteDefaultRoute(req); - return ApiResponse.success(resultMessage); + public ApiResponse deleteDefaultRoute(@RequestBody DeleteDefaultRouteRequest req) { + networkConfigService.deleteDefaultRoute(req); + return ApiResponse.success(); } @Operation(summary = "删除静态路由", description = "删除指定的静态路由") @PostMapping("/routes/static/delete") @ReplayProtected - public ApiResponse deleteStaticRoute(@RequestBody DeleteStaticRouteRequest req) { - String resultMessage = networkConfigService.deleteStaticRoute(req); - return ApiResponse.success(resultMessage); + public ApiResponse deleteStaticRoute(@RequestBody DeleteStaticRouteRequest req) { + networkConfigService.deleteStaticRoute(req); + return ApiResponse.success(); } } diff --git a/src/main/java/com/cisd/tms/modules/device/dto/TimeConfigRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/TimeConfigRequest.java index 52e2da0..bf07ff6 100644 --- a/src/main/java/com/cisd/tms/modules/device/dto/TimeConfigRequest.java +++ b/src/main/java/com/cisd/tms/modules/device/dto/TimeConfigRequest.java @@ -28,8 +28,8 @@ public class TimeConfigRequest { example = "[\"ntp.aliyun.com\", \"ntp.tencent.com\"]") private List ntpServers; - @Schema(description = "NTP同步间隔时间 (单位: 秒。默认 600)", - example = "600") + @Schema(description = "NTP同步间隔时间 (单位: 秒。默认 512)", + example = "512") private Integer syncInterval; } diff --git a/src/main/java/com/cisd/tms/modules/device/filter/IpWhitelistFilter.java b/src/main/java/com/cisd/tms/modules/device/filter/IpWhitelistFilter.java index 66aeb2f..f2afe3a 100644 --- a/src/main/java/com/cisd/tms/modules/device/filter/IpWhitelistFilter.java +++ b/src/main/java/com/cisd/tms/modules/device/filter/IpWhitelistFilter.java @@ -86,6 +86,19 @@ public class IpWhitelistFilter implements Filter { } private String getClientIp(HttpServletRequest request) { + + String remoteAddr = request.getRemoteAddr(); + + //TODO 后续添加网关ip + //添加信任的网关ip + String[] trustedProxies = {}; + boolean isTrustedProxy = false; + for (String proxy : trustedProxies) { + if (remoteAddr.equals(proxy)) { + isTrustedProxy = true; + break; + } + } //todo 这几个header获取的ip可能是客户端伪造的 String[] headers = { "X-Forwarded-For", @@ -94,13 +107,15 @@ public class IpWhitelistFilter implements Filter { "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR" }; - for (String header : headers) { - String ip = request.getHeader(header); - if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) { - return ip.split(",")[0].trim(); + if (isTrustedProxy) { + for (String header : headers) { + String ip = request.getHeader(header); + if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) { + return ip.split(",")[0].trim(); + } } } - return request.getRemoteAddr(); + return remoteAddr; } diff --git a/src/main/java/com/cisd/tms/modules/device/repository/IpWhitelistRepository.java b/src/main/java/com/cisd/tms/modules/device/repository/IpWhitelistRepository.java index 21b85ad..0611521 100644 --- a/src/main/java/com/cisd/tms/modules/device/repository/IpWhitelistRepository.java +++ b/src/main/java/com/cisd/tms/modules/device/repository/IpWhitelistRepository.java @@ -22,7 +22,7 @@ public interface IpWhitelistRepository { /** * 分页查询白名单 */ - Page selectPage(Page page, IpWhitelistRequest req); + Page selectPage(Page page); /** * 查询所有白名单 diff --git a/src/main/java/com/cisd/tms/modules/device/repository/impl/IpWhiltelistRepositoryImpl.java b/src/main/java/com/cisd/tms/modules/device/repository/impl/IpWhiltelistRepositoryImpl.java index cdd7b01..8ac3d88 100644 --- a/src/main/java/com/cisd/tms/modules/device/repository/impl/IpWhiltelistRepositoryImpl.java +++ b/src/main/java/com/cisd/tms/modules/device/repository/impl/IpWhiltelistRepositoryImpl.java @@ -53,7 +53,7 @@ public class IpWhiltelistRepositoryImpl implements IpWhitelistRepository { } @Override - public Page selectPage(Page page, IpWhitelistRequest req) { + public Page selectPage(Page page) { // LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); // // if (StringUtils.hasText(req.getIp())) { diff --git a/src/main/java/com/cisd/tms/modules/device/service/IpWhitelistService.java b/src/main/java/com/cisd/tms/modules/device/service/IpWhitelistService.java index b024b95..18a978e 100644 --- a/src/main/java/com/cisd/tms/modules/device/service/IpWhitelistService.java +++ b/src/main/java/com/cisd/tms/modules/device/service/IpWhitelistService.java @@ -2,6 +2,7 @@ package com.cisd.tms.modules.device.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.common.enums.ErrorCode; import com.cisd.tms.common.exception.BizException; import com.cisd.tms.modules.device.dto.network.IpWhitelistRequest; import com.cisd.tms.modules.device.dto.network.IpWhitelistResponse; @@ -11,6 +12,7 @@ import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; +import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -19,41 +21,48 @@ public class IpWhitelistService { private final IpWhitelistRepository ipWhitelistRepository; + private static final Pattern IPV4_PATTERN = Pattern.compile( + "^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$" + ); + + private static final Pattern CIDR_PATTERN = Pattern.compile( + "^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)/(0|[1-9]|[1-2]\\d|3[0-2])$" + ); + public IpWhitelistService(IpWhitelistRepository ipWhitelistRepository) { this.ipWhitelistRepository = ipWhitelistRepository; } public void addWhitelist(IpWhitelistRequest req) { // 新增前校验 IP+掩码 是否已存在 - checkIpRule(req.getIp(), req.getMask(), null); - - IpWhitelistEntity entity = new IpWhitelistEntity(); String ip = req.getIp(); String mask = req.getMask(); + checkIpRule(ip, mask, null); + IpWhitelistEntity entity = new IpWhitelistEntity(); - entity.setIp(ip); - entity.setMask(mask); + entity.setIp(ip.trim()); + entity.setMask(mask.trim()); ipWhitelistRepository.addWhitelist(entity); } // public void updateWhitelist(IpWhitelistRequest req) { if (req.getId() == null || req.getId().trim().isEmpty()) { - throw new IllegalArgumentException("ID should not be null"); + throw new IllegalArgumentException("ID 为空"); } - Long idLong = Long.valueOf(req.getId()); + Long idLong = Long.valueOf(req.getId().trim()); checkIpRule(req.getIp(), req.getMask(), idLong); Optional entityOpt = ipWhitelistRepository.findById(idLong); if (entityOpt.isEmpty()) { - throw new BizException(404, "ID is not exist"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "ID不存在"); } IpWhitelistEntity entity = entityOpt.get(); - entity.setIp(req.getIp()); - entity.setMask(req.getMask()); + entity.setIp(req.getIp().trim()); + entity.setMask(req.getMask().trim()); ipWhitelistRepository.updateWhitelist(entity); } @@ -61,15 +70,15 @@ public class IpWhitelistService { public void deleteWhitelist(Long id) { Optional entityOpt = ipWhitelistRepository.findById(id); if (entityOpt.isEmpty()) { - throw new BizException(404, "ID is not exist"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "ID不存在"); } ipWhitelistRepository.deleteById(id); } - public IPage getWhitelistPage(IpWhitelistRequest req) { - Page page = new Page<>(req.getPageNum(), req.getPageSize()); - ipWhitelistRepository.selectPage(page, req); + public IPage getWhitelistPage(int pageNum, int pageSize) { + Page page = new Page<>(pageNum, pageSize); + ipWhitelistRepository.selectPage(page); return page.convert(entity -> { @@ -93,13 +102,15 @@ public class IpWhitelistService { */ private void checkIpRule(String ip, String mask, Long excludeId) { if (ip == null || ip.isEmpty()){ - throw new IllegalArgumentException("ip is empty"); + throw new IllegalArgumentException("ip地址为空"); } if (mask == null || mask.isEmpty()){ - throw new IllegalArgumentException("mask is empty"); + throw new IllegalArgumentException("掩码为空"); } - + if (!IPV4_PATTERN.matcher(ip.trim()).matches()){ + throw new IllegalArgumentException("非法的 IPv4 地址: " + ip); + } String[] octets = ip.split("\\."); @@ -118,14 +129,19 @@ public class IpWhitelistService { } } - int maskInt= Integer.parseInt(mask); + int maskInt = Integer.parseInt(mask); if (maskInt < 0 || maskInt > 32) { throw new IllegalArgumentException("掩码范围需要在 0 到 32 之间"); } if (ipWhitelistRepository.existsByIpAndMask(ip, mask, excludeId)) { - throw new BizException(404, "已存在该 IP 和掩码配置"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "已存在该 IP 和掩码配置"); } } + + + private static String trim(String value) { + return value == null ? "" : value.trim(); + } } diff --git a/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java b/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java index 8d57c3c..509da7f 100644 --- a/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java +++ b/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java @@ -11,14 +11,20 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.Inet6Address; import java.net.InetAddress; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import java.util.*; +import java.util.regex.Pattern; @Service @Slf4j public class NetworkConfigService { + private static final Pattern IPV4_PATTERN = Pattern.compile( + "^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)$" + ); + + private static final Pattern CIDR_PATTERN = Pattern.compile( + "^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]?\\d)/(0|[1-9]|[1-2]\\d|3[0-2])$" + ); public List getNetworkInfo(){ @@ -70,7 +76,7 @@ public class NetworkConfigService { public Ipv4InfoResponse getIpv4Info(String deviceName){ - String connectionName = getConnectionNameByDeviceName(deviceName); + String connectionName = getConnectionNameByDeviceName(deviceName.trim()); if (connectionName == null){ connectionName = deviceName; } @@ -121,33 +127,41 @@ public class NetworkConfigService { } - public String setIpv4Config(Ipv4ConfigRequest req){ - - if (req.getDeviceName() == null || req.getDeviceName().trim().isEmpty()) { + public void setIpv4Config(Ipv4ConfigRequest req){ + String ipv4 = trim(req.getIpv4()); + String deviceName = trim(req.getDeviceName()); + String mask = trim(req.getMaskLength()); + if (deviceName.isEmpty()) { throw new IllegalArgumentException("网卡名称不能为空"); } - - - if (!isValidIpv4(req.getIpv4())) { + if (ipv4.isEmpty()) { + throw new IllegalArgumentException("ip地址不能为空"); + } + if (mask.isEmpty()) { + throw new IllegalArgumentException("子网掩码长度不能为空"); + } + if (!IPV4_PATTERN.matcher(ipv4).matches()) { throw new IllegalArgumentException("无效的 IP 地址格式: " + req.getIpv4()); } - boolean hasGateway = req.getGateway() != null && !req.getGateway().trim().isEmpty(); - - if (hasGateway && !isValidIpv4(req.getGateway())) { + if (hasGateway && !IPV4_PATTERN.matcher(req.getGateway().trim()).matches()) { throw new IllegalArgumentException("无效的网关地址格式: " + req.getGateway()); } - String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + //判断连接是否存在,如果不存在则新建新连接 + boolean isNewConnection = false; + String connectionName = getConnectionNameByDeviceName(deviceName); if (connectionName == null){ - connectionName = req.getDeviceName(); + connectionName = deviceName; + isNewConnection = true; // 标记为需要执行 add 创建配置 } + // 转化掩码格式并判断掩码格式是否正确 int maskLength = 0; try { - String netmask = req.getMaskLength(); - if (netmask == null || netmask.isEmpty()) { + String netmask = trim(req.getMaskLength()); + if (netmask.isEmpty()) { throw new IllegalArgumentException("掩码不能为空"); } // 传的是数字/24 @@ -170,9 +184,9 @@ public class NetworkConfigService { } //判断ip和网关是否在同一个子网里面 - int ip1Int = ipToInt(req.getIpv4()); + int ip1Int = ipToInt(req.getIpv4().trim()); if (hasGateway){ - int ip2Int = ipToInt(req.getGateway()); + int ip2Int = ipToInt(req.getGateway().trim()); int maskInt = (maskLength == 0) ? 0 : (0xFFFFFFFF << (32 - maskLength)); if (!((ip1Int & maskInt) == (ip2Int & maskInt))){ throw new IllegalArgumentException( @@ -184,17 +198,36 @@ public class NetworkConfigService { String cidr = req.getIpv4() + "/" + maskLength; - //todo 这里需要判断hasGateway是否为true,再确定命令是否有 ipv4.gateway。 try { - executeCommand("nmcli", "con", "mod", connectionName, - "ipv4.method", "manual", - "ipv4.addresses", cidr, - "ipv4.gateway", req.getGateway()); + if (isNewConnection) { + List cmdArgs = new ArrayList<>(); + cmdArgs.add("nmcli"); cmdArgs.add("con"); cmdArgs.add("add"); + cmdArgs.add("type"); cmdArgs.add("ethernet"); // 默认创建以太网类型 + cmdArgs.add("con-name"); cmdArgs.add(connectionName); + cmdArgs.add("ifname"); cmdArgs.add(deviceName); + cmdArgs.add("ipv4.method"); cmdArgs.add("manual"); + cmdArgs.add("ipv4.addresses"); cmdArgs.add(cidr); + if (hasGateway) { + cmdArgs.add("ipv4.gateway");cmdArgs.add(req.getGateway()); + cmdArgs.add("ipv4.never-default");cmdArgs.add("yes"); + } + executeCommand(cmdArgs.toArray(new String[0])); + } else { + if (hasGateway) { + executeCommand("nmcli", "con", "mod", connectionName, + "ipv4.method", "manual", + "ipv4.addresses", cidr, + "ipv4.gateway", req.getGateway(), + "ipv4.never-default", "yes"); + } else { + executeCommand("nmcli", "con", "mod", connectionName, + "ipv4.method", "manual", + "ipv4.addresses", cidr, + "ipv4.gateway", ""); + } + } executeCommand("nmcli", "con", "up", connectionName); - - return "网络连接 [" + req.getDeviceName() + "] IPv4 配置成功"; - } catch (RuntimeException e) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改网络配置失败: " + e.getMessage()); @@ -203,7 +236,7 @@ public class NetworkConfigService { public Ipv6InfoResponse getIpv6Info(String deviceName){ - + deviceName = trim(deviceName); String connectionName = getConnectionNameByDeviceName(deviceName); if (connectionName == null){ connectionName = deviceName; @@ -238,10 +271,10 @@ public class NetworkConfigService { Ipv6AddressItem item = new Ipv6AddressItem(); if (ipAndMaskLength.contains("/")) { String[] parts = ipAndMaskLength.split("/"); - item.setIpv6(parts[0]); + item.setIpv6(parts[0].replace("\\:", ":")); item.setMaskLength(parts[1]); } else { - item.setIpv6(ipAndMaskLength); + item.setIpv6(ipAndMaskLength.replace("\\:", ":")); } // 将解析好的单个 IP 对象放入集合 addressList.add(item); @@ -251,7 +284,7 @@ public class NetworkConfigService { if (lines.size()> 1 && !lines.get(1).trim().isEmpty()) { - resp.setGateway(lines.get(1).trim()); + resp.setGateway(lines.get(1).trim().replace("\\:", ":")); } if (lines.size() > 2 && !lines.get(2).trim().isEmpty()) { @@ -268,7 +301,8 @@ public class NetworkConfigService { return resp; } - public String setIpv6Config(Ipv6ConfigRequest req){ + public void setIpv6Config(Ipv6ConfigRequest req){ + String deviceName = trim(req.getDeviceName()); if (req.getDeviceName() == null || req.getDeviceName().trim().isEmpty()) { throw new IllegalArgumentException("网卡名称不能为空"); } @@ -293,29 +327,47 @@ public class NetworkConfigService { throw new IllegalArgumentException("无效的 IPv6 前缀长度,必须在 0 到 128 之间"); } - String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + // 判断连接是否存在,如果不存在则需要新建 + boolean isNewConnection = false; + String connectionName = getConnectionNameByDeviceName(deviceName); if (connectionName == null){ - connectionName = req.getDeviceName(); + connectionName = deviceName; + isNewConnection = true; } - String ipWithPrefix = req.getIpv6() + "/" + req.getMaskLength(); + String ipWithPrefix = req.getIpv6().trim() + "/" + req.getMaskLength().trim(); try { - if (hasGateway) { - executeCommand("nmcli", "con", "mod", connectionName, - "ipv6.method", "manual", - "ipv6.addresses", ipWithPrefix, - "ipv6.gateway", req.getGateway()); + if (isNewConnection) { + List cmdArgs = new ArrayList<>(); + cmdArgs.add("nmcli"); cmdArgs.add("con"); cmdArgs.add("add"); + cmdArgs.add("type"); cmdArgs.add("ethernet"); + cmdArgs.add("con-name"); cmdArgs.add(connectionName); + cmdArgs.add("ifname"); cmdArgs.add(deviceName); + cmdArgs.add("ipv6.method"); cmdArgs.add("manual"); + cmdArgs.add("ipv6.addresses"); cmdArgs.add(ipWithPrefix); + if (hasGateway) { + cmdArgs.add("ipv6.gateway"); cmdArgs.add(req.getGateway().trim()); + cmdArgs.add("ipv6.never-default");cmdArgs.add("yes"); + } + executeCommand(cmdArgs.toArray(new String[0])); } else { - executeCommand("nmcli", "con", "mod", connectionName, - "ipv6.method", "manual", - "ipv6.addresses", ipWithPrefix); + if (hasGateway) { + executeCommand("nmcli", "con", "mod", connectionName, + "ipv6.method", "manual", + "ipv6.addresses", ipWithPrefix, + "ipv6.gateway", req.getGateway().trim(), + "ipv6.never-default","yes"); + } else { + executeCommand("nmcli", "con", "mod", connectionName, + "ipv6.method", "manual", + "ipv6.addresses", ipWithPrefix, + "ipv6.gateway", ""); + } } executeCommand("nmcli", "con", "up", connectionName); - return "网络连接 [" + req.getDeviceName() + "] IPv6 配置成功"; - } catch (RuntimeException e) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改 IPv6 网络配置失败: " + e.getMessage()); } @@ -327,7 +379,7 @@ public class NetworkConfigService { List lines = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device"); for (String line : lines) { - String[] parts = line.split(":", -1); + String[] parts = line.split("(?= 2 && "bond".equals(parts[1])) { bondNames.add(parts[0]); } @@ -335,8 +387,9 @@ public class NetworkConfigService { return bondNames; } - public String createBond(BondCreateRequest req){ - if (req.getBondName() == null || req.getBondName().trim().isEmpty()) { + public void createBond(BondCreateRequest req){ + String bondName = trim(req.getBondName()); + if (bondName.isEmpty()) { throw new IllegalArgumentException("Bond 名称不能为空"); } if (req.getMode() == null) { @@ -347,8 +400,8 @@ public class NetworkConfigService { try { List lines = executeCommand("nmcli", "-g", "NAME", "con", "show"); for (String line : lines) { - if (req.getBondName().equals(line.trim())) { - throw new RuntimeException("网络连接名称 [" + req.getBondName() + "] 已存在,请勿重复创建"); + if (bondName.equals(line.trim())) { + throw new RuntimeException("网络连接名称 [" + bondName + "] 已存在,请勿重复创建"); } } } catch (RuntimeException e) { @@ -365,58 +418,93 @@ public class NetworkConfigService { try { executeCommand("nmcli", "con", "add", "type", "bond", - "con-name", req.getBondName(), - "ifname", req.getBondName(), + "con-name", bondName, + "ifname", bondName, "bond.options", bondOptions); } catch (RuntimeException e) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "创建 Bond 失败: " + e.getMessage()); } if (req.getIpv4Config() != null) { - req.getIpv4Config().setDeviceName(req.getBondName()); + req.getIpv4Config().setDeviceName(bondName); try { setIpv4Config(req.getIpv4Config()); } catch (Exception e) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + req.getBondName() + "] 创建成功,但 IP 配置失败: " + e.getMessage()); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 创建成功,但 IP 配置失败: " + e.getMessage()); } } - - return "Bond [" + req.getBondName() + "] 创建并初始化成功"; } - - public String deleteBond(String bondName) { - if (bondName == null || bondName.isEmpty()) { + public void deleteBond(String bondName) { + if (bondName == null || bondName.trim().isEmpty()) { throw new IllegalArgumentException("Bond 名称不能为空"); } - boolean bondExist = false; + bondName = bondName.trim(); + + List uuidsToDelete = new ArrayList<>(); + boolean bondFound = false; + try { - List lines = executeCommand("nmcli", "-g", "NAME", "con", "show"); + List lines = executeCommand("nmcli", "-t", "-f", "UUID,NAME,TYPE", "con", "show"); + for (String line : lines) { - if (bondName.equals(line.trim())) { - bondExist = true; + if (line.trim().isEmpty()) continue; + String[] parts = line.split("(?= 3) { + String uuid = parts[0]; + String name = parts[1].replace("\\:", ":"); + String type = parts[2]; + + // 匹配到 Bond 自身 + if (bondName.equals(name) && "bond".equals(type)) { + bondFound = true; + uuidsToDelete.add(uuid); + } + else if (type != null && type.contains("ethernet")) { + try { + List masterOutput = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid); + if (!masterOutput.isEmpty() && bondName.equals(masterOutput.get(0).trim())) { + uuidsToDelete.add(0, uuid); + } + } catch (Exception e) { + log.warn("查询连接 [{}] 的 master出现异常: {}", uuid, e.getMessage()); + } + } } } - if (bondExist) { - executeCommand("nmcli", "con", "delete", bondName); - } else { + if (!bondFound) { throw new RuntimeException("Bond [" + bondName + "] 不存在"); } + + + for (String targetUuid : uuidsToDelete) { + //先停后删 + try { + executeCommand("nmcli", "con", "down", "uuid", targetUuid); + } catch (Exception e) { + log.warn("停用连接 [{}] 时出现异常: {}", targetUuid, e.getMessage()); + } + + executeCommand("nmcli", "con", "delete", "uuid", targetUuid); + } + } catch (RuntimeException e) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 删除失败 " + e.getMessage()); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 及从属网卡删除失败: " + e.getMessage()); } - return "Bond [" + bondName + "] 删除成功"; + } - //todo - //这里是先删除物理网卡再添加bond,如果添加失败,那么之前删除的不可逆 - //建议对添加的bondname和slaveList做存在性校验,再执行添加。 或者记录已删除连接 UUID/名称,失败后回滚。 - public String addSlavesTOBond(BondAddSlavesRequest req) { + + + public void addSlavesTOBond(BondAddSlavesRequest req) { String bondName = req.getBondName(); List slaves = req.getSlaveList(); + + if (bondName == null || bondName.trim().isEmpty()) { throw new IllegalArgumentException("Bond 名称不能为空"); } @@ -424,87 +512,145 @@ public class NetworkConfigService { throw new IllegalArgumentException("物理网卡列表不能为空"); } - List existingConnections; + List existingDevices; try { - existingConnections = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME", "con", "show"); + existingConnections = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME,TYPE", "con", "show"); + existingDevices = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE", "dev"); } catch (RuntimeException e) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络连接列表失败"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络状态失败"); } - for (String phyIf : slaves) { - if (phyIf == null || phyIf.trim().isEmpty()){ - continue; + + //查询bond是否存在 + boolean bondExists = false; + for (String line : existingConnections) { + String[] parts = line.split("(?= 4) { + String name = parts[2].replace("\\:", ":"); + String type = parts[3]; + if (bondName.equals(name) && "bond".equals(type)) { + bondExists = true; + break; + } } + } + if (!bondExists) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "目标 Bond [" + bondName + "] 不存在"); + } + + + for (String phyIf : slaves) { + if (phyIf == null || phyIf.trim().isEmpty()) continue; + boolean devExists = existingDevices.stream() + .map(line -> line.split("(? devName.equals(phyIf)); + + if (!devExists) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "当前物理网卡设备 [" + phyIf + "] 在系统中不存在"); + } + } + + + List uuidsToDown = new ArrayList<>(); + for (String phyIf : slaves) { + if (phyIf == null || phyIf.trim().isEmpty()) continue; + String slaveConnectionName = bondName + "-slave-" + phyIf; for (String line : existingConnections) { - if (line.trim().isEmpty()){ - continue; - } - - String slaveConnectionName = bondName + "-slave-" + phyIf; - + if (line.trim().isEmpty()) continue; String[] parts = line.split("(?= 3) { String uuid = parts[0]; String device = parts[1].replace("\\:", ":"); String name = parts[2].replace("\\:", ":"); - if (slaveConnectionName.equals(name) || phyIf.equals(device)) { - try { - executeCommand("nmcli", "con", "delete", "uuid", uuid); - } catch (RuntimeException e) { - log.warn("清理物理网卡 {} 的旧连接/冲突连接 [{}] 失败: {}", phyIf, name, e.getMessage()); - } + if (slaveConnectionName.equals(name) || phyIf.equals(device)) { + uuidsToDown.add(uuid); } } } + } + + //先停止后删除 + for (String uuid : uuidsToDown) { + try { + executeCommand("nmcli", "con", "modify", "uuid", uuid, "connection.autoconnect", "no"); + } catch (RuntimeException e) { + log.warn("修改旧连接 [{}] 的自启属性失败,该连接可能不存在: {}", uuid, e.getMessage()); + } try { + executeCommand("nmcli", "con", "down", "uuid", uuid); + } catch (RuntimeException e) { + log.warn("暂停旧连接 [{}] 失败,该连接可能已经处于断开状态: {}", uuid, e.getMessage()); + } + } + + + List newlyAddedSlaveNames = new ArrayList<>(); // 记录已经成功添加的,用于添加失败回滚 + try { + for (String phyIf : slaves) { + if (phyIf == null || phyIf.trim().isEmpty()) continue; String slaveConnectionName = bondName + "-slave-" + phyIf; + executeCommand("nmcli", "con", "add", "type", "bond-slave", "con-name", slaveConnectionName, "ifname", phyIf, "master", bondName); - } catch (RuntimeException e) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), - String.format("将物理网卡 [%s] 加入 Bond [%s] 失败: %s", phyIf, bondName, e.getMessage())); + + newlyAddedSlaveNames.add(slaveConnectionName); } - } - try { executeCommand("nmcli", "con", "up", bondName); - } catch (RuntimeException e) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "激活 Bond [" + bondName + "] 失败: " + e.getMessage()); - } + //激活后删除 + for (String uuid : uuidsToDown) { + try { + executeCommand("nmcli", "con", "delete", "uuid", uuid); + } catch (Exception e) { + log.warn("Bond配置已生效,但清理废弃的旧连接 [{}] 失败: {}", uuid, e.getMessage()); + } + } - return String.format("Bond [%s] 成功添加 %d 个从属网卡并已激活", bondName, slaves.size()); + } catch (RuntimeException e) { + log.error("将网卡加入 Bond 失败,触发回滚,清理刚创建的 slave 连接", e); + + for (String addedSlaveName : newlyAddedSlaveNames) { + try { + executeCommand("nmcli", "con", "delete", addedSlaveName); + } catch (Exception rollbackEx) { + log.error("回滚失败: 无法删除的从属连接 [{}]", addedSlaveName, rollbackEx); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "将网卡加入 Bond 失败,并且无法删除刚创建的从属连接"); + } + } + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "添加物理网卡到 Bond 失败,已清理刚创建的配置"); + } } - public String removeSlaveFromBond(BondRemoveSlaveRequest req) { - String bondName = req.getBondName(); + String bondName = trim(req.getBondName()); List slaves = req.getSlaveList(); - if (bondName == null || bondName.trim().isEmpty()) { + if (bondName.isEmpty()) { throw new IllegalArgumentException("Bond 名称不能为空"); } if (slaves == null || slaves.isEmpty()) { throw new IllegalArgumentException("物理网卡名称不能为空"); } - int deletedCount = 0; - List notFoundSlaves = new ArrayList<>(); - // 请求输出格式为: UUID:DEVICE:NAME List lines; try { - lines = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME", "con", "show"); + lines = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME,TYPE", "con", "show"); } catch (RuntimeException e) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络连接列表失败"); } + int deletedCount = 0; + for (String phyIf : slaves) { + if (phyIf == null || phyIf.trim().isEmpty()) continue; + String targetUuid = null; String slaveName = bondName + "-slave-" + phyIf; @@ -515,106 +661,111 @@ public class NetworkConfigService { String[] parts = line.split("(?= 3) { + if (parts.length >= 4) { String uuid = parts[0]; String device = parts[1].replace("\\:", ":"); String name = parts[2].replace("\\:", ":"); - if(slaveName.equals(name) || phyIf.equals(device)){ - try{ + if (slaveName.equals(name) || phyIf.equals(device)) { + try { List masterOutputs = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid); String master = masterOutputs.isEmpty() ? "" : masterOutputs.get(0).trim(); if (bondName.equals(master)) { targetUuid = uuid; break; } - }catch(RuntimeException e){ - log.warn("查询网卡 [{}] master 属性失败,可能已被移除或状态异常", name); + } catch (RuntimeException e) { + log.warn("查询网卡 [{}] master 属性失败", name); } } - } } if (targetUuid == null) { - log.warn("未找到物理网卡 [{}] 对应的从属连接", phyIf); - notFoundSlaves.add(phyIf); - continue; + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "未找到网卡 [" + phyIf + "] 对应的从属配置,移除操作已中断"); } try { + try { + executeCommand("nmcli", "con", "down", "uuid", targetUuid); + } catch (Exception e) { + log.warn("停用从属网卡连接 [{}] 失败,忽略并继续删除: {}", targetUuid, e.getMessage()); + } + executeCommand("nmcli", "con", "delete", "uuid", targetUuid); - deletedCount ++; + deletedCount++; } catch (RuntimeException e) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), - String.format("从 Bond [%s] 移除网卡 [%s] 失败。原因: %s", - bondName, phyIf, e.getMessage())); + log.error("移除网卡 [{}] 失败: {}", phyIf, e.getMessage()); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("从 Bond [%s] 移除网卡 [%s] 失败: %s", bondName, phyIf, e.getMessage())); } - } - // 更新Bond状态 - try { - executeCommand("nmcli", "con", "up", bondName); - } catch (RuntimeException e) { - throw new BizException(ErrorCode.BIZ_ERROR.getCode(),"更新 Bond [" + bondName + "] 失败: " + e.getMessage()); - + // 只要有成功删除的记录,就重新激活 Bond 使其生效 + if (deletedCount > 0) { + try { + executeCommand("nmcli", "con", "up", bondName); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "网卡移除成功,但重新激活 Bond [" + bondName + "] 失败: " + e.getMessage()); + } } - if (notFoundSlaves.isEmpty()) { - return String.format("Bond [%s] 删除 %d 个从属网卡并已激活", bondName, deletedCount); - } else if (deletedCount == 0) { - return String.format("Bond [%s] 激活成功,但请求的网卡 %s 均不存在于该 Bond 中", bondName, notFoundSlaves); - } else { - return String.format("Bond [%s] 删除 %d 个从属网卡并已激活。提示:网卡 %s 不存在,已被忽略", - bondName, deletedCount, notFoundSlaves); - } + return String.format("成功从 Bond [%s] 中移除了 %d 个网卡", bondName, deletedCount); } - - public List getBondSlaves(String bondName) { - if (bondName == null || bondName.trim().isEmpty()) { + bondName = trim(bondName); + if (bondName.isEmpty()) { throw new IllegalArgumentException("Bond 名称不能为空"); } List slaveList = new ArrayList<>(); List lines; try { - lines = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME", "con", "show"); + lines = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME,TYPE", "con", "show"); } catch (RuntimeException e) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "查询 Bond 从属网卡列表失败"); } + boolean bondExists = false; + for (String line : lines) { if (line.trim().isEmpty()) { continue; } String[] parts = line.split("(?= 3) { + if (parts.length >= 4) { String uuid = parts[0].replace("\\:", ":"); String device = parts[1].replace("\\:", ":"); String name = parts[2].replace("\\:", ":"); + String type = parts[3]; - List masterOutputs = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid); - String master = masterOutputs.isEmpty() ? "" : masterOutputs.get(0).trim(); + if (bondName.equals(name) && "bond".equals(type)) { + bondExists = true; + } - if (bondName.equals(master)) { - if (!device.isEmpty() && !device.equals("--")) { - // 网卡处于激活连接状态 - slaveList.add(device); - } else { - // 网卡处于断开状态 - String prefix = bondName + "-slave-"; - if (name.startsWith(prefix)) { - slaveList.add(name.substring(prefix.length())); + if (type != null && type.contains("ethernet")) { + List masterOutputs = executeCommand("nmcli", "-g", "connection.master", "con", "show", uuid); + String master = masterOutputs.isEmpty() ? "" : masterOutputs.get(0).trim(); + + if (bondName.equals(master)) { + if (!device.isEmpty() && !device.equals("--")) { + slaveList.add(device); + } else { + String prefix = bondName + "-slave-"; + if (name.startsWith(prefix)) { + slaveList.add(name.substring(prefix.length())); + } } } } } } + if (!bondExists) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 不存在"); + } + return slaveList; } @@ -649,13 +800,14 @@ public class NetworkConfigService { } - public String setBondMode(BondModifyModeRequest req) { + public void setBondMode(BondModifyModeRequest req) { String bondName = req.getBondName(); Integer mode = req.getMode(); if (bondName == null || bondName.trim().isEmpty()) { throw new IllegalArgumentException("Bond 名称不能为空"); } + bondName = bondName.trim(); if (mode == null) { throw new IllegalArgumentException("新的 Bond 模式不能为空"); } @@ -687,7 +839,6 @@ public class NetworkConfigService { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "重新激活 Bond [" + bondName + "] 失败" + e.getMessage()); } - return String.format("Bond [%s] 已成功切换至 [%s] 模式并重启生效", bondName, newMode); } @@ -737,11 +888,11 @@ public class NetworkConfigService { } - public String setDefaultRoute(SetDefaultRouteRequest req) { + public void setDefaultRoute(SetDefaultRouteRequest req) { String deviceName = req.getDeviceName(); String gatewayIp = req.getGatewayIp(); - // 1. 基础校验 + if (deviceName == null || deviceName.trim().isEmpty()) { throw new IllegalArgumentException("网卡名称不能为空"); } @@ -749,7 +900,11 @@ public class NetworkConfigService { throw new IllegalArgumentException("网关 IP 不能为空"); } - String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + if (!IPV4_PATTERN.matcher(req.getGatewayIp().trim()).matches()) { + throw new IllegalArgumentException("无效的网关地址格式: " + req.getGatewayIp()); + } + + String connectionName = getConnectionNameByDeviceName(req.getDeviceName().trim()); if (connectionName == null){ connectionName = req.getDeviceName(); } @@ -758,8 +913,9 @@ public class NetworkConfigService { try { executeCommand("nmcli", "con", "mod", connectionName, "ipv4.gateway", gatewayIp, - //多张网卡配置了网关,需调整 Metric 优先级。 - "ipv4.route-metric", "50"); + //TODO待修改(多张网卡配置了网关,需调整 Metric 优先级)。 + "ipv4.route-metric", "50", + "ipv4.never-default", "no"); } catch (RuntimeException e) { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("为网卡 [%s] 设置网关失败: %s", deviceName, e.getMessage())); } @@ -772,27 +928,32 @@ public class NetworkConfigService { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage())); } - return String.format("成功将网卡 [%s] 的网关设置为 [%s]", deviceName, gatewayIp); } - public String addStaticRoute(AddStaticRouteRequest req) { + public void addStaticRoute(AddStaticRouteRequest req) { String deviceName = req.getDeviceName(); String targetCidr = req.getTargetCidr(); String nextHop = req.getNextHop(); - //todo 对gatewayIp、targetCidr、nextHop增加格式校验。可用正则表达式 if (deviceName == null || deviceName.trim().isEmpty()) { throw new IllegalArgumentException("网卡名称不能为空"); } if (targetCidr == null || targetCidr.trim().isEmpty()) { throw new IllegalArgumentException("目标网段(CIDR)不能为空"); } + + if (!CIDR_PATTERN.matcher(targetCidr.trim()).matches()) { + throw new IllegalArgumentException("非法的CIDR目标网段格式"); + } + + if (nextHop != null && !IPV4_PATTERN.matcher(nextHop.trim()).matches()) { + throw new IllegalArgumentException("非法的下一跳IP格式"); + } // if (nextHop == null || nextHop.trim().isEmpty()) { // throw new IllegalArgumentException("下一跳 IP 不能为空"); // } - - String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + String connectionName = getConnectionNameByDeviceName(req.getDeviceName().trim()); if (connectionName == null){ connectionName = req.getDeviceName(); } @@ -819,18 +980,17 @@ public class NetworkConfigService { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage())); } - return String.format("成功向网卡 [%s] 添加静态路由", deviceName); } - public String deleteDefaultRoute(DeleteDefaultRouteRequest req) { + public void deleteDefaultRoute(DeleteDefaultRouteRequest req) { String deviceName = req.getDeviceName(); if (deviceName == null || deviceName.trim().isEmpty()) { throw new IllegalArgumentException("网卡名称不能为空"); } - String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + String connectionName = getConnectionNameByDeviceName(req.getDeviceName().trim()); if (connectionName == null){ connectionName = req.getDeviceName(); } @@ -848,12 +1008,11 @@ public class NetworkConfigService { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage())); } - return String.format("已成功删除网卡 [%s] 的默认路由", deviceName); } - public String deleteStaticRoute(DeleteStaticRouteRequest req) { + public void deleteStaticRoute(DeleteStaticRouteRequest req) { String deviceName = req.getDeviceName(); String targetCidr = req.getTargetCidr(); String nextHop = req.getNextHop(); @@ -868,6 +1027,14 @@ public class NetworkConfigService { // throw new IllegalArgumentException("下一跳 IP 不能为空"); // } + if (!CIDR_PATTERN.matcher(targetCidr.trim()).matches()) { + throw new IllegalArgumentException("非法的CIDR目标网段格式"); + } + + if (nextHop != null && !IPV4_PATTERN.matcher(nextHop.trim()).matches()) { + throw new IllegalArgumentException("非法的下一跳IP格式"); + } + String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); if (connectionName == null){ connectionName = req.getDeviceName(); @@ -896,24 +1063,23 @@ public class NetworkConfigService { throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage())); } - return String.format("成功从网卡 [%s] 删除静态路由", deviceName); } - private boolean isValidIpv4(String ip) { - if (ip == null || ip.isEmpty()) return false; - try { - ipToInt(ip); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } +// private boolean isValidIpv4(String ip) { +// if (ip == null || ip.isEmpty()) return false; +// try { +// ipToInt(ip); +// return true; +// } catch (IllegalArgumentException e) { +// return false; +// } +// } private boolean isValidIpv6(String ip) { if (ip == null || ip.isEmpty()) return false; try { - InetAddress inetAddress = InetAddress.getByName(ip); + InetAddress inetAddress = InetAddress.getByName(ip.trim()); return inetAddress instanceof Inet6Address; } catch (Exception e) { return false; @@ -925,10 +1091,13 @@ public class NetworkConfigService { if (parts.length != 4) { throw new IllegalArgumentException("非法的 IPv4 地址: " + ipv4); } -//TODO 前导零处理 int result = 0; for (int i = 0; i < 4; i++) { - int octet = Integer.parseInt(parts[i]); + String part = parts[i]; + int octet = Integer.parseInt(part); + if (part.length() > 1 && part.startsWith("0")) { + throw new IllegalArgumentException("非法的 IPv4 地址,存在前导零: " + part); + } if (octet < 0 || octet > 255) { throw new IllegalArgumentException("非法的 IPv4 地址段: " + octet); } @@ -999,7 +1168,67 @@ public class NetworkConfigService { throw new RuntimeException("服务器内部处理中断");} } + + private static String trim(String value) { + return value == null ? "" : value.trim(); + } } - +//private void applyNmcliConnectionConfig(String deviceName, boolean isIpv6, String ipWithPrefix, String gateway) { +// String protocol = isIpv6 ? "ipv6" : "ipv4"; +// boolean hasGateway = gateway != null && !gateway.trim().isEmpty(); +// +// boolean isNewConnection = false; +// String connectionName = getConnectionNameByDeviceName(deviceName); +// if (connectionName == null) { +// connectionName = deviceName; +// isNewConnection = true; +// } +// +// if (isNewConnection) { +// // 新建连接 +// List cmdArgs = new ArrayList<>(Arrays.asList( +// "nmcli", "con", "add", +// "type", "ethernet", +// "con-name", connectionName, +// "ifname", deviceName, +// protocol + ".method", "manual", +// protocol + ".addresses", ipWithPrefix +// )); +// +// if (hasGateway) { +// cmdArgs.add(protocol + ".gateway"); +// cmdArgs.add(gateway.trim()); +// // 设置了网关,但明确告诉系统:不要把它当默认路由! +// cmdArgs.add(protocol + ".never-default"); +// cmdArgs.add("yes"); +// } +// executeCommand(cmdArgs.toArray(new String[0])); +// +// } else { +// // 修改连接 +// List cmdArgs = new ArrayList<>(Arrays.asList( +// "nmcli", "con", "mod", connectionName, +// protocol + ".method", "manual", +// protocol + ".addresses", ipWithPrefix +// )); +// +// if (hasGateway) { +// cmdArgs.add(protocol + ".gateway"); +// cmdArgs.add(gateway.trim()); +// // 修改时,同样加上这个限制 +// cmdArgs.add(protocol + ".never-default"); +// cmdArgs.add("yes"); +// } else { +// // 如果没有网关,清空旧网关 +// cmdArgs.add(protocol + ".gateway"); +// cmdArgs.add(""); +// } +// +// executeCommand(cmdArgs.toArray(new String[0])); +// } +// +// // 激活连接 +// executeCommand("nmcli", "con", "up", connectionName); +//} diff --git a/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java b/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java index ab35815..5321c44 100644 --- a/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/device/service/impl/TimeConfigServiceImpl.java @@ -1,18 +1,34 @@ package com.cisd.tms.modules.device.service.impl; +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; import com.cisd.tms.modules.device.dto.TimeConfigRequest; import com.cisd.tms.modules.device.service.TimeConfigService; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor +@Slf4j public class TimeConfigServiceImpl implements TimeConfigService { + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm").withResolverStyle(ResolverStyle.STRICT); + @Override public void processTimeConfig(TimeConfigRequest request) { String mode = request.getMode(); @@ -25,15 +41,35 @@ public class TimeConfigServiceImpl implements TimeConfigService { } String datetime = request.getDatetime(); - //todo 建议用LocalDateTime进行校验格式 - if (datetime == null || !datetime.matches("^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$")) { + if (datetime == null) { throw new IllegalArgumentException("Invalid or empty datetime format. Expected: YYYY-MM-DD HH:MM"); } + try { + LocalDateTime.parse(datetime, FORMATTER); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Invalid or empty datetime format. Expected: YYYY-MM-DD HH:MM", e); + } - executeCommand("timedatectl set-timezone " + request.getTimezone().trim()); - executeCommand("systemctl stop chronyd"); - //todo 失败后重启chronyd - executeCommand("date -s '" + request.getDatetime() + "'"); + try{ + executeCommand("timedatectl", "set-timezone", request.getTimezone().trim()); + } catch (Exception e){ + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "设置时区失败"); + } + + try { + executeCommand("systemctl", "stop", "chronyd"); + } catch (Exception e){ + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "停止chronyd服务失败"); + } + + + try { + executeCommand("date", "-s", request.getDatetime()); + } catch (Exception e) { + // 失败后重启 chronyd + executeCommand("systemctl", "start", "chronyd"); + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "设置时间失败,已回退并重启 chronyd 服务"); + } } else if ("NTP".equalsIgnoreCase(mode)) { @@ -41,72 +77,112 @@ public class TimeConfigServiceImpl implements TimeConfigService { if (servers == null || servers.isEmpty()) { throw new IllegalArgumentException("At least one NTP server IP must be provided in NTP mode"); } - //todo 参数校验SyncInterval:限制正数、范围或 2 的幂 - int interval = request.getSyncInterval() != null ? request.getSyncInterval() : 600; - int pollExp = (int) Math.round(Math.log(interval) / Math.log(2)); + + int interval = request.getSyncInterval() != null ? request.getSyncInterval() : 512; + if (interval <= 0) { + throw new IllegalArgumentException("SyncInterval must be a positive integer."); + } + if ((interval & (interval - 1)) != 0) { + throw new IllegalArgumentException("SyncInterval must be a power of 2."); + } + int MIN_INTERVAL = 8; + int MAX_INTERVAL = 131072; + if (interval < MIN_INTERVAL || interval > MAX_INTERVAL) { + throw new IllegalArgumentException(String.format("SyncInterval must be between %d and %d.", MIN_INTERVAL, MAX_INTERVAL)); + } + + int pollExp = Integer.numberOfTrailingZeros(interval); + StringBuilder serverConfLines = new StringBuilder(); for (String server : servers) { if (!server.matches("^[A-Za-z0-9.-]+$")) { throw new IllegalArgumentException("Invalid NTP server address format: " + server); } - String pingResult = executeCommand("ping -c 1 -W 1 " + server); + String pingResult = executeCommand("ping", "-c", "1", "-W", "1", server); if (!pingResult.contains("1 received")) { throw new RuntimeException("NTP server is unreachable via ping: " + server); } serverConfLines.append("server ").append(server) .append(" iburst minpoll ").append(pollExp) - .append(" maxpoll ").append(pollExp).append("\\n"); + .append(" maxpoll ").append(pollExp).append("\n"); } String confContent = serverConfLines + - "driftfile /var/lib/chrony/drift\\n" + - "makestep 1.0 3\\n" + - "rtcsync\\n"; - //todo 这里直接重写配置文件,如果失败则原配置丢失。建议备份,失败后回滚。 - executeCommand("echo -e '" + confContent + "' > /etc/chrony.conf"); - executeCommand("systemctl restart chronyd"); - executeCommand("chronyc -a makestep"); - String status = executeCommand("chronyc tracking"); - if (!status.contains("Reference ID")) { - throw new RuntimeException("NTP sync status check failed"); - } + "driftfile /var/lib/chrony/drift\n" + + "makestep 1.0 3\n" + + "rtcsync\n"; + Path confPath = Paths.get("/etc/chrony.conf"); + Path backupPath = Paths.get("/etc/chrony.conf.bak"); + try { + if (Files.exists(confPath)) { + Files.copy(confPath, backupPath, StandardCopyOption.REPLACE_EXISTING); + } + + Files.writeString(confPath, confContent); + + executeCommand("systemctl", "restart", "chronyd"); + executeCommand("chronyc", "-a", "makestep"); + + String status = executeCommand("chronyc", "tracking"); + if (!status.contains("Reference ID")) { + throw new RuntimeException("NTP sync status check failed"); + } + + } catch (Exception e) { + try { + if (Files.exists(backupPath)) { + Files.copy(backupPath, confPath, StandardCopyOption.REPLACE_EXISTING); + executeCommand("systemctl", "restart", "chronyd"); + } + } catch (Exception rollbackEx) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Failed to apply new Chrony configuration. CRITICAL: Rollback also failed!"); + } + + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Failed to apply new Chrony configuration. Rolled back to original state."); + } } else { throw new IllegalArgumentException("Unknown mode: " + mode); } } - private String executeCommand(String command) { - ProcessBuilder processBuilder = new ProcessBuilder("bash", "-c", command); + private String executeCommand(String... commandArgs) { + ProcessBuilder processBuilder = new ProcessBuilder(commandArgs); processBuilder.redirectErrorStream(true); Process process = null; try { process = processBuilder.start(); - StringBuilder output = new StringBuilder(); + StringBuilder outputBuilder = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { - output.append(line).append("\n"); + outputBuilder.append(line).append("\n"); } } int exitCode = process.waitFor(); + String outputStr = outputBuilder.toString().trim(); + if (exitCode != 0) { - throw new RuntimeException("command execution failed: [" + command + "] "); + log.error("Command execution failed. Exit code: {}, Command: {}, Output: {}", + exitCode, Arrays.toString(commandArgs), outputStr); + throw new RuntimeException("系统底层操作执行失败"); } - return output.toString(); + return outputStr; } catch (IOException e) { - throw new RuntimeException("I/O error executing command: [" + command + "]", e); + log.error("I/O error executing command: {}", Arrays.toString(commandArgs), e); + throw new RuntimeException("服务器内部操作异常", e); } catch (InterruptedException e) { if (process != null) { process.destroy(); } Thread.currentThread().interrupt(); - throw new RuntimeException("process was interrupted while executing command: [" + command + "]", e); + log.error("Process was interrupted while executing command: {}", Arrays.toString(commandArgs), e); + throw new RuntimeException("服务器内部处理中断", e); } } } diff --git a/src/main/resources/db/migration/V1__tms_schema_full.sql b/src/main/resources/db/migration/V1__tms_schema_full.sql index 8605c2a..97ff413 100644 --- a/src/main/resources/db/migration/V1__tms_schema_full.sql +++ b/src/main/resources/db/migration/V1__tms_schema_full.sql @@ -354,3 +354,15 @@ VALUES CURRENT_TIMESTAMP(3), CURRENT_TIMESTAMP(3) ); + + +-- ----------------------------------------------------------------------------- +-- IP白名单表 +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS tms_access_whitelist ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + ip VARCHAR(32) NOT NULL, + mask VARCHAR(10) NOT NULL, + create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) + ); \ No newline at end of file