项目的组件放在`internal/`​下
  • controller(控制器):负责核心业务逻辑的调度和协调,监听 Kubernetes 中 IPBlock 自定义资源的变化,维护和管理其生命周期。

  • engine(封禁后端):负责封禁命令的实际执行。

  • notify(通知机制):负责将封禁、解封等事件以多种方式通知给运维人员或其他系统。

  • trigger(触发器):事件触发中心,负责监听外部告警系统或业务事件(如 Grafana Alert等),并根据 Alert 策略触发相关封禁操作,自动创建 IPBlock CR,且支持自动解封。

  • policy(封禁策略):定义 IP 封禁的判定规则和执行策略。


### 介绍

封禁后端均被抽象为API,IPBlock-Operator-Plus通过调用API来实现实际的封禁行为。

目前通过`engine/control.py`​来实现API,接口列表如下:

| 接口 | 方法 | 说明 |
| -------- | ---- | --------------------------------------------- |
| /limit | GET | iptables限流接口 |
| /unlimit | GET | iptables解限流接口 |
| /limits | GET | iptables查看当前限流情况 |
| /update | | XDP封禁端口 |
| /remove | | XDP解封禁端口 |
| /ban | GET | (弃用)可从nginx日志查,返回超过一定次数的IP |
| /execute | GET | (弃用)接收IP,对其执行XDP封禁 |

engine支持列表:

- XDP:依赖于[evilsp/xdp_banner: 一个简单的 XDP 小程序,用于 BAN IP](https://github.com/evilsp/xdp_banner)

- iptables:依赖于Linux工具iptables。

规则为 IP 每分钟最多发起10个新连接(可突发20次),否则DROP

```go
iptables -A INPUT -s <IP> -p tcp --dport <TARGET_PORT> \
 -m state --state NEW \
 -m hashlimit --hashlimit 10/min --hashlimit-burst 20 \
 --hashlimit-mode srcip --hashlimit-name limit_<IP_REPLACED> \
 -j ACCEPT

iptables -A INPUT -s <IP> -p tcp --dport <TARGET_PORT> -j DROP

扩展开发指南

engine定义了接口,新adapter只需要实现这两个方法即可。

type Adapter interface {
// Ban 对某个 IP 发起封禁
// ip: 要封禁的 IP 地址
// isParmanent: 是否永久封禁(true 表示永久)
// durationSeconds: 封禁时长(单位:秒,仅在临时封禁时生效)
Ban(ip string, isParmanent bool, durationSeconds int) (string, error)

// UnBan 解封某个 IP
UnBan(ip string) (string, error)
}

然后在NewAdapter​注册对应的adapter

func NewAdapter(name, gatewayHost string) Adapter {
switch name {
case "xdp":
return &XDPAdapter{GatewayHost: gatewayHost}
case "iptables":
return &IptablesAdapter{GatewayHost: gatewayHost}
default:

return &XDPAdapter{GatewayHost: gatewayHost}
}
}

接着在controller.py​中要实现对应的API,最后在configmap​中engine​字段指定对应的adapter名即可。

notify

介绍.

notify支持列表:

  • lark:飞书,通过机器人来进行通知

扩展开发指南

notify定义了接口,新notify只需要实现这个接口即可。

// 主计算函数
int Calculate(char e[])
{
numStack OPND = InitNumStack();
opStack OPTR = InitOpStack();
PushOp(OPTR, '#'); // 初始化运算符栈

char ch, op;
int a, b, result = 0;
int num = 0;
int flag = 0; // 检测是否为多位数

strcat(e, "#");

for(int i = 0; e[i] != '\0'; i++)
{
ch = e[i];
if (ch >= '0' && ch <= '9') {
num = num * 10 + (ch - '0'); // 累加组合成多位数
flag = 1;
}
else {
if(flag) {
PushNum(OPND, num); // 压入操作数
num = 0;
flag = 0;
}

while(getPriority(GetOpTop(OPTR), ch) == '>') {
PopOp(OPTR, &op);
PopNum(OPND, &b);
PopNum(OPND, &a);
result = Operate(a, b, op);
PushNum(OPND, result);
}
if (getPriority(GetOpTop(OPTR), ch) == '<') {
PushOp(OPTR, ch); // 压入当前运算符
} else if (getPriority(GetOpTop(OPTR), ch) == '=') {
PopOp(OPTR, &op); // 出栈匹配
}
}
}
PopNum(OPND, &result);
return result;
}

以lark为例,在lark.go​中实现两个方法

// NewLarkNotify 创建一个 LarkNotify(飞书通知器)实例。
//
// 参数:
//   - webhookURL: 飞书群机器人的 Webhook 地址。
//   - templatePaths: 各类通知类型所用的 card 模板路径映射(如 map["ban"]="templates/ban.json")。
//
// 返回值:
//   - 成功: 返回 LarkNotify 实例。
//   - 失败: 返回 error,通常是模板解析失败或配置不合法。
func NewLarkNotify(webhookURL string, templatePaths map[string]string) (*LarkNotify, error) {}
// Notify 实现 Notifier 接口,
// 根据事件类型和变量构造飞书卡片消息,并发送至 webhook。
//
// 参数:
//   - ctx: 请求上下文,用于控制超时和取消等。
//   - eventType: 事件类型(如 "ban"、"resolve"、"error")。
//   - vars: 模板变量键值对,例如 {"ip": "1.2.3.4", "reason": "恶意连接"}。
//
// 返回值:
//   - 成功: 返回 nil。
//   - 失败: 返回 error,表示通知失败。
func (l *LarkNotify) Notify(ctx context.Context, eventType string, vars map[string]string) error {}

最后在main.go​中watchConfigMap​中完善loadNotify​。

int main()
{
char e[MAXSIZE]; // 一条表达式
char p[MAXSIZE][MAXSIZE]; // 所有表达式
int count = 0;

printf("输入表达式,每行一个,以\"=\"结束:\n");
while(1) {
gets(e); // 读取一行表达式
// 去除换行符
e[strcspn(e,"\n")] = 0;

if(strcmp(e, "=") == 0) {
break;
}

strcpy(p[count++], e);
}
printf("计算结果为:\n");
for(int i = 0; i <count; i++) {
int result = Calculate(p[i]);
printf("%d\n", result);
}
return 0;
}

trigger

介绍

trigger支持列表:

  • grafana:可以 Grafana Alert 联动,通过 Webhook 进行触发。

扩展开发指南

{0}注意新 trigger 在开发时需要考虑并发问题。具体实现可查看 grafana.go 中的处理逻辑,也可参考核心功能模块开发文档中并发处理的逻辑介绍。

trigger同样定义了接口,新trigger只需要实现接口即可。

go

// Trigger 是封禁事件的触发器接口,Start 启动监听任务,Stop 停止监听任务
type Trigger interface {
Name() string
Start(ctx context.Context) error
Stop(ctx context.Context) error
}

trigger在manager.go​中实现了StartAll​和StopAll​,会启动在configmap​中指定的所有trigger。

  triggers:                                          # 触发器,目前仅支持 Grafana
   - name: grafana
     addr: ":8090"
     path: "/trigger/grafana"
   - name: your_trigger
     other: xxx

trigger自定义的配置字段,在main.go​中进行配置

// Grafana Trigger 示例
type TriggerConfig struct {
Name string `yaml:"name"`
Addr string `yaml:"addr,omitempty"`
Path string `yaml:"path,omitempty"`
}

// 解析 trigger 字符串为 YAML 列表
func parseTriggers(yamlStr string) ([]TriggerConfig, error) {
var triggers []TriggerConfig
err := yaml.Unmarshal([]byte(yamlStr), &triggers)
if err != nil {
return nil, err
}
return triggers, nil
}

// 选择触发器
func CreateTriggerByConfig(cfg TriggerConfig, mgr ctrl.Manager) trigger.Trigger {
switch cfg.Name {
case "grafana":
return &trigger.GrafanaTrigger{
Client: mgr.GetClient(),
Addr: cfg.Addr,
Path: cfg.Path,
// 1000 个 IP, 60 秒防抖
// LRU中最多保存1000个IP,达到1000个后会自动淘汰最近最少使用的IP
// 对于同一个IP,如果最近60s内发生过一次封禁,那么这60s内再次收到该IP的相同请求时,会被防抖识别为重复
Debouncer: utils.NewLRUDebouncer(1000, 60*time.Second),
IPLocker: utils.NewIPLock(),
}
// TODO 其他触发器 ...
default:
return nil
}
}


policy支持功能列表:

- whitelist:白名单机制

policy实现比较灵活,下面描述白名单机制的实现流程。

1. 在`policy/watchlist.go`​中实现三个函数

```go
// 白名单机制
// 单IP白名单
// CIDR白名单
// 标签匹配

type Whitelist struct {
ipNets []*net.IPNet // IPNet的指针切片,保存CIDR网段
ips []net.IP // 精确IP白名单
}

// 新建白名单
func NewWhitelist(ipList []string) *Whitelist {}

// 判断目标IP是否在白名单中
func (w *Whitelist) IsWhitelisted(ip string) bool {}

// 打印所有白名单内容
func (w *Whitelist) StringSlice() []string {}
  1. config/loader.go​中实现一个LoadWhitelistFromConfigMap​,用于加载定义在configmap​中的白名单IP。

  2. main.go​的watchConfigMap​中来监听白名单的新建和更新情况。