Compliance: Server-side anti-proxy for trail authorization
This commit is contained in:
@@ -157,6 +157,16 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
// 字符串版(点分十进制 IPv4)。空串或解析失败按"非公网"处理(即返回 true),
|
||||
// 避免误报;调用方应自行确保传入的是有效 IPv4。仅 IPv4,IPv6 不在判定范围。
|
||||
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 后不再产生新的 trigger(per-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 当且仅当**本次**调用导致首次触发。
|
||||
// 后续调用即使继续超阈也返回 false(per-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
|
||||
|
||||
Reference in New Issue
Block a user