Compliance: Server-side anti-proxy for trail authorization

This commit is contained in:
yuanyuanxiang
2026-05-16 13:19:01 +02:00
parent 4279e79aa7
commit 4d2b12a9dd
11 changed files with 642 additions and 1 deletions

View File

@@ -157,6 +157,16 @@ public:
return false;
}
// 字符串版(点分十进制 IPv4。空串或解析失败按"非公网"处理(即返回 true
// 避免误报;调用方应自行确保传入的是有效 IPv4。仅 IPv4IPv6 不在判定范围。
static bool IsPrivateIPv4Str(const std::string& ipv4)
{
if (ipv4.empty()) return true;
in_addr addr;
if (inet_pton(AF_INET, ipv4.c_str(), &addr) != 1) return true;
return IsPrivateIP(addr.s_addr); // s_addr 已是网络字节序
}
// 获取本进程所有入站的外网TCP连接只检测别人连进来的不检测本进程连出去的
static std::vector<WanConnection> GetWanConnections()
{
@@ -563,6 +573,84 @@ private:
}
};
// 服务端 per-connection RTT 反代理检测器
//
// 设计动机:与 LANRttChecker 互补——LANRttChecker 是客户端单例(一个客户端只有一条主控连接,
// 全局滑窗即可),但服务端要同时盯多个连接,若用全局滑窗,一条 abusive 连接会被 N 条真 LAN
// 连接的中位数稀释。所以本类的每个实例只跟一条连接绑定,由 IOCPServer 持有,逐连接单独判定。
//
// 信号源:服务端 WSAIoctl(SIO_TCP_INFO).RttUs内核测得的纯网络 RTT微秒比客户端
// "心跳总耗时减 ProcessingMs" 更干净。因此阈值可以比客户端 25ms 严一点 → 20ms。
//
// 触发动作:仅返回"是否首次触发"是否真的弹框由调用方IOCPServer持全局 latch 决定。
// 同一连接生命周期内 triggered 后不再产生新的 triggerper-connection 自带 latch
//
// 线程模型单写者IOCPServer 的 RTT 轮询线程)。所有方法假设由同一线程串行调用,
// 内部不加锁;读取展示性字段建议直接走 CONTEXT_OBJECT 暴露的 atomic getter。
class TcpRttBreachDetector
{
public:
// 与 LANRttChecker 经验阈值对齐,但因信号更干净而严化:
static const int RTT_THRESHOLD_MS = 20;
static const int SAMPLE_WINDOW = 10; // 滑窗大小10s 历史 @ 1Hz
static const int WARMUP_SKIP = 5; // 跳过前 N 次样本,避免握手早期波动
static const int BREACH_PERSIST_COUNT = 3; // 连续 K 次中位数超阈值才触发
// 喂一次 RTT 样本(毫秒)。返回 true 当且仅当**本次**调用导致首次触发。
// 后续调用即使继续超阈也返回 falseper-instance latch由调用方决定是否仍要继续输入。
bool Feed(int rttMs)
{
if (m_triggered || rttMs <= 0) return false;
if (m_totalSeen++ < WARMUP_SKIP) return false;
m_samples.push_back(rttMs);
if ((int)m_samples.size() > SAMPLE_WINDOW)
m_samples.pop_front();
if ((int)m_samples.size() < SAMPLE_WINDOW)
return false;
int med = MedianMs(m_samples);
if (med > RTT_THRESHOLD_MS) m_breachRun++; else m_breachRun = 0;
if (m_breachRun >= BREACH_PERSIST_COUNT) {
m_triggered = true;
m_triggerMedianMs = med;
return true;
}
return false;
}
bool IsTriggered() const { return m_triggered; }
int TriggerMedianMs() const { return m_triggerMedianMs; }
int CurrentMedianMs() const
{
return ((int)m_samples.size() < SAMPLE_WINDOW) ? -1 : MedianMs(m_samples);
}
// 复用 CONTEXT_OBJECT 时调用(释放回 free pool 后再次接连接)。
void Reset()
{
m_samples.clear();
m_totalSeen = 0;
m_breachRun = 0;
m_triggered = false;
m_triggerMedianMs = 0;
}
private:
static int MedianMs(const std::deque<int>& s)
{
std::vector<int> v(s.begin(), s.end());
std::sort(v.begin(), v.end());
size_t n = v.size();
if (n == 0) return 0;
return (n % 2 == 0) ? (v[n / 2 - 1] + v[n / 2]) / 2 : v[n / 2];
}
std::deque<int> m_samples;
int m_totalSeen = 0;
int m_breachRun = 0;
int m_triggerMedianMs = 0;
bool m_triggered = false;
};
// 授权连接超时检测器
// 用于检测试用版/未授权用户是否长时间无法连接授权服务器
class AuthTimeoutChecker