From 2236b4e663f1d13781816ae95071a65c24ad02fd Mon Sep 17 00:00:00 2001 From: xydkj Date: Tue, 31 Mar 2026 17:20:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=99=BD=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E3=80=81=E7=BD=91=E7=BB=9C=E9=85=8D=E7=BD=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cisd/tms/common/config/FilterConfig.java | 30 + .../controller/IpWhitelistController.java | 50 + .../controller/NetworkConfigController.java | 155 +++ .../dto/network/AddStaticRouteRequest.java | 16 + .../dto/network/BondAddSlavesRequest.java | 16 + .../device/dto/network/BondCreateRequest.java | 32 + .../dto/network/BondModifyModeRequest.java | 15 + .../dto/network/BondRemoveSlaveRequest.java | 17 + .../network/DeleteDefaultRouteRequest.java | 11 + .../dto/network/DeleteStaticRouteRequest.java | 18 + .../dto/network/IpWhitelistRequest.java | 21 + .../dto/network/IpWhitelistResponse.java | 15 + .../device/dto/network/Ipv4ConfigRequest.java | 17 + .../device/dto/network/Ipv4InfoResponse.java | 18 + .../device/dto/network/Ipv6AddressItem.java | 13 + .../device/dto/network/Ipv6ConfigRequest.java | 17 + .../device/dto/network/Ipv6InfoResponse.java | 17 + .../dto/network/NetworkInfoResponse.java | 30 + .../device/dto/network/RouteInfoResponse.java | 18 + .../dto/network/SetDefaultRouteRequest.java | 15 + .../device/entity/IpWhitelistEntity.java | 39 + .../device/filter/IpWhitelistFilter.java | 123 ++ .../device/mapper/IpWhitelistMapper.java | 11 + .../repository/IpWhitelistRepository.java | 32 + .../impl/IpWhiltelistRepositoryImpl.java | 75 ++ .../device/service/IpWhitelistService.java | 131 +++ .../device/service/NetworkConfigService.java | 1003 +++++++++++++++++ 27 files changed, 1955 insertions(+) create mode 100644 src/main/java/com/cisd/tms/common/config/FilterConfig.java create mode 100644 src/main/java/com/cisd/tms/modules/device/controller/IpWhitelistController.java create mode 100644 src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/AddStaticRouteRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/BondAddSlavesRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/BondCreateRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/BondModifyModeRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/BondRemoveSlaveRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/DeleteDefaultRouteRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/DeleteStaticRouteRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/IpWhitelistRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/IpWhitelistResponse.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4ConfigRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4InfoResponse.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6AddressItem.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6ConfigRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6InfoResponse.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/NetworkInfoResponse.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/RouteInfoResponse.java create mode 100644 src/main/java/com/cisd/tms/modules/device/dto/network/SetDefaultRouteRequest.java create mode 100644 src/main/java/com/cisd/tms/modules/device/entity/IpWhitelistEntity.java create mode 100644 src/main/java/com/cisd/tms/modules/device/filter/IpWhitelistFilter.java create mode 100644 src/main/java/com/cisd/tms/modules/device/mapper/IpWhitelistMapper.java create mode 100644 src/main/java/com/cisd/tms/modules/device/repository/IpWhitelistRepository.java create mode 100644 src/main/java/com/cisd/tms/modules/device/repository/impl/IpWhiltelistRepositoryImpl.java create mode 100644 src/main/java/com/cisd/tms/modules/device/service/IpWhitelistService.java create mode 100644 src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java diff --git a/src/main/java/com/cisd/tms/common/config/FilterConfig.java b/src/main/java/com/cisd/tms/common/config/FilterConfig.java new file mode 100644 index 0000000..5577e7e --- /dev/null +++ b/src/main/java/com/cisd/tms/common/config/FilterConfig.java @@ -0,0 +1,30 @@ +package com.cisd.tms.common.config; + +import com.cisd.tms.modules.device.filter.IpWhitelistFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FilterConfig { + + @Bean + public IpWhitelistFilter ipWhitelistFilter() { + return new IpWhitelistFilter(); + } + + /** + * 注册 Filter 并配置拦截规则 + */ + @Bean + public FilterRegistrationBean ipWhitelistFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + + registration.setFilter(ipWhitelistFilter()); + + //设置拦截路径 + registration.addUrlPatterns("/api/v1/*"); + + return registration; + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..98a913e --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/controller/IpWhitelistController.java @@ -0,0 +1,50 @@ +package com.cisd.tms.modules.device.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.modules.device.dto.network.IpWhitelistResponse; +import com.cisd.tms.modules.device.service.IpWhitelistService; +import com.cisd.tms.modules.device.dto.network.IpWhitelistRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + + +@RestController +@RequestMapping("/api/v1/device/whitelist") +@Tag(name = "IP白名单管理", description = "提供IP白名单的增、删、改、查分页接口") +public class IpWhitelistController { + + @Autowired + private IpWhitelistService ipWhitelistService; + + @PostMapping("/add") + @Operation(summary = "添加IP白名单", description = "新增一条IP白名单记录") + public ApiResponse addWhitelist(@RequestBody IpWhitelistRequest req) { + ipWhitelistService.addWhitelist(req); + return ApiResponse.success("添加成功"); + } + + + @PostMapping("/update") + @Operation(summary = "更新IP白名单", description = "根据ID更新IP白名单信息") + public ApiResponse updateWhitelist(@RequestBody IpWhitelistRequest req) { + ipWhitelistService.updateWhitelist(req); + return ApiResponse.success("更新成功"); + } + + @PostMapping("/delete/{id}") + @Operation(summary = "删除IP白名单", description = "根据ID删除指定IP白名单") + public ApiResponse deleteWhitelist(@PathVariable Long id) { + ipWhitelistService.deleteWhitelist(id); + return ApiResponse.success("删除成功"); + } + + @GetMapping("/page") + @Operation(summary = "分页查询IP白名单", description = "支持分页查询IP白名单列表") + public ApiResponse> getWhitelistPage(@RequestBody IpWhitelistRequest req) { + IPage page = ipWhitelistService.getWhitelistPage(req); + return ApiResponse.success(page); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..805e0ea --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java @@ -0,0 +1,155 @@ +package com.cisd.tms.modules.device.controller; + + +import com.cisd.tms.common.api.ApiResponse; +import com.cisd.tms.modules.device.dto.network.*; +import com.cisd.tms.modules.device.service.NetworkConfigService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "网络配置管理", description = "提供设备网络信息查询、IPv4/IPv6配置、Bond配置及路由管理等接口") +@RestController +@RequestMapping("/api/v1/device/network-config") +public class NetworkConfigController { + + private final NetworkConfigService networkConfigService; + + public NetworkConfigController(NetworkConfigService networkConfigService){ + this.networkConfigService = networkConfigService; + } + + + @Operation(summary = "获取网络信息", description = "获取当前设备的所有网络连接信息") + @GetMapping("/network-info") + public ApiResponse> getNetworkInfo(){ + return ApiResponse.success(networkConfigService.getNetworkInfo()); + } + + @Operation(summary = "获取IPv4配置信息", description = "根据设备名称获取指定网络接口的IPv4配置详情") + @GetMapping("/ipv4-info/{deviceName}") + public ApiResponse getIpv4Info(@PathVariable String deviceName){ + return ApiResponse.success(networkConfigService.getIpv4Info(deviceName)); + } + + @Operation(summary = "设置IPv4配置", description = "为指定网络接口配置IPv4地址、掩码、网关等信息") + @PostMapping("/ipv4-config/set") + public ApiResponse setIpv4Config(@RequestBody Ipv4ConfigRequest req) { + String resultMessage = networkConfigService.setIpv4Config(req); + return ApiResponse.success(resultMessage); + } + + @Operation(summary = "获取IPv6配置信息", description = "根据设备名称获取指定网络接口的IPv6配置详情") + @GetMapping("/ipv6-info/{deviceName}") + public ApiResponse getIpv6Info(@PathVariable String deviceName){ + return ApiResponse.success(networkConfigService.getIpv6Info(deviceName)); + } + + + @Operation(summary = "设置IPv6配置", description = "为指定网络接口配置IPv6地址、前缀长度、网关等信息") + @PostMapping("/ipv6-config/set") + public ApiResponse setIpv6Config(@RequestBody Ipv6ConfigRequest req) { + String resultMessage = networkConfigService.setIpv6Config(req); + return ApiResponse.success(resultMessage); + } + + @Operation(summary = "获取所有Bond名称", description = "返回当前系统中所有已创建的Bond接口名称") + @GetMapping("/all-bond-name") + public ApiResponse> getAllBondName(){ + return ApiResponse.success(networkConfigService.getAllBondName()); + } + + @Operation(summary = "创建Bond", description = "创建一个新的Bond聚合接口,需指定名称、模式和从属接口") + @PostMapping("/bond/create") + public ApiResponse createBond(@RequestBody BondCreateRequest req) { + String resultMessage = networkConfigService.createBond(req); + return ApiResponse.success(resultMessage); + } + + + @Operation(summary = "删除Bond", description = "根据Bond名称删除指定的聚合接口") + @PostMapping("/bond/delete/{bondName}") + public ApiResponse deleteBond(@PathVariable("bondName") String bondName){ + String resultMessage = networkConfigService.deleteBond(bondName); + return ApiResponse.success(resultMessage); + } + + @Operation(summary = "添加从属接口到Bond", description = "向指定Bond中添加一个或多个从属网络接口") + @PostMapping("/bond-slave/add") + public ApiResponse addSlavesTOBond(@RequestBody BondAddSlavesRequest req) { + String resultMessage = networkConfigService.addSlavesTOBond(req); + return ApiResponse.success(resultMessage); + } + + @Operation(summary = "从Bond中移除从属接口", description = "从指定Bond中移除一个或多个从属网络接口") + @PostMapping("/bond-slave/remove") + public ApiResponse removeSlaveFromBond(@RequestBody BondRemoveSlaveRequest req) { + String resultMessage = networkConfigService.removeSlaveFromBond(req); + return ApiResponse.success(resultMessage); + } + + @Operation(summary = "获取Bond的从属接口列表", description = "根据Bond名称返回其所有从属接口名称") + @GetMapping("/bond-slave/{bondName}") + public ApiResponse> getBondSlaves(@PathVariable String bondName){ + return ApiResponse.success(networkConfigService.getBondSlaves(bondName)); + } + + + @Operation(summary = "获取Bond模式", description = "根据Bond名称查询当前的绑定模式") + @GetMapping("/bond/mode/{bondName}") + public ApiResponse getBondMode(@PathVariable("bondName") String bondName) { + + String mode = networkConfigService.getBondMode(bondName); + + if (mode == null) { + return ApiResponse.fail(400, "未找到该连接的 Bond 模式", null); + } + + return ApiResponse.success(mode); + } + + @Operation(summary = "设置Bond模式", description = "修改指定Bond的绑定模式") + @PostMapping("/bond/mode/set") + public ApiResponse setBondMode(@RequestBody BondModifyModeRequest req) { + + String resultMessage = networkConfigService.setBondMode(req); + return ApiResponse.success(resultMessage); + } + + @Operation(summary = "获取路由表", description = "返回当前系统的IPv4/IPv6路由表信息") + @GetMapping("/routes/routingTable") + public ApiResponse> getRoutingTable() { + List routes = networkConfigService.getRoutingTable(); + return ApiResponse.success(routes); + } + + @Operation(summary = "设置默认路由", description = "配置或修改系统的默认网关路由") + @PostMapping("/routes/default/set") + public ApiResponse setDefaultRoute(@RequestBody SetDefaultRouteRequest req) { + String resultMessage = networkConfigService.setDefaultRoute(req); + return ApiResponse.success(resultMessage); + } + + @Operation(summary = "添加静态路由", description = "新增一条静态路由规则") + @PostMapping("/routes/static/add") + public ApiResponse addStaticRoute(@RequestBody AddStaticRouteRequest req) { + String resultMessage = networkConfigService.addStaticRoute(req); + return ApiResponse.success(resultMessage); + } + + @Operation(summary = "删除默认路由", description = "删除指定的默认路由") + @PostMapping("/routes/default/delete") + public ApiResponse deleteDefaultRoute(@RequestBody DeleteDefaultRouteRequest req) { + String resultMessage = networkConfigService.deleteDefaultRoute(req); + return ApiResponse.success(resultMessage); + } + + @Operation(summary = "删除静态路由", description = "删除指定的静态路由") + @PostMapping("/routes/static/delete") + public ApiResponse deleteStaticRoute(@RequestBody DeleteStaticRouteRequest req) { + String resultMessage = networkConfigService.deleteStaticRoute(req); + return ApiResponse.success(resultMessage); + } +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/AddStaticRouteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/AddStaticRouteRequest.java new file mode 100644 index 0000000..7e298eb --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/AddStaticRouteRequest.java @@ -0,0 +1,16 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "添加静态路由请求参数") +public class AddStaticRouteRequest { + + @Schema(description = "网络设备名称(如 eth0、bond0)", example = "eth0") + private String deviceName; + @Schema(description = "目标网络CIDR", example = "192.168.1.0/24") + private String targetCidr; + @Schema(description = "下一跳地址(网关IP)", example = "192.168.1.1") + private String nextHop; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/BondAddSlavesRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/BondAddSlavesRequest.java new file mode 100644 index 0000000..39b44ff --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/BondAddSlavesRequest.java @@ -0,0 +1,16 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "向Bond添加从属接口请求参数") +public class BondAddSlavesRequest { + + @Schema(description = "Bond接口名称", example = "bond0") + private String bondName; + @Schema(description = "要添加到Bond的从属接口列表", example = "[\"eth0\", \"eth1\"]") + private List slaveList; +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/BondCreateRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/BondCreateRequest.java new file mode 100644 index 0000000..94ae083 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/BondCreateRequest.java @@ -0,0 +1,32 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "创建Bond请求参数") +public class BondCreateRequest { + /** + * Bond 名称,例如 "bond0" + */ + @Schema(description = "Bond 名称", example = "bond0") + private String bondName; + + /** + * 模式 (例如: 0, 1, 2, 4) + * 0: balance-rr (轮询) + * 1: active-backup (主备) + * 2: balance-xor + * 4: 802.3ad (动态链路聚合) + */ + @Schema(description = "Bond 模式(0: balance-rr 轮询, 1: active-backup 主备, 2: balance-xor, 4: 802.3ad 动态链路聚合" + ,example = "1") + private Integer mode; + + /** + * 可选的 IPv4 配置 + * 如果前端传了该对象,创建 Bond 后会自动配置 IP + */ + @Schema(description = "可选的 IPv4 配置,如果提供该对象,创建 Bond 后会自动配置 IP") + private Ipv4ConfigRequest ipv4Config; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/BondModifyModeRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/BondModifyModeRequest.java new file mode 100644 index 0000000..1d888e5 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/BondModifyModeRequest.java @@ -0,0 +1,15 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "修改bond模式请求参数") +public class BondModifyModeRequest { + + @Schema(description = "Bond 名称", example = "bond0") + private String bondName; + @Schema(description = "Bond 模式(0: balance-rr 轮询, 1: active-backup 主备, 2: balance-xor, 4: 802.3ad 动态链路聚合" + ,example = "1") + private Integer mode; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/BondRemoveSlaveRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/BondRemoveSlaveRequest.java new file mode 100644 index 0000000..0eec0a6 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/BondRemoveSlaveRequest.java @@ -0,0 +1,17 @@ +package com.cisd.tms.modules.device.dto.network; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "从Bond移除从属接口请求参数") +public class BondRemoveSlaveRequest { + + @Schema(description = "Bond接口名称", example = "bond0") + private String bondName; + @Schema(description = "要从Bond移除的从属接口列表", example = "[\"eth0\", \"eth1\"]") + private List slaveList; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteDefaultRouteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteDefaultRouteRequest.java new file mode 100644 index 0000000..8b3ca59 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteDefaultRouteRequest.java @@ -0,0 +1,11 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "删除默认路由请求参数") +public class DeleteDefaultRouteRequest { + @Schema(description = "网络设备名称(如 eth0、bond0)", example = "eth0") + private String deviceName; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteStaticRouteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteStaticRouteRequest.java new file mode 100644 index 0000000..ddcf1d7 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/DeleteStaticRouteRequest.java @@ -0,0 +1,18 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "删除静态路由请求参数") +public class DeleteStaticRouteRequest { + + @Schema(description = "网络设备名称", example = "eth0") + private String deviceName; + + @Schema(description = "目标网络CIDR", example = "192.168.1.0/24") + private String targetCidr; + + @Schema(description = "下一跳地址(网关IP)", example = "192.168.1.1") + private String nextHop; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/IpWhitelistRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/IpWhitelistRequest.java new file mode 100644 index 0000000..13130aa --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/IpWhitelistRequest.java @@ -0,0 +1,21 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "IP白名单请求参数") +public class IpWhitelistRequest { + @Schema(description = "白名单记录ID(更新时必填)", example = "1") + private String id; + @Schema(description = "IP地址", example = "192.168.1.1") + private String ip; + @Schema(description = "掩码长度", example = "24") + private String mask; + + // 分页参数 + @Schema(description = "当前页码", defaultValue = "1", example = "1") + private Integer pageNum = 1; + @Schema(description = "每页条数", defaultValue = "10", example = "10") + private Integer pageSize = 10; +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/IpWhitelistResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/IpWhitelistResponse.java new file mode 100644 index 0000000..42db946 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/IpWhitelistResponse.java @@ -0,0 +1,15 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "IP白名单响应参数") +public class IpWhitelistResponse { + @Schema(description = "白名单记录ID(更新时必填)", example = "1") + private String id; + @Schema(description = "IP地址", example = "192.168.1.1") + private String ip; + @Schema(description = "掩码长度", example = "24") + private String mask; +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4ConfigRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4ConfigRequest.java new file mode 100644 index 0000000..921b965 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4ConfigRequest.java @@ -0,0 +1,17 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "IPv4配置请求参数") +public class Ipv4ConfigRequest { + @Schema(description = "网络设备名称", example = "eth0") + private String deviceName; + @Schema(description = "IPv4地址", example = "192.168.1.100") + private String ipv4; + @Schema(description = "子网掩码长度", example = "24") + private String maskLength; + @Schema(description = "默认网关地址", example = "192.168.1.1") + private String gateway; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4InfoResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4InfoResponse.java new file mode 100644 index 0000000..adb07f3 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv4InfoResponse.java @@ -0,0 +1,18 @@ +package com.cisd.tms.modules.device.dto.network; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "IPv4配置信息响应") +public class Ipv4InfoResponse { + @Schema(description = "IPv4地址", example = "192.168.1.100") + private String ipv4; + @Schema(description = "子网掩码长度", example = "24") + private String maskLength; + @Schema(description = "默认网关地址", example = "192.168.1.1") + private String gateway; + @Schema(description = "IP获取方式(如 static、dhcp)", example = "static") + private String method; +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6AddressItem.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6AddressItem.java new file mode 100644 index 0000000..e32fa7f --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6AddressItem.java @@ -0,0 +1,13 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "单个IPv6地址及掩码") +public class Ipv6AddressItem { + @Schema(description = "IPv6地址", example = "2001:db8::2") + private String ipv6; + @Schema(description = "子网掩码前缀长度", example = "64") + private String maskLength; +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6ConfigRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6ConfigRequest.java new file mode 100644 index 0000000..df9f0aa --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6ConfigRequest.java @@ -0,0 +1,17 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "IPv6配置请求参数") +public class Ipv6ConfigRequest { + @Schema(description = "网络设备名称(如 eth0、bond0)", example = "eth0") + private String deviceName; + @Schema(description = "IPv6地址", example = "2001:db8::1") + private String ipv6; + @Schema(description = "默认网关地址", example = "2001:db8::1") + private String gateway; + @Schema(description = "子网前缀长度(如 64)", example = "64") + private String maskLength; +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6InfoResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6InfoResponse.java new file mode 100644 index 0000000..c88cc32 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/Ipv6InfoResponse.java @@ -0,0 +1,17 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "IPv6配置信息响应") +public class Ipv6InfoResponse { + @Schema(description = "IPv6地址及掩码列表") + private List ipv6List; + @Schema(description = "默认网关地址", example = "2001:db8::1") + private String gateway; + @Schema(description = "IP获取方式(如 static、dhcp、auto)", example = "static") + private String method; +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkInfoResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkInfoResponse.java new file mode 100644 index 0000000..80c9c05 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/NetworkInfoResponse.java @@ -0,0 +1,30 @@ +package com.cisd.tms.modules.device.dto.network; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +@Schema(description = "网络接口信息响应参数") +public class NetworkInfoResponse { + + @Schema(description = "设备名称", example = "eth0") + private String deviceName; + @Schema(description = "接口类型", example = "ethernet") + private String type; + @Schema(description = "连接状态", example = "connected") + private String state; + @Schema(description = "NetworkManager中的连接名称(通常与deviceName相同)", example = "eth0") + private String connectionName; + @Schema(description = "IPv4 地址", example = "192.168.1.100") + private String ipv4; + @Schema(description = "IPv6 地址", example = "2001:db8::1") + private List ipv6; + @Schema(description = "MAC 地址", example = "00:11:22:33:44:55") + private String mac; + @Schema(description = "默认网关地址", example = "192.168.1.1") + private String gateway; + +} diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/RouteInfoResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteInfoResponse.java new file mode 100644 index 0000000..28ca406 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/RouteInfoResponse.java @@ -0,0 +1,18 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "路由信息响应") +public class RouteInfoResponse { + + @Schema(description = "目标网络或主机(CIDR格式或主机地址)", example = "0.0.0.0/0") + private String destination; + + @Schema(description = "下一跳地址(网关)", example = "192.168.1.1") + private String nextHop; + + @Schema(description = "出口网络接口名称", example = "eth0") + private String interfaceName; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/device/dto/network/SetDefaultRouteRequest.java b/src/main/java/com/cisd/tms/modules/device/dto/network/SetDefaultRouteRequest.java new file mode 100644 index 0000000..634027b --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/dto/network/SetDefaultRouteRequest.java @@ -0,0 +1,15 @@ +package com.cisd.tms.modules.device.dto.network; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "设置默认路由请求参数") +public class SetDefaultRouteRequest { + + @Schema(description = "网络设备名称(如 eth0、bond0)", example = "eth0") + private String deviceName; + + @Schema(description = "默认网关IP地址", example = "192.168.1.1") + private String gatewayIp; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/device/entity/IpWhitelistEntity.java b/src/main/java/com/cisd/tms/modules/device/entity/IpWhitelistEntity.java new file mode 100644 index 0000000..d21f0e9 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/entity/IpWhitelistEntity.java @@ -0,0 +1,39 @@ +package com.cisd.tms.modules.device.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@TableName("tms_access_whitelist") +public class IpWhitelistEntity { + + /** + * 主键 + */ + private Long id; + + /** + * IP地址 + */ + private String ip; + + /** + * 掩码长度 + */ + private String mask; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} \ No newline at end of file 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 new file mode 100644 index 0000000..45ac136 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/filter/IpWhitelistFilter.java @@ -0,0 +1,123 @@ +package com.cisd.tms.modules.device.filter; + +import com.cisd.tms.modules.device.service.IpWhitelistService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + + +@Slf4j +public class IpWhitelistFilter implements Filter { + + @Autowired + private IpWhitelistService ipWhitelistService; + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + String clientIp = getClientIp(request); + List whitelist = ipWhitelistService.getAllIp(); + + // 空名单兜底 + if (whitelist == null || whitelist.isEmpty()) { + filterChain.doFilter(request, response); + return; + } + + + int clientIpInt; + try { + clientIpInt = ipToInt(clientIp); + } catch (IllegalArgumentException e) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setContentType("application/json;charset=utf-8"); + response.getWriter().write("{\"code\": 403, \"msg\": \"非法的客户端 IP 格式,拒绝访问\"}"); + return; + } + + // 白名单匹配 + boolean isAllowed = false; + for (String cidr : whitelist) { + try{ + String[] parts = cidr.split("/"); + String networkIp = parts[0]; + int maskLength = parts.length > 1 ? Integer.parseInt(parts[1]) : 32; + + if (maskLength < 0 || maskLength > 32) { + continue; + } + + int networkIpInt = ipToInt(networkIp); + + int mask = (maskLength == 0) ? 0 : (0xFFFFFFFF << (32 - maskLength)); + isAllowed = (networkIpInt & mask) == (clientIpInt & mask); + if (isAllowed){ + break; + } + } catch (IllegalArgumentException e){ + log.error("数据库白名单{}该条规则解析异常,", cidr, e); + } + + } + + // 默认拒绝 + if (!isAllowed) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setContentType("application/json;charset=utf-8"); + response.getWriter().write("{\"code\": 403, \"msg\": \"非法访问:您的IP不在允许的白名单内\"}"); + return; + } + + filterChain.doFilter(request, response); + } + + @Override + public void destroy() { + } + + private String getClientIp(HttpServletRequest request) { + String[] headers = { + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "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(); + } + } + return request.getRemoteAddr(); + } + + + private int ipToInt(String ipAddress) { + String[] octets = ipAddress.split("\\."); + if (octets.length != 4) { + throw new IllegalArgumentException("非法的 IPv4 地址: " + ipAddress); + } + + int result = 0; + for (int i = 0; i < 4; i++) { + int octet = Integer.parseInt(octets[i]); + if (octet < 0 || octet > 255) { + throw new IllegalArgumentException("非法的 IPv4 地址段: " + octet); + } + result |= (octet << (24 - (8 * i))); + } + return result; + } + +} diff --git a/src/main/java/com/cisd/tms/modules/device/mapper/IpWhitelistMapper.java b/src/main/java/com/cisd/tms/modules/device/mapper/IpWhitelistMapper.java new file mode 100644 index 0000000..2d341b1 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/mapper/IpWhitelistMapper.java @@ -0,0 +1,11 @@ +package com.cisd.tms.modules.device.mapper; + +import com.cisd.tms.infrastructure.persistence.mapper.BaseMapperX; +import com.cisd.tms.modules.device.entity.IpWhitelistEntity; +import org.apache.ibatis.annotations.Mapper; + + +@Mapper +public interface IpWhitelistMapper extends BaseMapperX { + +} 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 new file mode 100644 index 0000000..21b85ad --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/repository/IpWhitelistRepository.java @@ -0,0 +1,32 @@ +package com.cisd.tms.modules.device.repository; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.modules.device.dto.network.IpWhitelistRequest; +import com.cisd.tms.modules.device.entity.IpWhitelistEntity; + +import java.util.List; +import java.util.Optional; + +public interface IpWhitelistRepository { + + void addWhitelist(IpWhitelistEntity entity); + + Optional findById(Long id); + + void updateWhitelist(IpWhitelistEntity entity); + + void deleteById(Long id); + + boolean existsByIpAndMask(String ip, String mask, Long excludeId); + + /** + * 分页查询白名单 + */ + Page selectPage(Page page, IpWhitelistRequest req); + + /** + * 查询所有白名单 + */ + List selectAll(); + +} 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 new file mode 100644 index 0000000..cdd7b01 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/repository/impl/IpWhiltelistRepositoryImpl.java @@ -0,0 +1,75 @@ +package com.cisd.tms.modules.device.repository.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.cisd.tms.modules.device.dto.network.IpWhitelistRequest; +import com.cisd.tms.modules.device.entity.IpWhitelistEntity; +import com.cisd.tms.modules.device.mapper.IpWhitelistMapper; +import com.cisd.tms.modules.device.repository.IpWhitelistRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + + +@Repository +public class IpWhiltelistRepositoryImpl implements IpWhitelistRepository { + + @Autowired + private IpWhitelistMapper ipWhitelistMapper; + + @Override + public void addWhitelist(IpWhitelistEntity entity){ + ipWhitelistMapper.insert(entity); + } + + @Override + public Optional findById(Long id){ + return Optional.ofNullable(ipWhitelistMapper.selectById(id)); + } + + @Override + public void updateWhitelist(IpWhitelistEntity entity){ + ipWhitelistMapper.updateById(entity); + } + + @Override + public void deleteById(Long id){ + ipWhitelistMapper.deleteById(id); + } + + + @Override + public boolean existsByIpAndMask(String ip, String mask, Long excludeId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(IpWhitelistEntity::getIp, ip) + .eq(IpWhitelistEntity::getMask, mask); + + if (excludeId != null) { + wrapper.ne(IpWhitelistEntity::getId, excludeId); + } + return ipWhitelistMapper.exists(wrapper); + } + + @Override + public Page selectPage(Page page, IpWhitelistRequest req) { +// LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); +// +// if (StringUtils.hasText(req.getIp())) { +// wrapper.like(IpWhitelistEntity::getIp, req.getIp()); +// } +// if (StringUtils.hasText(req.getMask())) { +// wrapper.eq(IpWhitelistEntity::getMask, req.getMask()); +// } +// wrapper.orderByDesc(IpWhitelistEntity::getCreateTime); + + return ipWhitelistMapper.selectPage(page, null); + } + + @Override + public List selectAll() { + return ipWhitelistMapper.selectList(null); + } + +} 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 new file mode 100644 index 0000000..b024b95 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/service/IpWhitelistService.java @@ -0,0 +1,131 @@ +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.exception.BizException; +import com.cisd.tms.modules.device.dto.network.IpWhitelistRequest; +import com.cisd.tms.modules.device.dto.network.IpWhitelistResponse; +import com.cisd.tms.modules.device.entity.IpWhitelistEntity; +import com.cisd.tms.modules.device.repository.IpWhitelistRepository; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + + +@Service +public class IpWhitelistService { + + private final IpWhitelistRepository ipWhitelistRepository; + + 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(); + + + entity.setIp(ip); + entity.setMask(mask); + + ipWhitelistRepository.addWhitelist(entity); + } +// + public void updateWhitelist(IpWhitelistRequest req) { + if (req.getId() == null || req.getId().trim().isEmpty()) { + throw new IllegalArgumentException("ID should not be null"); + } + + Long idLong = Long.valueOf(req.getId()); + checkIpRule(req.getIp(), req.getMask(), idLong); + + Optional entityOpt = ipWhitelistRepository.findById(idLong); + if (entityOpt.isEmpty()) { + throw new BizException(404, "ID is not exist"); + } + IpWhitelistEntity entity = entityOpt.get(); + + entity.setIp(req.getIp()); + entity.setMask(req.getMask()); + ipWhitelistRepository.updateWhitelist(entity); + } + + + public void deleteWhitelist(Long id) { + Optional entityOpt = ipWhitelistRepository.findById(id); + if (entityOpt.isEmpty()) { + throw new BizException(404, "ID is not exist"); + } + ipWhitelistRepository.deleteById(id); + } + + + public IPage getWhitelistPage(IpWhitelistRequest req) { + Page page = new Page<>(req.getPageNum(), req.getPageSize()); + ipWhitelistRepository.selectPage(page, req); + + + return page.convert(entity -> { + IpWhitelistResponse response = new IpWhitelistResponse(); + response.setId(String.valueOf(entity.getId())); + response.setIp(entity.getIp()); + response.setMask(entity.getMask()); + return response; + }); + } + + public List getAllIp() { + // CIDR 格式 + return ipWhitelistRepository.selectAll().stream() + .map(entity -> entity.getIp() + "/" + entity.getMask()) + .collect(Collectors.toList()); + } + + /** + * 校验 IP+掩码是否已存在 + */ + private void checkIpRule(String ip, String mask, Long excludeId) { + if (ip == null || ip.isEmpty()){ + throw new IllegalArgumentException("ip is empty"); + } + if (mask == null || mask.isEmpty()){ + throw new IllegalArgumentException("mask is empty"); + } + + + + + String[] octets = ip.split("\\."); + for (int i = 0; i < 4; i++) { + if (octets.length != 4) { + throw new IllegalArgumentException("非法的 IPv4 地址: " + ip); + } + + if (octets[i].length() > 1 && octets[i].startsWith("0")) { + throw new IllegalArgumentException("IP 地址段不允许包含前导零: " + octets[i]); + } + + int octet = Integer.parseInt(octets[i]); + if (octet < 0 || octet > 255) { + throw new IllegalArgumentException("非法的 IPv4 地址段: " + octet); + } + } + + 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 和掩码配置"); + } + } +} 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 new file mode 100644 index 0000000..1d32dd9 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/device/service/NetworkConfigService.java @@ -0,0 +1,1003 @@ +package com.cisd.tms.modules.device.service; + +import com.cisd.tms.common.enums.ErrorCode; +import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.device.dto.network.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +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; + +@Service +@Slf4j +public class NetworkConfigService { + + + public List getNetworkInfo(){ + + List networkInfo = new ArrayList<>(); + + List lines = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device"); + for (String line : lines){ + String[] parts = line.split(":", -1); + String device = parts[0]; + String type = parts[1]; + String state = parts[2]; + String connection = parts[3]; + + if ("lo".equals(device) || "virbr".equals(device)){ + continue; + } + + if (!"ethernet".equals(type) && !"bond".equals(type)){ + continue; + } + + NetworkInfoResponse resp = new NetworkInfoResponse(); + resp.setDeviceName(device); + resp.setType(type); + resp.setState(state); + resp.setConnectionName(connection); + List infoLines = executeCommand("nmcli", "-t", "-f", "IP4.ADDRESS,IP4.GATEWAY,IP6.ADDRESS,GENERAL.HWADDR", "device", "show", device); + Listipv6List = new ArrayList<>(); + for(String info : infoLines){ + int idx = info.indexOf(':'); //IP4.ADDRESS[1]:192.168.7.65/20 + String key = info.substring(0, idx); + String value = info.substring(idx + 1); + + if (key.startsWith("IP4.ADDRESS")){ + resp.setIpv4(value); + } else if (key.startsWith("IP6.ADDRESS")) { + ipv6List.add(value); + } else if (key.startsWith("IP4.GATEWAY")) { + resp.setGateway(value); + } else if ("GENERAL.HWADDR".equals(key)) { + resp.setMac(value); + } + } + resp.setIpv6(ipv6List); + networkInfo.add(resp); + } + return networkInfo; + } + + + public Ipv4InfoResponse getIpv4Info(String deviceName){ + String connectionName = getConnectionNameByDeviceName(deviceName); + if (connectionName == null){ + connectionName = deviceName; + } + + List lines; + try{ + lines = executeCommand("nmcli", "-g", "IPv4.ADDRESSES,IPv4.GATEWAY,IPv4.METHOD", "con", "show", connectionName); + } catch (RuntimeException e){ + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "读取网络配置信息失败,请检查网卡 [" + deviceName + "] 是否存在或已配置"); + } + + Ipv4InfoResponse resp = new Ipv4InfoResponse(); + + // 命令输出格式 + // IP/前缀 + // 网关 + // 模式 + + if (lines.size() > 0 && !lines.get(0).trim().isEmpty()) { + String ipAndMaskLength = lines.get(0).trim(); + if (ipAndMaskLength.contains("/")) { + String[] parts = ipAndMaskLength.split("/"); + resp.setIpv4(parts[0]); + resp.setMaskLength(parts[1]); + } else { + resp.setIpv4(ipAndMaskLength); + } + } + + + if (lines.size() > 1 && !lines.get(1).trim().isEmpty()) { + resp.setGateway(lines.get(1).trim()); + } + + + if (lines.size() > 2 && !lines.get(2).trim().isEmpty()) { + String method = lines.get(2).trim(); + if ("manual".equalsIgnoreCase(method)) { + resp.setMethod("static"); + } else if ("auto".equalsIgnoreCase(method)) { + resp.setMethod("dhcp"); + } else { + resp.setMethod(method); + } + } + + return resp; + } + + + public String setIpv4Config(Ipv4ConfigRequest req){ + + if (req.getDeviceName() == null || req.getDeviceName().trim().isEmpty()) { + throw new IllegalArgumentException("网卡名称不能为空"); + } + + + if (!isValidIpv4(req.getIpv4())) { + throw new IllegalArgumentException("无效的 IP 地址格式: " + req.getIpv4()); + } + + boolean hasGateway = req.getGateway() != null && !req.getGateway().trim().isEmpty(); + + if (hasGateway && !isValidIpv4(req.getGateway())) { + throw new IllegalArgumentException("无效的网关地址格式: " + req.getGateway()); + } + + String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + if (connectionName == null){ + connectionName = req.getDeviceName(); + } + + // 转化掩码格式并判断掩码格式是否正确 + int maskLength = 0; + try { + String netmask = req.getMaskLength(); + if (netmask == null || netmask.isEmpty()) { + throw new IllegalArgumentException("掩码不能为空"); + } + // 传的是数字/24 + if (!netmask.contains(".")) { + int prefix = Integer.parseInt(netmask); + if (prefix < 0 || prefix > 32) throw new IllegalArgumentException("非法的掩码前缀: " + prefix); + maskLength = prefix; + } + //传的是255.255.255.0 +// int maskInt = ipToInt(netmask); +// for (int i = 31; i >= 0; i --) { +// if ((maskInt & (1 << i)) != 0) { +// maskLength ++; +// } else { +// break; +// } +// } + } catch (Exception e) { + throw new IllegalArgumentException("无效的子网掩码格式: " + req.getMaskLength()); + } + + //判断ip和网关是否在同一个子网里面 + int ip1Int = ipToInt(req.getIpv4()); + if (hasGateway){ + int ip2Int = ipToInt(req.getGateway()); + int maskInt = (maskLength == 0) ? 0 : (0xFFFFFFFF << (32 - maskLength)); + if (!((ip1Int & maskInt) == (ip2Int & maskInt))){ + throw new IllegalArgumentException( + String.format("网关 (%s) 不在 IP (%s/%d) 所在的子网内", + req.getGateway(), req.getIpv4(), maskLength) + ); + } + } + + + String cidr = req.getIpv4() + "/" + maskLength; + + try { + executeCommand("nmcli", "con", "mod", connectionName, + "ipv4.method", "manual", + "ipv4.addresses", cidr, + "ipv4.gateway", req.getGateway()); + + executeCommand("nmcli", "con", "up", connectionName); + + return "网络连接 [" + req.getDeviceName() + "] IPv4 配置成功"; + + } catch (RuntimeException e) { + + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改网络配置失败: " + e.getMessage()); + } + } + + + public Ipv6InfoResponse getIpv6Info(String deviceName){ + + String connectionName = getConnectionNameByDeviceName(deviceName); + if (connectionName == null){ + connectionName = deviceName; + } + + List lines; + try{ + lines = executeCommand("nmcli", "-g", "IPv6.ADDRESSES,IPv6.GATEWAY,IPv6.METHOD", "con", "show", connectionName); + } catch (RuntimeException e){ + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "读取网络配置信息失败,请检查网卡 [" + deviceName + "] 是否存在或已配置"); + } + + + Ipv6InfoResponse resp = new Ipv6InfoResponse(); + + // 命令输出格式 + // IP/前缀 + // 网关 + // 模式 + List addressList = new ArrayList<>(); + if (lines.size() > 0 && !lines.get(0).trim().isEmpty()) { + // 按逗号分割出所有的 IPv6 地址 + String rawAddresses = lines.get(0).trim(); + String[] addressArray = rawAddresses.split(","); + + for (String ipAndMaskLength : addressArray) { + ipAndMaskLength = ipAndMaskLength.trim(); + if (ipAndMaskLength.isEmpty()) { + continue; + } + + Ipv6AddressItem item = new Ipv6AddressItem(); + if (ipAndMaskLength.contains("/")) { + String[] parts = ipAndMaskLength.split("/"); + item.setIpv6(parts[0]); + item.setMaskLength(parts[1]); + } else { + item.setIpv6(ipAndMaskLength); + } + // 将解析好的单个 IP 对象放入集合 + addressList.add(item); + } + } + resp.setIpv6List(addressList); + + + if (lines.size()> 1 && !lines.get(1).trim().isEmpty()) { + resp.setGateway(lines.get(1).trim()); + } + + if (lines.size() > 2 && !lines.get(2).trim().isEmpty()) { + String method = lines.get(2).trim(); + if ("manual".equalsIgnoreCase(method)) { + resp.setMethod("static"); + } else if ("auto".equalsIgnoreCase(method)) { + resp.setMethod("dhcp"); + } else { + resp.setMethod(method); + } + } + + return resp; + } + + public String setIpv6Config(Ipv6ConfigRequest req){ + if (req.getDeviceName() == null || req.getDeviceName().trim().isEmpty()) { + throw new IllegalArgumentException("网卡名称不能为空"); + } + + + if (!isValidIpv6(req.getIpv6())) { + throw new IllegalArgumentException("无效的 IPv6 地址格式: " + req.getIpv6()); + } + + //判断网关格式 + boolean hasGateway = req.getGateway() != null && !req.getGateway().trim().isEmpty(); + if (hasGateway && !isValidIpv6(req.getGateway())) { + throw new IllegalArgumentException("无效的 IPv6 网关格式: " + req.getGateway()); + } + + if (req.getMaskLength() == null || req.getMaskLength().isEmpty()) { + throw new IllegalArgumentException("掩码不能为空"); + } + + int maskLength = Integer.parseInt(req.getMaskLength()); + if (maskLength < 0 || maskLength > 128) { + throw new IllegalArgumentException("无效的 IPv6 前缀长度,必须在 0 到 128 之间"); + } + + String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + if (connectionName == null){ + connectionName = req.getDeviceName(); + } + + + String ipWithPrefix = req.getIpv6() + "/" + req.getMaskLength(); + try { + if (hasGateway) { + executeCommand("nmcli", "con", "mod", connectionName, + "ipv6.method", "manual", + "ipv6.addresses", ipWithPrefix, + "ipv6.gateway", req.getGateway()); + } else { + executeCommand("nmcli", "con", "mod", connectionName, + "ipv6.method", "manual", + "ipv6.addresses", ipWithPrefix); + } + + executeCommand("nmcli", "con", "up", connectionName); + + return "网络连接 [" + req.getDeviceName() + "] IPv6 配置成功"; + + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改 IPv6 网络配置失败: " + e.getMessage()); + } + } + + + public List getAllBondName(){ + List bondNames = new ArrayList<>(); + List lines = executeCommand("nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device"); + + for (String line : lines) { + String[] parts = line.split(":", -1); + if (parts.length >= 2 && "bond".equals(parts[1])) { + bondNames.add(parts[0]); + } + } + return bondNames; + } + + public String createBond(BondCreateRequest req){ + if (req.getBondName() == null || req.getBondName().trim().isEmpty()) { + throw new IllegalArgumentException("Bond 名称不能为空"); + } + if (req.getMode() == null) { + throw new IllegalArgumentException("Bond 模式不能为空"); + } + + + try { + List lines = executeCommand("nmcli", "-g", "NAME", "con", "show"); + for (String line : lines) { + if (req.getBondName().equals(line.trim())) { + throw new RuntimeException("网络连接名称 [" + req.getBondName() + "] 已存在,请勿重复创建"); + } + } + } catch (RuntimeException e) { + if (e.getMessage().contains("已存在")) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), e.getMessage()); + }else{ + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "创建 Bond 失败: " + e.getMessage()); + } + } + + + String bondOptions = String.format("mode=%d,miimon=100", req.getMode()); + + try { + executeCommand("nmcli", "con", "add", + "type", "bond", + "con-name", req.getBondName(), + "ifname", req.getBondName(), + "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()); + try { + setIpv4Config(req.getIpv4Config()); + } catch (Exception e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + req.getBondName() + "] 创建成功,但 IP 配置失败: " + e.getMessage()); + } + } + + return "Bond [" + req.getBondName() + "] 创建并初始化成功"; + } + + + + public String deleteBond(String bondName) { + if (bondName == null || bondName.isEmpty()) { + throw new IllegalArgumentException("Bond 名称不能为空"); + } + + boolean bondExist = false; + try { + List lines = executeCommand("nmcli", "-g", "NAME", "con", "show"); + for (String line : lines) { + if (bondName.equals(line.trim())) { + bondExist = true; + } + } + + if (bondExist) { + executeCommand("nmcli", "con", "delete", bondName); + } else { + throw new RuntimeException("Bond [" + bondName + "] 不存在"); + } + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "Bond [" + bondName + "] 删除失败 " + e.getMessage()); + } + return "Bond [" + bondName + "] 删除成功"; + } + + + public String addSlavesTOBond(BondAddSlavesRequest req) { + String bondName = req.getBondName(); + List slaves = req.getSlaveList(); + if (bondName == null || bondName.trim().isEmpty()) { + throw new IllegalArgumentException("Bond 名称不能为空"); + } + if (slaves == null || slaves.isEmpty()) { + throw new IllegalArgumentException("物理网卡列表不能为空"); + } + + + List existingConnections; + try { + existingConnections = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME", "con", "show"); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络连接列表失败"); + } + + for (String phyIf : slaves) { + if (phyIf == null || phyIf.trim().isEmpty()){ + continue; + } + + for (String line : existingConnections) { + if (line.trim().isEmpty()){ + continue; + } + + String slaveConnectionName = bondName + "-slave-" + phyIf; + + 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()); + } + } + } + } + + try { + 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())); + } + } + + try { + executeCommand("nmcli", "con", "up", bondName); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "激活 Bond [" + bondName + "] 失败: " + e.getMessage()); + } + + return String.format("Bond [%s] 成功添加 %d 个从属网卡并已激活", bondName, slaves.size()); + } + + + public String removeSlaveFromBond(BondRemoveSlaveRequest req) { + String bondName = req.getBondName(); + List slaves = req.getSlaveList(); + + if (bondName == null || bondName.trim().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"); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "获取系统网络连接列表失败"); + } + + for (String phyIf : slaves) { + String targetUuid = null; + String slaveName = bondName + "-slave-" + phyIf; + + for (String line : lines) { + 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(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); + } + } + + } + } + + if (targetUuid == null) { + log.warn("未找到物理网卡 [{}] 对应的从属连接", phyIf); + notFoundSlaves.add(phyIf); + continue; + } + + try { + executeCommand("nmcli", "con", "delete", "uuid", targetUuid); + deletedCount ++; + } catch (RuntimeException e) { + 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()); + + } + + 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); + } + } + + + + public List getBondSlaves(String bondName) { + if (bondName == null || bondName.trim().isEmpty()) { + throw new IllegalArgumentException("Bond 名称不能为空"); + } + + List slaveList = new ArrayList<>(); + List lines; + try { + lines = executeCommand("nmcli", "-t", "-f", "UUID,DEVICE,NAME", "con", "show"); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "查询 Bond 从属网卡列表失败"); + } + + for (String line : lines) { + if (line.trim().isEmpty()) { + continue; + } + + String[] parts = line.split("(?= 3) { + String uuid = parts[0].replace("\\:", ":"); + String device = parts[1].replace("\\:", ":"); + String name = parts[2].replace("\\:", ":"); + + 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())); + } + } + } + } + } + + return slaveList; + } + + + public String getBondMode(String bondName) { + if (bondName == null || bondName.trim().isEmpty()) { + throw new IllegalArgumentException("Bond 名称不能为空"); + } + + List lines; + try { + lines = executeCommand("nmcli", "-g", "bond.options", "con", "show", bondName); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "读取 Bond 配置信息失败,请检查名称 [" + bondName + "] 是否存在"); + } + + if (lines.isEmpty() || lines.get(0).trim().isEmpty()) { + return null; + } + + + String optionsLine = lines.get(0).trim(); + + String[] options = optionsLine.split(","); + for (String option : options) { + if (option.startsWith("mode=")) { + return option.substring(5); + } + } + + return null; + } + + + public String setBondMode(BondModifyModeRequest req) { + String bondName = req.getBondName(); + Integer mode = req.getMode(); + + if (bondName == null || bondName.trim().isEmpty()) { + throw new IllegalArgumentException("Bond 名称不能为空"); + } + if (mode == null) { + throw new IllegalArgumentException("新的 Bond 模式不能为空"); + } + + HashMap modeMap = new HashMap<>(); + modeMap.put(0, "balance-rr"); + modeMap.put(1, "active-backup"); + modeMap.put(2, "balance-xor"); + modeMap.put(4, "802.3ad"); + + String newMode = modeMap.get(mode); + if (newMode == null) { + throw new IllegalArgumentException("不支持当前Bond模式"); + } + + String bondOptions = String.format("mode=%s,miimon=100", newMode); + try { + executeCommand("nmcli", "con", "mod", bondName, "bond.options", bondOptions); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "修改 Bond [" + bondName + "] 模式配置失败: " + e.getMessage()); + } + + + //重启激活服务 + try { + executeCommand("nmcli", "con", "down", bondName); + executeCommand("nmcli", "con", "up", bondName); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "重新激活 Bond [" + bondName + "] 失败" + e.getMessage()); + } + + return String.format("Bond [%s] 已成功切换至 [%s] 模式并重启生效", bondName, newMode); + } + + + + public List getRoutingTable() { + List routeList = new ArrayList<>(); + List lines; + + try { + lines = executeCommand("ip", "route", "show"); + } catch (RuntimeException e) { + throw new RuntimeException("读取系统路由表失败", e); + } + + for (String line : lines) { + if (line.trim().isEmpty()) { + continue; + } + + String[] parts = line.trim().split("\\s+"); + if (parts.length == 0) { + continue; + } + + RouteInfoResponse route = new RouteInfoResponse(); + route.setDestination(parts[0]); + for (int i = 1; i < parts.length; i ++ ) { + if ("via".equals(parts[i]) && i + 1 < parts.length) { + route.setNextHop(parts[i + 1]); + } + else if ("dev".equals(parts[i]) && i + 1 < parts.length) { + route.setInterfaceName(parts[i + 1]); + } + } + + if (route.getNextHop() == null) { + route.setNextHop(""); + } + if (route.getInterfaceName() == null) { + route.setInterfaceName(""); + } + + routeList.add(route); + } + + return routeList; + } + + + public String setDefaultRoute(SetDefaultRouteRequest req) { + String deviceName = req.getDeviceName(); + String gatewayIp = req.getGatewayIp(); + + // 1. 基础校验 + if (deviceName == null || deviceName.trim().isEmpty()) { + throw new IllegalArgumentException("网卡名称不能为空"); + } + if (gatewayIp == null || gatewayIp.trim().isEmpty()) { + throw new IllegalArgumentException("网关 IP 不能为空"); + } + + String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + if (connectionName == null){ + connectionName = req.getDeviceName(); + } + + + try { + executeCommand("nmcli", "con", "mod", connectionName, + "ipv4.gateway", gatewayIp, + //多张网卡配置了网关,需调整 Metric 优先级。 + "ipv4.route-metric", "50"); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("为网卡 [%s] 设置网关失败: %s", deviceName, e.getMessage())); + } + + + try { + executeCommand("nmcli", "con", "down", connectionName); + executeCommand("nmcli", "con", "up", connectionName); + } catch (RuntimeException e) { + 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) { + String deviceName = req.getDeviceName(); + String targetCidr = req.getTargetCidr(); + String nextHop = req.getNextHop(); + + + if (deviceName == null || deviceName.trim().isEmpty()) { + throw new IllegalArgumentException("网卡名称不能为空"); + } + if (targetCidr == null || targetCidr.trim().isEmpty()) { + throw new IllegalArgumentException("目标网段(CIDR)不能为空"); + } +// if (nextHop == null || nextHop.trim().isEmpty()) { +// throw new IllegalArgumentException("下一跳 IP 不能为空"); +// } + + String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + if (connectionName == null){ + connectionName = req.getDeviceName(); + } + + String routeValue = ""; + if (nextHop == null || nextHop.trim().isEmpty()) { + routeValue = String.format("%s", targetCidr.trim()); + } + else{ + routeValue = String.format("%s %s", targetCidr.trim(), nextHop.trim()); + } + + + try { + executeCommand("nmcli", "con", "mod", connectionName, "+ipv4.routes", routeValue); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("为网卡 [%s] 添加静态路由失败: %s", deviceName, e.getMessage())); + } + + try { + executeCommand("nmcli", "con", "down", connectionName); + executeCommand("nmcli", "con", "up", connectionName); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage())); + } + + return String.format("成功向网卡 [%s] 添加静态路由", deviceName); + } + + + public String deleteDefaultRoute(DeleteDefaultRouteRequest req) { + String deviceName = req.getDeviceName(); + + if (deviceName == null || deviceName.trim().isEmpty()) { + throw new IllegalArgumentException("网卡名称不能为空"); + } + + String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + if (connectionName == null){ + connectionName = req.getDeviceName(); + } + + try { + executeCommand("nmcli", "con", "mod", connectionName, "ipv4.gateway", ""); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("清空网卡 [%s] 的网关失败: %s", deviceName, e.getMessage())); + } + + try { + executeCommand("nmcli", "con", "down", connectionName); + executeCommand("nmcli", "con", "up", connectionName); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("网卡 [%s] 激活配置失败: %s", deviceName, e.getMessage())); + } + + return String.format("已成功删除网卡 [%s] 的默认路由", deviceName); + } + + + + public String deleteStaticRoute(DeleteStaticRouteRequest req) { + String deviceName = req.getDeviceName(); + String targetCidr = req.getTargetCidr(); + String nextHop = req.getNextHop(); + + if (deviceName == null || deviceName.trim().isEmpty()) { + throw new IllegalArgumentException("网卡名称不能为空"); + } + if (targetCidr == null || targetCidr.trim().isEmpty()) { + throw new IllegalArgumentException("目标网段(CIDR)不能为空"); + } +// if (nextHop == null || nextHop.trim().isEmpty()) { +// throw new IllegalArgumentException("下一跳 IP 不能为空"); +// } + + String connectionName = getConnectionNameByDeviceName(req.getDeviceName()); + if (connectionName == null){ + connectionName = req.getDeviceName(); + } + + String routeValue = ""; + if (nextHop == null || nextHop.trim().isEmpty()) { + routeValue = String.format("%s", targetCidr.trim()); + } + else{ + routeValue = String.format("%s %s", targetCidr.trim(), nextHop.trim()); + } + + + try { + executeCommand("nmcli", "con", "mod", connectionName, "-ipv4.routes", routeValue); + } catch (RuntimeException e) { + throw new BizException(ErrorCode.BIZ_ERROR.getCode(), String.format("为网卡 [%s] 删除静态路由失败,请检查该路由是否存在: %s", deviceName, e.getMessage())); + } + + + try { + executeCommand("nmcli", "con", "down", connectionName); + executeCommand("nmcli", "con", "up", connectionName); + } catch (RuntimeException e) { + 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 isValidIpv6(String ip) { + if (ip == null || ip.isEmpty()) return false; + try { + InetAddress inetAddress = InetAddress.getByName(ip); + return inetAddress instanceof Inet6Address; + } catch (Exception e) { + return false; + } + } + + private int ipToInt(String ipv4) { + String[] parts = ipv4.split("\\."); + 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]); + if (octet < 0 || octet > 255) { + throw new IllegalArgumentException("非法的 IPv4 地址段: " + octet); + } + result |= (octet << (24 - (8 * i))); + } + return result; + } + + + + private String getConnectionNameByDeviceName(String deviceName) { + if (deviceName == null || deviceName.trim().isEmpty()) return null; + try { + List lines = executeCommand("nmcli", "-t", "-f", "DEVICE,CONNECTION", "dev", "status"); + + for (String line : lines) { + String[] parts = line.split("(?= 2) { + String device = parts[0].replace("\\:", ":"); + String connection = parts[1].replace("\\:", ":"); + + if (deviceName.equals(device)) { + if (!connection.isEmpty() && !connection.equals("--")) { + return connection; + } + } + } + } + } catch (RuntimeException e) { + log.warn("通过设备名 [{}] 查找连接配置名失败。原因: {}", deviceName, e.getMessage(), e); + } + return null; + } + + + private List executeCommand(String... commandArgs){ +// ProcessBuilder processBuilder = new ProcessBuilder("bash", "-c", command); + ProcessBuilder processBuilder = new ProcessBuilder(commandArgs); + processBuilder.redirectErrorStream(true); + + Process process = null; + String commandStr = String.join(" ", commandArgs); + try { + process = processBuilder.start(); + + List output = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + output.add(line); + } + } + + int exitCode = process.waitFor(); + if (exitCode != 0) { + log.error("Command execution failed. Exit code: {}, Command: [{}], Output: {}", exitCode, commandArgs, output); + throw new RuntimeException("系统底层操作执行失败");} + return output; + } catch (IOException e){ + log.error("I/O error executing command: [{}]", commandArgs, e); + throw new RuntimeException("服务器内部操作异常"); + } catch (InterruptedException e) { + if (process != null) { + process.destroy(); + } + Thread.currentThread().interrupt(); + log.error("Process was interrupted while executing command: [{}]", commandArgs, e); + throw new RuntimeException("服务器内部处理中断");} + } + +} + + +