mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-09-30 00:06:38 +08:00
修复隧道添加问题
This commit is contained in:
@@ -13,6 +13,51 @@
|
||||
- 提供灵活的转发策略配置,适用于多种网络代理场景
|
||||
|
||||
---
|
||||
# 流量统计算法
|
||||
|
||||
## 流量流向定义
|
||||
|
||||
```
|
||||
用户端 ←→ 转发服务器 ←→ 目标服务器/出口节点
|
||||
```
|
||||
|
||||
### 数据流向说明
|
||||
|
||||
| 流向 | 描述 | 计费类型 |
|
||||
|-----|-----|----------|
|
||||
| 用户 → 转发服务器 | 用户发送到服务器的数据包 | 入站流量 |
|
||||
| 转发服务器 → 目标服务器 | 服务器转发到目标的数据包 | 出站流量 |
|
||||
| 目标服务器 → 转发服务器 | 目标服务器返回的数据包 | 入站流量 |
|
||||
| 转发服务器 → 用户 | 服务器返回给用户的数据包 | 出站流量 |
|
||||
|
||||
## 计费模式
|
||||
|
||||
### 🔄 双向计费
|
||||
统计所有方向的流量,包括:
|
||||
- ✅ 用户 → 转发服务器
|
||||
- ✅ 转发服务器 → 目标服务器
|
||||
- ✅ 目标服务器 → 转发服务器
|
||||
- ✅ 转发服务器 → 用户
|
||||
|
||||
**计费公式**:`总流量 = 入站流量 + 出站流量`
|
||||
|
||||
### ➡️ 单向计费
|
||||
仅统计有效的转发流量,包括:
|
||||
- ❌ 用户 → 转发服务器 (不计费)
|
||||
- ✅ 转发服务器 → 目标服务器 (计费)
|
||||
- ❌ 目标服务器 → 转发服务器 (不计费)
|
||||
- ✅ 转发服务器 → 用户 (计费)
|
||||
|
||||
**计费公式**:`总流量 = 转发出站流量`
|
||||
|
||||
## 使用场景
|
||||
|
||||
- **双向计费**:适用于需要精确计量所有网络资源消耗的场景
|
||||
- **单向计费**:适用于用户友好的计费模式,只对实际的数据转发服务收费
|
||||
|
||||
> 💡 **提示**:可以在创建隧道时选择不同的流量计算方式,灵活适配不同的业务需求
|
||||
---
|
||||
|
||||
## 部署流程
|
||||
|
||||
### 源码编译部署
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -36,7 +36,7 @@ public class NodeServiceImpl extends ServiceImpl<NodeMapper, Node> implements No
|
||||
// ========== 常量定义 ==========
|
||||
|
||||
/** 节点默认状态:启用 */
|
||||
private static final int NODE_STATUS_ACTIVE = 1;
|
||||
private static final int NODE_STATUS_ACTIVE = 0;
|
||||
|
||||
/** 成功响应消息 */
|
||||
private static final String SUCCESS_CREATE_MSG = "节点创建成功";
|
||||
|
||||
@@ -69,6 +69,8 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
||||
private static final String ERROR_IN_PORT_RANGE_INVALID = "入口端口开始不能大于结束端口";
|
||||
private static final String ERROR_OUT_PORT_RANGE_INVALID = "出口端口开始不能大于结束端口";
|
||||
private static final String ERROR_NO_AVAILABLE_TUNNELS = "暂无可用隧道";
|
||||
private static final String ERROR_IN_NODE_IP_INVALID = "入口节点IP地址无效,请先安装节点端的gost(支持IPv4和IPv6)";
|
||||
private static final String ERROR_OUT_NODE_IP_INVALID = "出口节点IP地址无效,请先安装节点端的gost(支持IPv4和IPv6)";
|
||||
|
||||
/** 使用检查相关消息 */
|
||||
private static final String ERROR_FORWARDS_IN_USE = "该隧道还有 %d 个转发在使用,请先删除相关转发";
|
||||
@@ -221,6 +223,11 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
||||
return NodeValidationResult.error(ERROR_IN_NODE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 验证入口节点IP地址
|
||||
if (!isValidNodeIp(inNode.getIp())) {
|
||||
return NodeValidationResult.error(ERROR_IN_NODE_IP_INVALID);
|
||||
}
|
||||
|
||||
// 验证入口端口范围
|
||||
if (tunnelDto.getInPortSta() > tunnelDto.getInPortEnd()) {
|
||||
return NodeValidationResult.error(ERROR_IN_PORT_RANGE_INVALID);
|
||||
@@ -306,6 +313,11 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
||||
return R.err(ERROR_OUT_NODE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 验证出口节点IP地址
|
||||
if (!isValidNodeIp(outNode.getIp())) {
|
||||
return R.err(ERROR_OUT_NODE_IP_INVALID);
|
||||
}
|
||||
|
||||
// 验证出口端口参数
|
||||
if (tunnelDto.getOutIpSta() == null || tunnelDto.getOutIpEnd() == null) {
|
||||
return R.err(ERROR_OUT_PORT_REQUIRED);
|
||||
@@ -333,6 +345,115 @@ public class TunnelServiceImpl extends ServiceImpl<TunnelMapper, Tunnel> impleme
|
||||
tunnel.setUpdatedTime(currentTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证节点IP地址是否有效
|
||||
* 支持IPv4和IPv6格式
|
||||
*
|
||||
* @param ip IP地址字符串
|
||||
* @return IP地址是否有效
|
||||
*/
|
||||
private boolean isValidNodeIp(String ip) {
|
||||
if (StrUtil.isBlank(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证IPv4格式
|
||||
if (isValidIpv4(ip)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 验证IPv6格式
|
||||
return isValidIpv6(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证IPv4地址格式
|
||||
*
|
||||
* @param ip IP地址字符串
|
||||
* @return 是否为有效的IPv4地址
|
||||
*/
|
||||
private boolean isValidIpv4(String ip) {
|
||||
if (StrUtil.isBlank(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] parts = ip.split("\\.");
|
||||
if (parts.length != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
for (String part : parts) {
|
||||
int num = Integer.parseInt(part);
|
||||
if (num < 0 || num > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证IPv6地址格式
|
||||
*
|
||||
* @param ip IP地址字符串
|
||||
* @return 是否为有效的IPv6地址
|
||||
*/
|
||||
private boolean isValidIpv6(String ip) {
|
||||
if (StrUtil.isBlank(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 简化的IPv6验证,支持标准格式和压缩格式
|
||||
try {
|
||||
// 移除可能的端口号
|
||||
if (ip.startsWith("[") && ip.contains("]:")) {
|
||||
ip = ip.substring(1, ip.indexOf("]:"));
|
||||
}
|
||||
|
||||
// 检查是否包含非法字符
|
||||
if (!ip.matches("^[0-9a-fA-F:]+$")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 处理双冒号压缩格式
|
||||
if (ip.contains("::")) {
|
||||
if (ip.indexOf("::") != ip.lastIndexOf("::")) {
|
||||
// 不能有多个双冒号
|
||||
return false;
|
||||
}
|
||||
// 展开双冒号
|
||||
String[] parts = ip.split("::");
|
||||
int leftParts = parts[0].isEmpty() ? 0 : parts[0].split(":").length;
|
||||
int rightParts = parts.length > 1 && !parts[1].isEmpty() ? parts[1].split(":").length : 0;
|
||||
int totalParts = leftParts + rightParts;
|
||||
if (totalParts >= 8) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 标准格式,必须有8个部分
|
||||
String[] parts = ip.split(":");
|
||||
if (parts.length != 8) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证每个部分都是有效的十六进制数
|
||||
for (String part : parts) {
|
||||
if (part.length() > 4 || part.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
Integer.parseInt(part, 16); // 如果不是有效的十六进制会抛出异常
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查隧道是否存在
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user