注意:这篇文章上次更新于932天前,文章内容可能已经过时。
起因
4 月 13 号早上八点多,我还在睡梦中,被 Telegram 的一条推送吵醒了。
很奇怪,我一个根本没人访问的 WebServer 小项目竟然会自己就宕机了😕。
上午十点睡醒了,起来看一眼日志吧。
好家伙,有人拿我 抄来 的小项目在练手?
等等…很多很多
我也看不到他这些操作是在干什么,但我知道正常在网页上点来点去是不会有这样的请求数据的。这一系列花里胡哨的操作持续了 10 多分钟我就宕机了。
为了应对这种情况,只能再加一个 IP 黑名单模块了。
基本功能
我的想法是这样的,如果在短时间内同一个 IP 连续多次获得了 404 ,那就把它放在 IP 黑名单里封禁一段时间。
实现逻辑如下:
- IP 黑名单类中需要维护以下数据成员
- 404计数阈值(404次数超过这个阈值,将有可能屏蔽这个IP的连接)
- 已经在黑名单中的 IP 过期时间(对 IP 不进行永久封禁,超过这个时间将被移除黑名单)
- 404计数时间范围(在功能描述中提到,只对短时间内的404进行计数,这个变量规定了这个短时间是多长时间)
- 一个IP到该IP目前状态的映射(也就是说,键是 IP,值包含两部分,第一部分是目前被记录的404次数,第二部分是第一次被记录404的时间点)
- 主要维护以下公有函数
- void set_member(int cnt404,int expire ,int countTime); // 设置成员变量
- bool in_list(u_int32_t IP); // 判断当前 IP 是否在黑名单内
- void add_404(u_int32_t IP); // 增加当前 IP 的 404 计数
由于我们既需要在主线程中使用黑名单对象检查当前 IP 是否在黑名单内,也需要在子线程中使用该对象为 IP 增加 404 计数。所以采用单例模式实现 IP 黑名单类。
代码实现
头文件
/*
* @Author : WANG-Guangxin
* @Date : 2022-04-15
* @filename : blacklist.h
*/
#ifndef BLACKLIST_H
#define BLACKLIST_H
#include <unordered_map>
#include <assert.h>
#include <chrono>
#include <memory>
#include <sys/time.h>
#include <mutex>
using namespace std;
using namespace std::chrono;
using BlackMap = std::unique_ptr<unordered_map<u_int32_t,pair<int,steady_clock::time_point>>>;
using myClock = std::chrono::steady_clock;
class BlackList{
public:
void set_member(int cnt404,int expire ,int countTime);
static BlackList* get_instance();
bool in_list(u_int32_t IP);
void add_404(u_int32_t IP);
private:
BlackList();
virtual ~BlackList();
private:
BlackMap blacklist_;
int cnt404_;
int expires_;
int countTime_;
std::mutex mtx_;
};
#endif
源文件
/*
* @Author : WANG-Guangxin
* @Date : 2022-04-15
* @filename : blacklist.cpp
*/
#include "blacklist.h"
using namespace std;
BlackList* BlackList::get_instance(){
static BlackList bklist;
return &bklist;
}
void BlackList::set_member(int cnt404,int expire,int countTime){
cnt404_ = cnt404;
expires_ = expire;
countTime_ = countTime;
}
bool BlackList::in_list(u_int32_t IP){
if(blacklist_->count(IP)){
if(blacklist_->at(IP).first >= cnt404_){
if(duration_cast<chrono::seconds>( myClock::now() - blacklist_->at(IP).second ).count() > expires_ ){
blacklist_->erase(IP);
return false;
}
else{
return true;
}
}
else{
return false;
}
}
else{
return false;
}
}
void BlackList::add_404(u_int32_t IP){
lock_guard<std::mutex> locker(mtx_);
if(blacklist_->count(IP)){
if(duration_cast<chrono::seconds>(myClock::now() - blacklist_->at(IP).second).count() < countTime_ ){
blacklist_->at(IP).first += 1;
}
else{
blacklist_->at(IP).first = 1;
blacklist_->at(IP).second = myClock::now();
}
}
else{
blacklist_->insert({IP,{1,myClock::now()}});
}
}
BlackList::BlackList():
blacklist_(new unordered_map<u_int32_t,pair<int,steady_clock::time_point>>()),
cnt404_(30),expires_(10800),countTime_(120){
}
BlackList::~BlackList(){
}
效果测试
我在两分钟内请求了几次不存在的页面
就直接这样了
并且服务器记录下相关日志
我换了一个新的 IP 可以正常访问服务器。我设置的封禁时间是 3 个小时,应该三小时后就解封了吧。
最后还是欢迎路过的大佬指出错误。