Files
DenyIP-go/ipset.go
2025-12-23 13:14:53 +08:00

184 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"strconv"
"strings"
)
// 创建 ipset 集合
func createIPSet(setName string) error {
// 建议增加 maxelem防止频繁扩容。默认 65536 有点小。
// 加上 -exist 防止报错
cmd := exec.Command("ipset", "create", setName, "hash:ip", "maxelem", "200000", "-exist")
var stderr bytes.Buffer
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("ipset create failed: %v, stderr: %s", err, stderr.String())
}
return nil
}
// 添加 IP 到集合
func AddIPSet(setName string, ip string) error {
cmd := exec.Command("ipset", "add", setName, ip)
// 不关注输出,只关注 exit code
return cmd.Run()
}
// NumIPSet 获取集合中 IP 的数量
// 注意:频繁调用此函数开销较大(因为它通过 shell 解析文本),建议仅在监控循环中低频调用
func NumIPSet(setName string) (int, error) {
// 这里必须用 sh -c 因为涉及管道符 |
cmd := exec.Command("sh", "-c", fmt.Sprintf("ipset list %s | grep 'Number of entries' | cut -d ':' -f 2", setName))
var stdout bytes.Buffer
cmd.Stdout = &stdout
err := cmd.Run()
if err != nil {
return 0, err
}
output := strings.TrimSpace(stdout.String())
numEntries, err := strconv.Atoi(output)
if err != nil {
return 0, fmt.Errorf("解析数量失败: %w, 输出: %s", err, output)
}
return numEntries, nil
}
// Is_Name_Ipset 检查集合是否存在
// 返回 0 表示存在 (ExitCode 0),其他表示不存在
func Is_Name_Ipset(setName string) int {
cmd := exec.Command("ipset", "list", setName, "-n") // -n 只输出名字,减少开销
err := cmd.Run()
if err != nil {
return 1 // 不存在或出错
}
return 0 // 存在
}
// Is_Ip_Ipset 检查 IP 是否在 ANY root 集合中
// 优化:不再使用 grep 全局搜索,而是遍历 root0...rootN 使用 ipset test
func Is_Ip_Ipset(ip string) int {
// 遍历所有可能的集合名
for i := 0; i < MAX_IPSET_NAME; i++ {
setName := fmt.Sprintf("root%d", i)
// 快速检查集合是否存在(可选,为了严谨)
// 如果 ipset test 在集合不存在时会报错,所以我们直接运行 test
// 如果 ip 在集合中exit code 为 0
cmd := exec.Command("ipset", "test", setName, ip)
err := cmd.Run()
if err == nil {
return 1 // 找到了IP 在该集合中
}
}
return 0 // 所有集合都找过了,不在其中
}
// RemoveIPIfInSets 在所有集合中查找并删除某个 IP
func RemoveIPIfInSets(prefix string, max int, ip string) (string, error) {
for i := 0; i < max; i++ {
setName := fmt.Sprintf("%s%d", prefix, i)
// 1. 先测试是否存在 (避免无意义的 del 调用报错)
testCmd := exec.Command("ipset", "test", setName, ip)
if err := testCmd.Run(); err != nil {
// exit code != 0 说明不在这个集合里,继续找下一个
continue
}
// 2. 存在则删除
delCmd := exec.Command("ipset", "del", setName, ip)
if err := delCmd.Run(); err != nil {
return "", fmt.Errorf("从 %s 删除 %s 失败: %v", setName, ip, err)
}
return setName, nil // 成功删除
}
return "", nil // 未找到
}
// iptables_add 添加规则
// 修正:使用 -I (Insert) 而不是 -A (Append),确保规则在最前面生效
func iptables_add(setName string) error {
// 检查规则是否已经存在,避免重复添加
checkCmd := exec.Command("iptables", "-C", "INPUT", "-m", "set", "--match-set", setName, "src", "-j", "DROP")
if err := checkCmd.Run(); err == nil {
// 规则已存在,直接返回
return nil
}
// 添加规则
cmd := exec.Command("iptables", "-I", "INPUT", "-m", "set", "--match-set", setName, "src", "-j", "DROP")
var stderr bytes.Buffer
cmd.Stderr = &stderr
err := cmd.Run()
if err != nil {
log.Printf("添加 iptables 规则失败 [%s]: %v, stderr: %s", setName, err, stderr.String())
}
return err
}
// iptables_del 删除规则
func iptables_del(setName string) error {
// 循环删除,防止有重复规则
for {
cmd := exec.Command("iptables", "-D", "INPUT", "-m", "set", "--match-set", setName, "src", "-j", "DROP")
if err := cmd.Run(); err != nil {
// 删除失败通常意味着规则不存在了,跳出循环
break
}
}
return nil
}
// iptables_list 打印规则
func iptables_list() error {
cmd := exec.Command("iptables", "-L", "INPUT", "-v", "-n", "--line-numbers")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
/*
func main() {
// 创建 IPSet但即使出错也继续执行
err := createIPSet("root0")
if err != nil {
log.Println("创建 IPSet 出错:", err)
} else {
fmt.Println("IPSet 创建成功!")
}
// 添加 IP 到 IPSet出错时继续执行
err = AddIPSet("root0", "1.1.1.1")
if err != nil {
log.Println("添加 IP 到 IPSet 出错:", err)
}
// 获取 IPSet 条目数,出错时继续执行
num, err := NumIPSet("root0")
if err != nil {
log.Println("获取 IPSet 条目数出错:", err)
} else {
fmt.Printf("IPSet 条目数: %d\n", num)
}
iptables("root0")
}
*/