起因

4 月 13 号早上八点多,我还在睡梦中,被 Telegram 的一条推送吵醒了。

image-20220415144506233

很奇怪,我一个根本没人访问的 WebServer 小项目竟然会自己就宕机了😕。

上午十点睡醒了,起来看一眼日志吧。

好家伙,有人拿我 抄来 的小项目在练手?

image-20220415145015955

image-20220415145042684

image-20220415145122937

image-20220415145149467

image-20220415145254826

image-20220415145355170

image-20220415145446810

image-20220415145518180

等等…很多很多

我也看不到他这些操作是在干什么,但我知道正常在网页上点来点去是不会有这样的请求数据的。这一系列花里胡哨的操作持续了 10 多分钟我就宕机了。

为了应对这种情况,只能再加一个 IP 黑名单模块了。

基本功能

我的想法是这样的,如果在短时间内同一个 IP 连续多次获得了 404 ,那就把它放在 IP 黑名单里封禁一段时间。

实现逻辑如下:

  1. IP 黑名单类中需要维护以下数据成员
    • 404计数阈值(404次数超过这个阈值,将有可能屏蔽这个IP的连接)
    • 已经在黑名单中的 IP 过期时间(对 IP 不进行永久封禁,超过这个时间将被移除黑名单)
    • 404计数时间范围(在功能描述中提到,只对短时间内的404进行计数,这个变量规定了这个短时间是多长时间)
    • 一个IP到该IP目前状态的映射(也就是说,键是 IP,值包含两部分,第一部分是目前被记录的404次数,第二部分是第一次被记录404的时间点)
  2. 主要维护以下公有函数
    • 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(){

}

效果测试

我在两分钟内请求了几次不存在的页面

就直接这样了

image-20220415153901827

并且服务器记录下相关日志

image-20220415154454907

我换了一个新的 IP 可以正常访问服务器。我设置的封禁时间是 3 个小时,应该三小时后就解封了吧。

最后还是欢迎路过的大佬指出错误。