起因

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

image-20220415144506233

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

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

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

Why It Happened

Around 8 am on April 13th, I was still fast asleep when I was woken up by a push notification on Telegram.

image-20220415144506233

Strange — my little WebServer project, which nobody even visits, had crashed all by itself 😕.

I woke up again around 10 am and took a look at the logs.

Well well, someone is practicing on my copied little project?

image-20220415145015955

image-20220415145042684

image-20220415145122937

image-20220415145149467

image-20220415145254826

image-20220415145355170

image-20220415145446810

image-20220415145518180

等等…很多很多

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

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

image-20220415145015955

image-20220415145042684

image-20220415145122937

image-20220415145149467

image-20220415145254826

image-20220415145355170

image-20220415145446810

image-20220415145518180

And so on… a lot of these.

I couldn’t tell what he was doing with all these operations, but I knew that normal clicking around on a web page wouldn’t produce such request data. After this whole series of fancy operations went on for more than 10 minutes, my server crashed.

To deal with this, the only option was to add an IP blacklist module.

基本功能

我的想法是这样的,如果在短时间内同一个 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 黑名单类。

Basic Functionality

My idea was this: if the same IP gets 404s multiple times in a short period, put it on the IP blacklist and ban it for a while.

The implementation logic is as follows:

  1. The IP blacklist class needs to maintain the following data members
    • 404 count threshold (if the 404 count exceeds this threshold, the IP’s connection may be blocked)
    • Expiration time for IPs already on the blacklist (IPs are not permanently banned; after this time they will be removed from the blacklist)
    • 404 counting time window (as mentioned in the description, only 404s within a short period are counted; this variable defines how long that short period is)
    • A mapping from an IP to its current state (i.e., the key is the IP, and the value has two parts: the first is the currently recorded 404 count, the second is the time point when the first 404 was recorded)
  2. Maintains the following public functions
    • 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 计数

Since we need to use the blacklist object in the main thread to check whether the current IP is on the blacklist, and also use it in child threads to increment the 404 count for an IP, the IP blacklist class is implemented as a singleton.

代码实现

头文件

Code Implementation

Header file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
* @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

源文件

Source file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*
* @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 个小时,应该三小时后就解封了吧。

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

Effect Test

Within two minutes, I made several requests to non-existent pages

And it immediately looked like this

image-20220415153901827

And the server recorded the relevant logs

image-20220415154454907

I switched to a new IP and could access the server normally. I set the ban time to 3 hours, so it should be unbanned after three hours.

Finally, I welcome any expert passing by to point out mistakes.