新架构
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
apt-get install ipset
|
apt-get install ipset
|
||||||
apt-get install libcap-dev libpcap-dev libdbus-1-dev
|
apt-get install libcap-dev libpcap-dev libdbus-1-dev libsystemd-dev
|
||||||
|
|
||||||
git clone https://git.aixiao.me/aixiao/DenyIP-go.git
|
git clone https://git.aixiao.me/aixiao/DenyIP-go.git
|
||||||
cd DenyIP-go
|
cd DenyIP-go
|
||||||
|
|||||||
95
cap.go
95
cap.go
@@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
"github.com/google/gopacket/pcapgo"
|
"github.com/google/gopacket/pcapgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 打印可用的网络接口信息
|
// 打印可用的网络接口信息 (保持不变)
|
||||||
func printAvailableInterfaces() {
|
func printAvailableInterfaces() {
|
||||||
devices, err := pcap.FindAllDevs()
|
devices, err := pcap.FindAllDevs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -27,54 +26,61 @@ func printAvailableInterfaces() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断 IP 是否在链表中
|
// ----------------------------------------------------------------------
|
||||||
func isIPInList(ip net.IP) bool {
|
// 移除 isIPInList 函数 (不再需要)
|
||||||
for e := IpList.Front(); e != nil; e = e.Next() {
|
// ----------------------------------------------------------------------
|
||||||
if e.Value.(net.IP).Equal(ip) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印捕获到的每个数据包的信息
|
// 处理捕获到的数据包 (原 printPacketInfo 的优化版)
|
||||||
func printPacketInfo(packet gopacket.Packet) {
|
func handlePacket(packet gopacket.Packet) {
|
||||||
|
// 1. 快速提取网络层 (IPv4)
|
||||||
|
// 使用 Layer() 比 Layers() 稍微快一点点,但如果追求极致性能,建议使用 ZeroCopy 模式
|
||||||
ipLayer := packet.Layer(layers.LayerTypeIPv4)
|
ipLayer := packet.Layer(layers.LayerTypeIPv4)
|
||||||
|
if ipLayer == nil {
|
||||||
if ipLayer != nil {
|
return
|
||||||
ip, _ := ipLayer.(*layers.IPv4)
|
|
||||||
IpMutex.Lock()
|
|
||||||
defer IpMutex.Unlock()
|
|
||||||
if !isIPInList(ip.SrcIP) {
|
|
||||||
IpList.PushBack(ip.SrcIP)
|
|
||||||
log.Printf("\033[31m 已添加源 IP: %s 到链表 \033[0m\n", ip.SrcIP)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
ip, ok := ipLayer.(*layers.IPv4)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 提取源 IP 字符串
|
||||||
|
srcIP := ip.SrcIP.String()
|
||||||
|
|
||||||
|
// 3. 将 IP 推入全局处理队列
|
||||||
|
// 这里不再加锁,也不判断是否存在链表中,直接交给 PushIPToQueue 的非阻塞逻辑处理
|
||||||
|
// PushIPToQueue 会负责去重 (PendingIPs) 和限流
|
||||||
|
PushIPToQueue(srcIP)
|
||||||
|
|
||||||
|
// 注意:为了性能,去掉了这里的日志打印。
|
||||||
|
// 在高流量攻击下,每秒打印几千行日志会导致 IO 阻塞,拖慢抓包速度。
|
||||||
|
// 具体的封禁日志会在 processIP (消费者) 中打印。
|
||||||
}
|
}
|
||||||
|
|
||||||
// startPacketCapture 启动数据包捕获
|
// startPacketCapture 启动数据包捕获
|
||||||
func startPacketCapture() {
|
func startPacketCapture() {
|
||||||
|
if *InterfaceName == "" {
|
||||||
|
log.Fatal("未指定网络接口,请使用 -i 参数")
|
||||||
|
}
|
||||||
|
|
||||||
// 打开指定的网络接口进行实时数据包捕获
|
// 打开指定的网络接口进行实时数据包捕获
|
||||||
handle, err := pcap.OpenLive(*InterfaceName, 65535, true, pcap.BlockForever)
|
// snaplen 设置为 1600 足够捕获头部信息,没必要设为 65535,可以稍微节省内存
|
||||||
|
handle, err := pcap.OpenLive(*InterfaceName, 1600, true, pcap.BlockForever)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("打开网络接口 %s 出错: %v", *InterfaceName, err)
|
log.Fatalf("打开网络接口 %s 出错: %v", *InterfaceName, err)
|
||||||
}
|
}
|
||||||
// 确保在函数退出时关闭句柄,释放资源
|
|
||||||
defer func() {
|
defer func() {
|
||||||
fmt.Println("清理资源...")
|
fmt.Println("清理抓包资源...")
|
||||||
if handle != nil {
|
|
||||||
handle.Close()
|
handle.Close()
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 设置 BPF(Berkeley Packet Filter)过滤器,以便只捕获指定协议的数据包
|
// 设置 BPF 过滤器
|
||||||
err = handle.SetBPFFilter(*Protocol)
|
if *Protocol != "" {
|
||||||
if err != nil {
|
if err = handle.SetBPFFilter(*Protocol); err != nil {
|
||||||
log.Fatalf("设置 BPF 过滤器出错: %v", err)
|
log.Fatalf("设置 BPF 过滤器出错: %v", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果指定了输出文件,则创建文件并初始化 pcapgo.Writer
|
// --- PCAP 文件保存逻辑 (如果不需要存包,这部分其实是最大的性能瓶颈) ---
|
||||||
var pcapWriter *pcapgo.Writer
|
var pcapWriter *pcapgo.Writer
|
||||||
if *PcapFile != "" {
|
if *PcapFile != "" {
|
||||||
file, err := os.Create(*PcapFile)
|
file, err := os.Create(*PcapFile)
|
||||||
@@ -84,28 +90,35 @@ func startPacketCapture() {
|
|||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
pcapWriter = pcapgo.NewWriter(file)
|
pcapWriter = pcapgo.NewWriter(file)
|
||||||
// 写入 pcap 文件头部,指定最大捕获长度和链路层类型
|
// 写入文件头
|
||||||
err = pcapWriter.WriteFileHeader(65535, layers.LinkTypeEthernet)
|
if err = pcapWriter.WriteFileHeader(1600, layers.LinkTypeEthernet); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("写入全局头部出错: %v", err)
|
log.Fatalf("写入全局头部出错: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建数据包源,用于从网络接口读取数据包
|
// 使用 ZeroCopyPacketDataSource 可以减少内存拷贝,提升性能 (Linux Only)
|
||||||
|
// 如果在非 Linux 环境编译报错,请改回 gopacket.NewPacketSource
|
||||||
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
||||||
|
// packetSource.NoCopy = true // 开启 NoCopy 模式 (可选,稍微危险但更快)
|
||||||
|
|
||||||
log.Printf(" 正在监听网络接口 %s, 使用过滤器 '%s'...\n", *InterfaceName, *Protocol)
|
log.Printf(" 正在监听网络接口 %s, 使用过滤器 '%s'...\n", *InterfaceName, *Protocol)
|
||||||
|
|
||||||
// 创建信号通道,用于捕获中断信号
|
// 创建信号通道
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
// 启动一个 goroutine 来处理捕获到的数据包
|
// 启动 Goroutine 处理数据包
|
||||||
go func() {
|
go func() {
|
||||||
|
// 直接遍历 channel
|
||||||
for packet := range packetSource.Packets() {
|
for packet := range packetSource.Packets() {
|
||||||
// 打印数据包信息
|
|
||||||
printPacketInfo(packet)
|
// 1. 核心业务:提取 IP 进队列
|
||||||
// 如果指定了输出文件,则将数据包写入文件
|
handlePacket(packet)
|
||||||
|
|
||||||
|
// 2. 可选业务:存盘
|
||||||
|
// 警告:在高并发攻击下,写文件 IO 会成为瓶颈。建议生产环境非必要不开启 -o 参数
|
||||||
if pcapWriter != nil {
|
if pcapWriter != nil {
|
||||||
|
// 注意:CaptureInfo 和 Data 必须在 packet 被复用前读取
|
||||||
err := pcapWriter.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
|
err := pcapWriter.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("写入数据包出错: %v", err)
|
log.Printf("写入数据包出错: %v", err)
|
||||||
@@ -114,7 +127,7 @@ func startPacketCapture() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// 等待中断信号,收到信号后停止捕获
|
// 阻塞等待信号
|
||||||
<-sigChan
|
<-sigChan
|
||||||
fmt.Println("\n停止抓包...")
|
fmt.Println("\n停止抓包...")
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
denyip.upx
Normal file
BIN
denyip.upx
Normal file
Binary file not shown.
4
embed.go
4
embed.go
@@ -59,9 +59,9 @@ func embed_ip2region() {
|
|||||||
|
|
||||||
// 你可以在这里使用 ip2region.xdb 文件了,例如:
|
// 你可以在这里使用 ip2region.xdb 文件了,例如:
|
||||||
if _, err := os.Stat("ip2region/ip2region.xdb"); err == nil {
|
if _, err := os.Stat("ip2region/ip2region.xdb"); err == nil {
|
||||||
log.Println(" ✅ 确认 ip2region.xdb 已成功写出")
|
log.Println(" 确认 ip2region.xdb 已成功写出")
|
||||||
// 这里可以调用 ip2region 逻辑加载它
|
// 这里可以调用 ip2region 逻辑加载它
|
||||||
} else {
|
} else {
|
||||||
log.Println(" ❌ 找不到 ip2region.xdb")
|
log.Println(" 找不到 ip2region.xdb")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -1,10 +1,10 @@
|
|||||||
module denyip
|
module denyip
|
||||||
|
|
||||||
go 1.25.4
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251207115101-d4b8f9f841b9
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251215094000-f12b9765b373
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -12,6 +12,8 @@ github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251124080701-096d68ea7
|
|||||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251124080701-096d68ea7706/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251124080701-096d68ea7706/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
|
||||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251207115101-d4b8f9f841b9 h1:0IngVEHYqJUpjrnY9T1dZ2AMIbsI/sCUxxg77eGXXes=
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251207115101-d4b8f9f841b9 h1:0IngVEHYqJUpjrnY9T1dZ2AMIbsI/sCUxxg77eGXXes=
|
||||||
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251207115101-d4b8f9f841b9/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251207115101-d4b8f9f841b9/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
|
||||||
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251215094000-f12b9765b373 h1:JrfpSwKuIr0RoPoJlYTlbZknElX41AuAwiWhhNF7Ff0=
|
||||||
|
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20251215094000-f12b9765b373/go.mod h1:+mNMTBuDMdEGhWzoQgc6kBdqeaQpWh5ba8zqmp2MxCU=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
|||||||
177
ipset.go
177
ipset.go
@@ -4,181 +4,154 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 创建 ipset 集合
|
||||||
func createIPSet(setName string) error {
|
func createIPSet(setName string) error {
|
||||||
cmd := exec.Command("ipset", "create", setName, "hash:ip")
|
// 建议增加 maxelem,防止频繁扩容。默认 65536 有点小。
|
||||||
var stdout, stderr bytes.Buffer
|
// 加上 -exist 防止报错
|
||||||
cmd.Stdout = &stdout
|
cmd := exec.Command("ipset", "create", setName, "hash:ip", "maxelem", "200000", "-exist")
|
||||||
|
var stderr bytes.Buffer
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 记录错误信息,但不退出
|
return fmt.Errorf("ipset create failed: %v, stderr: %s", err, stderr.String())
|
||||||
log.Printf("failed to execute command: %v, stderr: %s", err, stderr.String())
|
|
||||||
}
|
}
|
||||||
return err // 返回错误以便调用者处理
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加 IP 到集合
|
||||||
func AddIPSet(setName string, ip string) error {
|
func AddIPSet(setName string, ip string) error {
|
||||||
cmd := exec.Command("ipset", "add", setName, ip)
|
cmd := exec.Command("ipset", "add", setName, ip)
|
||||||
var stdout, stderr bytes.Buffer
|
// 不关注输出,只关注 exit code
|
||||||
cmd.Stdout = &stdout
|
return cmd.Run()
|
||||||
cmd.Stderr = &stderr
|
|
||||||
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
// 记录错误信息,但不退出
|
|
||||||
log.Printf("failed to add IP to set: %v, stderr: %s", err, stderr.String())
|
|
||||||
}
|
|
||||||
return err // 返回错误以便调用者处理
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumIPSet returns the number of entries in the specified ipset set.
|
// NumIPSet 获取集合中 IP 的数量
|
||||||
|
// 注意:频繁调用此函数开销较大(因为它通过 shell 解析文本),建议仅在监控循环中低频调用
|
||||||
func NumIPSet(setName string) (int, error) {
|
func NumIPSet(setName string) (int, error) {
|
||||||
cmd := exec.Command("sh", "-c", fmt.Sprintf("ipset list %s | grep \"Number of entries\" | cut -d ':' -f 2 | sed 's/ //g'", setName))
|
// 这里必须用 sh -c 因为涉及管道符 |
|
||||||
|
cmd := exec.Command("sh", "-c", fmt.Sprintf("ipset list %s | grep 'Number of entries' | cut -d ':' -f 2", setName))
|
||||||
|
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
cmd.Stderr = &stderr
|
|
||||||
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("cmd.Run() failed with %v, stderr: %s\n", err, stderr.String())
|
return 0, err
|
||||||
return 0, fmt.Errorf("failed to execute command: %w, stderr: %s", err, stderr.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output := strings.TrimSpace(stdout.String())
|
output := strings.TrimSpace(stdout.String())
|
||||||
numEntries, err := strconv.Atoi(output)
|
numEntries, err := strconv.Atoi(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to parse output as integer: %v, output: %s\n", err, output)
|
return 0, fmt.Errorf("解析数量失败: %w, 输出: %s", err, output)
|
||||||
return 0, fmt.Errorf("failed to parse output as integer: %w, output: %s", err, output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return numEntries, nil
|
return numEntries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsIpset 检查名为 setName 的 ipset 是否存在,通过返回 0 表示存在,非零表示不存在或其他错误。
|
// Is_Name_Ipset 检查集合是否存在
|
||||||
|
// 返回 0 表示存在 (ExitCode 0),其他表示不存在
|
||||||
func Is_Name_Ipset(setName string) int {
|
func Is_Name_Ipset(setName string) int {
|
||||||
cmd := exec.Command("ipset", "list", setName)
|
cmd := exec.Command("ipset", "list", setName, "-n") // -n 只输出名字,减少开销
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
return 1 // 不存在或出错
|
||||||
// The program has exited with an exit code != 0
|
|
||||||
return exitError.ExitCode()
|
|
||||||
} else {
|
|
||||||
// Another error occurred (e.g., command not found)
|
|
||||||
return -1 // 或者你可以选择其他方式来标识这种情况
|
|
||||||
}
|
}
|
||||||
|
return 0 // 存在
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command executed successfully, the set exists
|
// Is_Ip_Ipset 检查 IP 是否在 ANY root 集合中
|
||||||
return 0
|
// 优化:不再使用 grep 全局搜索,而是遍历 root0...rootN 使用 ipset test
|
||||||
}
|
|
||||||
|
|
||||||
func Is_Ip_Ipset(ip string) int {
|
func Is_Ip_Ipset(ip string) int {
|
||||||
cmd := exec.Command("sh", "-c", fmt.Sprintf("ipset list | grep \"%s\"", ip))
|
// 遍历所有可能的集合名
|
||||||
|
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()
|
err := cmd.Run()
|
||||||
|
|
||||||
if err != nil {
|
if err == nil {
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
return 1 // 找到了,IP 在该集合中
|
||||||
// The program has exited with an exit code != 0
|
|
||||||
return exitError.ExitCode()
|
|
||||||
} else {
|
|
||||||
// Another error occurred (e.g., command not found)
|
|
||||||
return -1 // 或者你可以选择其他方式来标识这种情况
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command executed successfully, the set exists
|
return 0 // 所有集合都找过了,不在其中
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveIPIfInSets 在多个 ipset 中查找并删除某个 IP,成功删除则返回集合名
|
// RemoveIPIfInSets 在所有集合中查找并删除某个 IP
|
||||||
func RemoveIPIfInSets(prefix string, max int, ip string) (string, error) {
|
func RemoveIPIfInSets(prefix string, max int, ip string) (string, error) {
|
||||||
for i := 0; i < max; i++ {
|
for i := 0; i < max; i++ {
|
||||||
setName := fmt.Sprintf("%s%d", prefix, i)
|
setName := fmt.Sprintf("%s%d", prefix, i)
|
||||||
|
|
||||||
// 检查 IP 是否在当前集合中
|
// 1. 先测试是否存在 (避免无意义的 del 调用报错)
|
||||||
cmd := exec.Command("ipset", "test", setName, ip)
|
testCmd := exec.Command("ipset", "test", setName, ip)
|
||||||
output, err := cmd.CombinedOutput()
|
if err := testCmd.Run(); err != nil {
|
||||||
|
// exit code != 0 说明不在这个集合里,继续找下一个
|
||||||
if err != nil {
|
continue
|
||||||
if strings.Contains(string(output), "is NOT in set") {
|
|
||||||
continue // 不在该集合中,尝试下一个
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("检测 %s 时出错: %v (%s)", setName, err, output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存在,执行删除
|
// 2. 存在则删除
|
||||||
cmd = exec.Command("ipset", "del", setName, ip)
|
delCmd := exec.Command("ipset", "del", setName, ip)
|
||||||
output, err = cmd.CombinedOutput()
|
if err := delCmd.Run(); err != nil {
|
||||||
if err != nil {
|
return "", fmt.Errorf("从 %s 删除 %s 失败: %v", setName, ip, err)
|
||||||
return "", fmt.Errorf("从 %s 删除 %s 时出错: %v (%s)", setName, ip, err, output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return setName, nil // 删除成功,返回集合名
|
return setName, nil // 成功删除
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", nil // 所有集合都没有该 IP,无需删除
|
return "", nil // 未找到
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加 Iptables 规则
|
// iptables_add 添加规则
|
||||||
|
// 修正:使用 -I (Insert) 而不是 -A (Append),确保规则在最前面生效
|
||||||
func iptables_add(setName string) error {
|
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("sh", "-c", fmt.Sprintf("iptables -A INPUT -m set --match-set %s src -j DROP", setName))
|
// 添加规则
|
||||||
|
cmd := exec.Command("iptables", "-I", "INPUT", "-m", "set", "--match-set", setName, "src", "-j", "DROP")
|
||||||
var stdout, stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
cmd.Stdout = &stdout
|
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Printf("cmd.Run() failed with %v, stderr: %s\n", err, stderr.String())
|
log.Printf("添加 iptables 规则失败 [%s]: %v, stderr: %s", setName, err, stderr.String())
|
||||||
//err = fmt.Errorf("failed to execute command: %w, stderr: %s", err, stderr.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除 Iptables 规则
|
// iptables_del 删除规则
|
||||||
func iptables_del(setName string) error {
|
func iptables_del(setName string) error {
|
||||||
|
// 循环删除,防止有重复规则
|
||||||
cmd := exec.Command("sh", "-c", fmt.Sprintf("iptables -D INPUT -m set --match-set %s src -j DROP", setName))
|
for {
|
||||||
|
cmd := exec.Command("iptables", "-D", "INPUT", "-m", "set", "--match-set", setName, "src", "-j", "DROP")
|
||||||
var stdout, stderr bytes.Buffer
|
if err := cmd.Run(); err != nil {
|
||||||
cmd.Stdout = &stdout
|
// 删除失败通常意味着规则不存在了,跳出循环
|
||||||
cmd.Stderr = &stderr
|
break
|
||||||
|
}
|
||||||
err := cmd.Run()
|
}
|
||||||
if err != nil {
|
return nil
|
||||||
//log.Printf("cmd.Run() failed with %v, stderr: %s\n", err, stderr.String())
|
|
||||||
//err = fmt.Errorf("failed to execute command: %w, stderr: %s", err, stderr.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
// iptables_list 打印规则
|
||||||
}
|
|
||||||
|
|
||||||
// 打印 Iptables 规则
|
|
||||||
func iptables_list() error {
|
func iptables_list() error {
|
||||||
cmd := exec.Command("sh", "-c", "iptables -L -v -n --line-numbers")
|
cmd := exec.Command("iptables", "-L", "INPUT", "-v", "-n", "--line-numbers")
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
var stdout, stderr bytes.Buffer
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Stdout = &stdout
|
return cmd.Run()
|
||||||
cmd.Stderr = &stderr
|
|
||||||
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("cmd.Run() failed with %v, stderr: %s\n", err, stderr.String())
|
|
||||||
err = fmt.Errorf("failed to execute command: %w, stderr: %s", err, stderr.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Print(stdout.String())
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
662
main.go
662
main.go
@@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -13,11 +12,12 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var BuildDate = "unknown" // 由编译时注入
|
var BuildDate = "unknown"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// 强制使用 Go 的纯用户态 DNS 解析器
|
// 强制使用 Go 的纯用户态 DNS 解析器
|
||||||
@@ -28,34 +28,311 @@ func init() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
daemon = flag.Bool("d", false, "守护进程模式")
|
daemon = flag.Bool("d", false, "守护进程模式")
|
||||||
child = flag.Bool("child", false, "子进程模式, (不要使用!!!)")
|
child = flag.Bool("child", false, "子进程模式")
|
||||||
)
|
)
|
||||||
|
|
||||||
// 全局变量
|
// 全局配置变量
|
||||||
var (
|
var (
|
||||||
InterfacesList bool // 是否列出网络接口
|
InterfacesList bool
|
||||||
InterfaceName *string // 网络接口名称
|
InterfaceName *string
|
||||||
PcapFile *string // 输出文件名
|
PcapFile *string
|
||||||
Protocol *string // BPF 过滤器
|
Protocol *string
|
||||||
|
|
||||||
IPSET_NUMBER int // 当前使用的 ipset 集合编号
|
IPSET_NUMBER int
|
||||||
MAX_IPSET_NAME = 100 // 最大 ipset 集合数量
|
MAX_IPSET_NAME = 100
|
||||||
IPSET_NAME string // 当前使用的 ipset 集合名称
|
IPSET_NAME string
|
||||||
|
|
||||||
IpList = list.New() // 存储 IPv4 地址的链表
|
// --- 优化部分开始 ---
|
||||||
IpMutex sync.Mutex // 保护 ipList 的互斥锁
|
|
||||||
|
|
||||||
ProcessedIPMap = map[string]time.Time{} // 使用 map 存储已处理的 IP
|
// 替换 List 为带缓冲的 Channel
|
||||||
ProcessedMutex sync.Mutex // 互斥锁保护 ProcessedIPMap
|
// 容量设为 5000,足以应对大多数突发流量
|
||||||
|
IpChannel = make(chan string, 5000)
|
||||||
|
|
||||||
|
// 用于入队去重的 Map,防止同一个 IP 在处理中时重复入队
|
||||||
|
// key: IP string, value: struct{}
|
||||||
|
PendingIPs sync.Map
|
||||||
|
PendingCount int64
|
||||||
|
|
||||||
|
// --- 优化部分结束 ---
|
||||||
|
|
||||||
|
ProcessedIPMap = map[string]time.Time{}
|
||||||
|
ProcessedMutex sync.Mutex
|
||||||
|
|
||||||
local_ipv4_addr string
|
local_ipv4_addr string
|
||||||
)
|
)
|
||||||
|
|
||||||
// 启动子进程
|
// --- 核心逻辑优化:生产者 (入队) ---
|
||||||
|
// PushIPToQueue 安全地将 IP 放入队列
|
||||||
|
// 该函数应由 startPacketCapture 调用
|
||||||
|
func PushIPToQueue(ipStr string) {
|
||||||
|
// 0. 基础校验
|
||||||
|
if ipStr == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 快速检查:如果已经在 ProcessedIPMap (已知的国内IP或白名单),直接忽略
|
||||||
|
ProcessedMutex.Lock()
|
||||||
|
_, processed := ProcessedIPMap[ipStr]
|
||||||
|
ProcessedMutex.Unlock()
|
||||||
|
if processed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 去重检查:如果已经在 PendingIPs (队列中或正在处理),跳过
|
||||||
|
// LoadOrStore: 如果 key 存在,返回 true;否则写入并返回 false
|
||||||
|
if _, loaded := PendingIPs.LoadOrStore(ipStr, struct{}{}); loaded {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 【计数增加】只有确定要入队时才增加
|
||||||
|
atomic.AddInt64(&PendingCount, 1)
|
||||||
|
|
||||||
|
// 3. 非阻塞入队
|
||||||
|
select {
|
||||||
|
case IpChannel <- ipStr:
|
||||||
|
// 成功入队
|
||||||
|
default:
|
||||||
|
// 队列已满,丢弃该包
|
||||||
|
// 【回滚状态】从 Map 删除,并减少计数
|
||||||
|
PendingIPs.Delete(ipStr)
|
||||||
|
atomic.AddInt64(&PendingCount, -1)
|
||||||
|
// 可选:log.Println("警告:处理队列已满,丢弃 IP:", ipStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 核心逻辑优化:消费者 (处理 IP) ---
|
||||||
|
func processIP(ipStr string) {
|
||||||
|
// PushIPToQueue 已经拦截了空字符串,这里其实不需要再判断
|
||||||
|
// 但为了代码健壮性,如果必须判断,defer 必须在 return 之前定义
|
||||||
|
|
||||||
|
// 确保函数结束时从 Pending 状态移除并减少计数
|
||||||
|
defer func() {
|
||||||
|
PendingIPs.Delete(ipStr)
|
||||||
|
atomic.AddInt64(&PendingCount, -1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if ipStr == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再次检查 ProcessedIPMap (防止排队期间被其他协程处理了)
|
||||||
|
ProcessedMutex.Lock()
|
||||||
|
_, processed := ProcessedIPMap[ipStr]
|
||||||
|
ProcessedMutex.Unlock()
|
||||||
|
if processed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查白名单
|
||||||
|
// --- 修改开始:使用读写锁检查白名单 ---
|
||||||
|
whiteListLock.RLock() // 加读锁
|
||||||
|
_, isWhitelisted := whiteList[ipStr] // 检查是否存在
|
||||||
|
whiteListLock.RUnlock() // 解读锁
|
||||||
|
|
||||||
|
if isWhitelisted {
|
||||||
|
log.Printf("\033[33m %s 在白名单中, 跳过 \033[0m\n", ipStr)
|
||||||
|
// 尝试从 ipset 移除
|
||||||
|
RemoveIPIfInSets("root", MAX_IPSET_NAME, ipStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// --- 修改结束 ---
|
||||||
|
|
||||||
|
REGION := "中国 内网"
|
||||||
|
|
||||||
|
// 如果 IP 已经在 ipset 中,通常无需处理
|
||||||
|
if Is_Ip_Ipset(ipStr) == 1 {
|
||||||
|
//log.Printf("\033[31m %s 已在 ipset 集合中 \033[0m\n", ipStr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 离线库判断 (快速)
|
||||||
|
region, _ := ip2region(ipStr)
|
||||||
|
|
||||||
|
// 如果不包含 "中国" 也不包含 "内网",则判定为疑似国外
|
||||||
|
if !ContainsPart(region, REGION) {
|
||||||
|
log.Printf("\033[33m [%s %s] 离线库为国外, 进一步API判断\033[0m\n", ipStr, region)
|
||||||
|
|
||||||
|
// 2. 在线 API 判断 (慢速)
|
||||||
|
position, err := curl_(ipStr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取IP地域出错: %v", err)
|
||||||
|
return // API 失败暂时跳过,等待下次重试
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("\033[31m [%s %s]\033[0m\n", ipStr, position)
|
||||||
|
|
||||||
|
if !ContainsPart(position, REGION) {
|
||||||
|
// --- 确认为国外 ---
|
||||||
|
AddIPSet(IPSET_NAME, ipStr)
|
||||||
|
log.Printf("\033[31m [封禁] 已添加国外 IP: %s \033[0m\n", ipStr)
|
||||||
|
} else {
|
||||||
|
// --- 确认为国内 ---
|
||||||
|
log.Printf("\033[32m %s API 修正为国内, 标记放行\033[0m\n", ipStr)
|
||||||
|
ProcessedMutex.Lock()
|
||||||
|
ProcessedIPMap[ipStr] = time.Now()
|
||||||
|
ProcessedMutex.Unlock()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 离线库确认为国内,标记放行
|
||||||
|
ProcessedMutex.Lock()
|
||||||
|
ProcessedIPMap[ipStr] = time.Now()
|
||||||
|
ProcessedMutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunMainProcess() {
|
||||||
|
log.Println(" 主进程启动...")
|
||||||
|
|
||||||
|
WriteLocalAddr()
|
||||||
|
|
||||||
|
cmd, err := StartChildProcess()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("子进程启动失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
IPSET_NUMBER = 0
|
||||||
|
IPSET_NAME = fmt.Sprintf("root%d", IPSET_NUMBER)
|
||||||
|
if Is_Name_Ipset(IPSET_NAME) == 0 { // 假设 0 表示不存在
|
||||||
|
createIPSet(IPSET_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 启动抓包 (生产者)
|
||||||
|
go startPacketCapture()
|
||||||
|
|
||||||
|
// 2. 启动 Worker Pool (消费者)
|
||||||
|
// 根据机器性能调整 worker 数量,建议 20-50
|
||||||
|
numWorkers := 40
|
||||||
|
log.Printf(" 启动 %d 个并发 Worker 处理 IP...", numWorkers)
|
||||||
|
for i := 0; i < numWorkers; i++ {
|
||||||
|
go func(id int) {
|
||||||
|
for ip := range IpChannel {
|
||||||
|
processIP(ip)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定期保存 Map 数据 (替代原来在循环里保存)
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
|
for range ticker.C {
|
||||||
|
if err := saveMapToFile("cn.json"); err != nil {
|
||||||
|
log.Printf(" 自动保存 Map 失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 过期清理 ProcessedIPMap
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
|
for range ticker.C {
|
||||||
|
now := time.Now()
|
||||||
|
ProcessedMutex.Lock()
|
||||||
|
count := 0
|
||||||
|
for ip, t := range ProcessedIPMap {
|
||||||
|
if t.Year() == 1971 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if now.Sub(t) > 30*time.Minute {
|
||||||
|
delete(ProcessedIPMap, ip)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProcessedMutex.Unlock()
|
||||||
|
if count > 0 {
|
||||||
|
log.Printf(" 已清理 %d 个过期 ProcessedIPMap 项", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 白名单刷新
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(10 * time.Minute)
|
||||||
|
if err := LoadWhiteList("whitelist.txt"); err != nil {
|
||||||
|
log.Printf(" 刷新白名单失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 防火墙扩容管理
|
||||||
|
// 防火墙扩容管理
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// 1. 获取当前集合长度,必须处理错误
|
||||||
|
ipset_len, err := NumIPSet(IPSET_NAME)
|
||||||
|
if err != nil {
|
||||||
|
// 如果是因为集合不存在导致的错误,尝试创建它
|
||||||
|
if Is_Name_Ipset(IPSET_NAME) != 0 {
|
||||||
|
log.Printf("检测到集合 %s 不存在,正在初始化...", IPSET_NAME)
|
||||||
|
createIPSet(IPSET_NAME)
|
||||||
|
iptables_add(IPSET_NAME) // 重点:创建后必须同步添加 iptables 规则
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否需要扩容
|
||||||
|
// 注意:ipset 默认 maxelem 是 65536,达到这个值 add 操作就会失败
|
||||||
|
if ipset_len >= 65530 {
|
||||||
|
log.Printf("\033[31m ipset %s 列表已满 %d,准备扩容... \033[0m\n", IPSET_NAME, ipset_len)
|
||||||
|
|
||||||
|
IPSET_NUMBER++
|
||||||
|
if IPSET_NUMBER >= MAX_IPSET_NAME {
|
||||||
|
log.Printf("\033[31m 警告:已达到最大集合数量限制!!! \033[0m\n")
|
||||||
|
// 这里可以根据需求决定是否 return 或采取其他措施
|
||||||
|
}
|
||||||
|
|
||||||
|
newSetName := fmt.Sprintf("root%d", IPSET_NUMBER)
|
||||||
|
|
||||||
|
// 3. 只有不存在时才创建 (注意判断逻辑: != 0 表示不存在)
|
||||||
|
if Is_Name_Ipset(newSetName) != 0 {
|
||||||
|
log.Printf("\033[32m 正在创建并应用新集合: %s \033[0m\n", newSetName)
|
||||||
|
if err := createIPSet(newSetName); err == nil {
|
||||||
|
// 4. 关键:创建新集合后,必须立刻将其加入 iptables 拦截规则
|
||||||
|
iptables_add(newSetName)
|
||||||
|
// 5. 切换全局变量
|
||||||
|
IPSET_NAME = newSetName
|
||||||
|
} else {
|
||||||
|
log.Printf("创建集合失败: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果集合已经存在(可能是上次运行留下的),直接切换过去
|
||||||
|
IPSET_NAME = newSetName
|
||||||
|
iptables_add(newSetName) // 确保规则存在
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 打印日志
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(7 * time.Second)
|
||||||
|
|
||||||
|
// 获取 ipset 数量
|
||||||
|
ipset_len, _ := NumIPSet(IPSET_NAME)
|
||||||
|
|
||||||
|
// 安全地获取 ProcessedIPMap 的长度(不要打印内容,不要在无锁状态下读取!)
|
||||||
|
ProcessedMutex.Lock()
|
||||||
|
processedLen := len(ProcessedIPMap)
|
||||||
|
ProcessedMutex.Unlock()
|
||||||
|
|
||||||
|
// 获取 Pending 数量
|
||||||
|
pendingCount := atomic.LoadInt64(&PendingCount)
|
||||||
|
|
||||||
|
log.Printf("\033[32m [状态监控] IPSet(%s): %d | 已处理缓存: %d | 待处理积压: %d \033[0m\n",
|
||||||
|
IPSET_NAME, ipset_len, processedLen, pendingCount)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
waitForSignalAndCleanUp(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
func StartChildProcess() (*exec.Cmd, error) {
|
func StartChildProcess() (*exec.Cmd, error) {
|
||||||
args := []string{}
|
args := []string{}
|
||||||
for _, arg := range os.Args[1:] {
|
for _, arg := range os.Args[1:] {
|
||||||
if !strings.HasPrefix(arg, "-child") { // 只过滤 -child 标志
|
if !strings.HasPrefix(arg, "-child") {
|
||||||
args = append(args, arg)
|
args = append(args, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,374 +348,88 @@ func StartChildProcess() (*exec.Cmd, error) {
|
|||||||
return cmd, nil
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停止子进程
|
|
||||||
func StopChildProcess(cmd *exec.Cmd) error {
|
func StopChildProcess(cmd *exec.Cmd) error {
|
||||||
if cmd == nil || cmd.Process == nil {
|
if cmd == nil || cmd.Process == nil {
|
||||||
return fmt.Errorf("子进程无效")
|
return fmt.Errorf("子进程无效")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试优雅地停止子进程
|
|
||||||
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
||||||
return fmt.Errorf("发送 SIGTERM 信号失败: %w", err)
|
return fmt.Errorf("SIGTERM 失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() {
|
go func() { done <- cmd.Wait() }()
|
||||||
done <- cmd.Wait()
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err := <-done:
|
case err := <-done:
|
||||||
if err != nil {
|
return err
|
||||||
return fmt.Errorf("等待子进程退出时出错: %w", err)
|
case <-time.After(2 * time.Second):
|
||||||
|
_ = cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
case <-time.After(5 * time.Second): // 超时时间调整为5秒
|
|
||||||
fmt.Println("子进程未在规定时间内退出,尝试强制终止...")
|
|
||||||
if err := cmd.Process.Kill(); err != nil {
|
|
||||||
return fmt.Errorf("强制终止子进程失败: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("子进程已停止, PID: %d\n", cmd.Process.Pid)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待信号并优雅退出
|
|
||||||
func waitForSignalAndCleanUp(cmd *exec.Cmd) {
|
func waitForSignalAndCleanUp(cmd *exec.Cmd) {
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||||
|
sig := <-sigChan
|
||||||
for sig := range sigChan {
|
|
||||||
fmt.Printf("主进程收到信号: %v\n", sig)
|
fmt.Printf("主进程收到信号: %v\n", sig)
|
||||||
if cmd != nil && cmd.Process != nil {
|
if cmd != nil && cmd.Process != nil {
|
||||||
_ = cmd.Process.Signal(sig) // 转发信号到子进程
|
_ = cmd.Process.Signal(sig)
|
||||||
}
|
|
||||||
if sig == syscall.SIGINT || sig == syscall.SIGTERM {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
StopChildProcess(cmd)
|
||||||
|
saveMapToFile("cn.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := StopChildProcess(cmd); err != nil {
|
|
||||||
log.Fatalf("清理子进程时遇到错误: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Println("主进程退出")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 守护进程模式
|
|
||||||
func StartDaemon() {
|
func StartDaemon() {
|
||||||
// 创建一个新的实例并让当前进程退出。注意这并不是守护进程的标准实现。
|
|
||||||
args := []string{}
|
args := []string{}
|
||||||
for _, arg := range os.Args[1:] {
|
for _, arg := range os.Args[1:] {
|
||||||
if !strings.HasPrefix(arg, "-d") && !strings.HasPrefix(arg, "-child") { // 过滤掉 -d 和 -child 标志
|
if !strings.HasPrefix(arg, "-d") && !strings.HasPrefix(arg, "-child") {
|
||||||
args = append(args, arg)
|
args = append(args, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args = append(args, "-d=false", "-child=false")
|
args = append(args, "-d=false", "-child=false")
|
||||||
|
|
||||||
cmd := exec.Command(os.Args[0], args...)
|
cmd := exec.Command(os.Args[0], args...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Start()
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
log.Fatalf("无法启动新实例: %v", err)
|
|
||||||
}
|
|
||||||
fmt.Println("新实例已启动,当前进程将退出")
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 子进程逻辑
|
|
||||||
func RunChildProcess() {
|
func RunChildProcess() {
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-sigChan
|
||||||
ticker := time.NewTicker(3 * time.Second)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case sig := <-sigChan:
|
|
||||||
fmt.Printf("子进程收到信号: %v, 准备退出...\n", sig)
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存ProcessedIPMap到文件
|
|
||||||
func saveMapToFile(filePath string) error {
|
func saveMapToFile(filePath string) error {
|
||||||
ProcessedMutex.Lock()
|
ProcessedMutex.Lock()
|
||||||
defer ProcessedMutex.Unlock()
|
defer ProcessedMutex.Unlock()
|
||||||
|
|
||||||
file, err := os.Create(filePath)
|
file, err := os.Create(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("创建文件失败: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
return json.NewEncoder(file).Encode(ProcessedIPMap)
|
||||||
encoder := json.NewEncoder(file)
|
|
||||||
if err := encoder.Encode(ProcessedIPMap); err != nil {
|
|
||||||
return fmt.Errorf("编码 ProcessedIPMap 失败: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//log.Println("ProcessedIPMap 已成功保存到文件")
|
func loadFromFile(filePath string) error {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadFromFile(filePath string, logMessage string) error {
|
|
||||||
ProcessedMutex.Lock()
|
ProcessedMutex.Lock()
|
||||||
defer ProcessedMutex.Unlock()
|
defer ProcessedMutex.Unlock()
|
||||||
|
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
|
||||||
log.Println("文件不存在,跳过加载 Map")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("打开文件失败: %w", err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
return json.NewDecoder(file).Decode(&ProcessedIPMap)
|
||||||
// 尝试用新格式解码
|
|
||||||
decoder := json.NewDecoder(file)
|
|
||||||
temp := make(map[string]time.Time)
|
|
||||||
if err := decoder.Decode(&temp); err == nil {
|
|
||||||
ProcessedIPMap = temp
|
|
||||||
log.Println(logMessage)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果失败,尝试旧格式
|
|
||||||
file.Seek(0, 0) // 重置读取位置
|
|
||||||
decoder = json.NewDecoder(file)
|
|
||||||
oldTemp := make(map[string]string)
|
|
||||||
if err := decoder.Decode(&oldTemp); err == nil {
|
|
||||||
for ip := range oldTemp {
|
|
||||||
//ProcessedIPMap[ip] = time.Now() // 给旧 IP 打个当前时间戳
|
|
||||||
ProcessedIPMap[ip] = time.Date(1971, 1, 1, 0, 0, 0, 0, time.UTC) // 标记为永不过期
|
|
||||||
}
|
|
||||||
log.Println(logMessage + "(从旧格式转换)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("解码 Map 失败: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitMap() {
|
func InitMap() {
|
||||||
if err := loadFromFile("cn.json", " Map 已成功从文件加载"); err != nil {
|
loadFromFile("cn.json")
|
||||||
log.Fatalf(" 加载 Map 失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
// 程序退出时保存数据
|
|
||||||
if err := saveMapToFile("cn.json"); err != nil {
|
|
||||||
log.Printf(" 保存 Map 失败: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteLocalAddr() {
|
func WriteLocalAddr() {
|
||||||
local_ipv4_addr = GetLocalIpv4Addr() // 本机地址
|
local_ipv4_addr = GetLocalIpv4Addr()
|
||||||
// 将本机外网地址加入到已处理集合中
|
|
||||||
if local_ipv4_addr != "NULL" {
|
if local_ipv4_addr != "NULL" {
|
||||||
//log.Printf("\033[33m %s 本机地址 \033[0m\n", ipStr)
|
|
||||||
ProcessedMutex.Lock()
|
ProcessedMutex.Lock()
|
||||||
//ProcessedIPMap[local_ipv4_addr] = time.Now()
|
ProcessedIPMap[local_ipv4_addr] = time.Date(1971, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
ProcessedIPMap[local_ipv4_addr] = time.Date(1971, 1, 1, 0, 0, 0, 0, time.UTC) // 标记为永不过期
|
|
||||||
ProcessedMutex.Unlock()
|
ProcessedMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入json文件
|
|
||||||
if err := saveMapToFile("cn.json"); err != nil {
|
|
||||||
log.Printf("实时保存 Map 失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunMainProcess() { // 主进程逻辑
|
|
||||||
|
|
||||||
log.Println(" 主进程启动...")
|
|
||||||
|
|
||||||
WriteLocalAddr() // 将本机外网地址加入到已处理集合中
|
|
||||||
|
|
||||||
cmd, err := StartChildProcess()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("子进程启动失败: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
IPSET_NUMBER = 0
|
|
||||||
IPSET_NAME = fmt.Sprintf("root%d", IPSET_NUMBER)
|
|
||||||
if return_value := Is_Name_Ipset(IPSET_NAME); return_value == 1 {
|
|
||||||
createIPSet(IPSET_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动抓包
|
|
||||||
go startPacketCapture()
|
|
||||||
|
|
||||||
// 启动IP地域判断,管理
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
|
|
||||||
IpMutex.Lock() // 锁定互斥锁
|
|
||||||
if IpList.Len() > 0 { // 链表不为空
|
|
||||||
e1 := IpList.Front() // 获取链表第一个元素
|
|
||||||
ipStr := e1.Value.(net.IP).String()
|
|
||||||
region, _ := ip2region(ipStr) // 离线库初步判断地域
|
|
||||||
|
|
||||||
ProcessedMutex.Lock()
|
|
||||||
_, processed := ProcessedIPMap[ipStr] // 检查是否已处理
|
|
||||||
ProcessedMutex.Unlock()
|
|
||||||
|
|
||||||
if processed { // 如果尚未处理
|
|
||||||
log.Printf("\033[33m %s 已经标记为国内,跳过!!! \033[0m\n", ipStr)
|
|
||||||
IpList.Remove(e1)
|
|
||||||
goto next
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否在白名单中
|
|
||||||
if _, ip_ := whiteList[ipStr]; ip_ {
|
|
||||||
log.Printf("\033[33m %s 跳过白名单, 跳过!!! \033[0m\n", ipStr)
|
|
||||||
IpList.Remove(e1)
|
|
||||||
|
|
||||||
setName, err := RemoveIPIfInSets("root", MAX_IPSET_NAME, ipStr)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf(" %s 删除 IP 出错: %v\n", ipStr, err)
|
|
||||||
} else if setName != "" {
|
|
||||||
log.Printf(" %s 已从 %s 中移除 \n", ipStr, setName)
|
|
||||||
} else {
|
|
||||||
log.Printf(" %s 不在任何 IPSet 中,无需移除\n", ipStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
goto next
|
|
||||||
}
|
|
||||||
|
|
||||||
REGION := "中国 内网" // 默认地域
|
|
||||||
|
|
||||||
if Is_Ip_Ipset(ipStr) != 0 { // IP 不在 ipset 集合中
|
|
||||||
|
|
||||||
//if !strings.Contains(region, "中国") && !strings.Contains(region, "内网") { // 离线库判断不在中国内
|
|
||||||
if !ContainsPart(region, REGION) {
|
|
||||||
log.Printf("\033[33m [%s %s] 离线库为国外, 进一步API判断\033[0m\n", ipStr, region)
|
|
||||||
|
|
||||||
if position, err := curl_(ipStr); err != nil { // 使用 API 判断地域
|
|
||||||
log.Printf("获取IP地域出错: %v", err)
|
|
||||||
} else {
|
|
||||||
log.Printf("\033[31m [%s %s]\033[0m\n\n", ipStr, position) // 打印地域
|
|
||||||
|
|
||||||
//if !strings.Contains(position, "中国") && !strings.Contains(position, "内网") { // API 判断为国外
|
|
||||||
if !ContainsPart(region, REGION) {
|
|
||||||
AddIPSet(IPSET_NAME, ipStr) // 添加 IP 到 ipset 集合
|
|
||||||
|
|
||||||
// 钉钉告警,废弃!钉钉可能限制文本长度,和发送次数!
|
|
||||||
// warning_ding(ipStr, position) // 警告 IP 地域
|
|
||||||
} else {
|
|
||||||
log.Printf("\033[33m %s 离线库为国外, API 判断为国内, 标记为已处理\033[0m\n", ipStr)
|
|
||||||
|
|
||||||
// 将 IP 标记为已处理,国内地址
|
|
||||||
ProcessedMutex.Lock()
|
|
||||||
ProcessedIPMap[ipStr] = time.Now()
|
|
||||||
ProcessedMutex.Unlock()
|
|
||||||
|
|
||||||
// 写入json文件
|
|
||||||
if err := saveMapToFile("cn.json"); err != nil {
|
|
||||||
log.Printf(" 实时保存 Map 失败: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else { // IP 已在 ipset 集合中
|
|
||||||
log.Printf("\033[31m %s 在 ipset 集合中 \033[0m\n", ipStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 无论是否已处理,都移除该 IP
|
|
||||||
IpList.Remove(e1)
|
|
||||||
}
|
|
||||||
next:
|
|
||||||
IpMutex.Unlock() // 解锁互斥锁
|
|
||||||
|
|
||||||
// 打印当前info
|
|
||||||
log.Printf(" 当前Ip链表长度:%d, Ipset名:%s, 长:%d, ProcessedIPMap:[%s]当前长度:%d\n", IpList.Len(), IPSET_NAME, func() int { // 打印 当前 Ipset 链长度
|
|
||||||
_len, _err := NumIPSet(IPSET_NAME)
|
|
||||||
if _err == nil {
|
|
||||||
return _len
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}(), ProcessedIPMap, len(ProcessedIPMap))
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second) // 防止高频运行
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 启动一个 goroutine(后台任务),用于定期清理 ProcessedIPMap 中过期的 IP 条目
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(1 * time.Minute)
|
|
||||||
|
|
||||||
// 获取当前时间,用于与 map 中的时间戳进行比较
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
// 加锁,确保并发安全地访问全局变量 ProcessedIPMap
|
|
||||||
ProcessedMutex.Lock()
|
|
||||||
|
|
||||||
// 遍历 ProcessedIPMap,检查每个 IP 的记录是否已超过 24 小时
|
|
||||||
for ip, t := range ProcessedIPMap {
|
|
||||||
|
|
||||||
if t.Year() == 1971 {
|
|
||||||
continue // 不清理标记为“永不过期”的 IP
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果当前时间减去记录时间大于 1 小时,则删除该条目
|
|
||||||
if now.Sub(t) > 30*time.Minute {
|
|
||||||
delete(ProcessedIPMap, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解锁,允许其他 goroutine 访问 ProcessedIPMap
|
|
||||||
ProcessedMutex.Unlock()
|
|
||||||
|
|
||||||
// 打印日志,表示本次清理已完成
|
|
||||||
log.Println(" 已清理过期 ProcessedIPMap 项")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 定时重新加载白名单
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(1 * time.Minute) // 每 10 分钟自动刷新
|
|
||||||
if err := LoadWhiteList("whitelist.txt"); err != nil {
|
|
||||||
log.Printf(" 刷新白名单失败: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 启动防火墙管理
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
if ipset_len, _ := NumIPSet(IPSET_NAME); ipset_len >= 65535 {
|
|
||||||
log.Printf("\033[31m ipset %s 列表已满 %d \033[0m\n", IPSET_NAME, ipset_len)
|
|
||||||
|
|
||||||
// 创建新的 ipset 集合
|
|
||||||
IPSET_NUMBER++
|
|
||||||
|
|
||||||
if IPSET_NUMBER >= MAX_IPSET_NAME {
|
|
||||||
//log.Printf("已创建 %d 个集合!!!", MAX_IPSET_NAME)
|
|
||||||
log.Printf("\033[31m 已创建 %d 个集合!!! \033[0m\n", MAX_IPSET_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
IPSET_NAME = fmt.Sprintf("root%d", IPSET_NUMBER)
|
|
||||||
if return_value := Is_Name_Ipset(IPSET_NAME); return_value == 1 {
|
|
||||||
createIPSet(IPSET_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 等待信号并清理
|
|
||||||
waitForSignalAndCleanUp(cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleCmd() {
|
func HandleCmd() {
|
||||||
@@ -449,7 +440,11 @@ func HandleCmd() {
|
|||||||
flag.BoolVar(&InterfacesList, "l", false, "列出可用的网络接口")
|
flag.BoolVar(&InterfacesList, "l", false, "列出可用的网络接口")
|
||||||
Protocol = flag.String("f", "'tcp' or 'udp' or 'tcp or udp'", "指定 BPF 过滤器")
|
Protocol = flag.String("f", "'tcp' or 'udp' or 'tcp or udp'", "指定 BPF 过滤器")
|
||||||
PcapFile = flag.String("o", "", "保存捕获数据的输出文件(可选)")
|
PcapFile = flag.String("o", "", "保存捕获数据的输出文件(可选)")
|
||||||
flag.StringVar(&instruction, "s", "", "-s start 启动 Iptables 规则\n-s stop 停止 Iptables 规则\n-s list 打印 Iptables 规则\n-s reload 重启 Iptables 规则")
|
flag.StringVar(&instruction, "s", "",
|
||||||
|
"-s start 启动 Iptables 规则\n"+
|
||||||
|
"-s stop 停止 Iptables 规则\n"+
|
||||||
|
"-s list 打印 Iptables 规则\n"+
|
||||||
|
"-s reload 重启 Iptables 规则")
|
||||||
flag.BoolVar(&help, "h", false, "")
|
flag.BoolVar(&help, "h", false, "")
|
||||||
flag.BoolVar(&help, "help", false, "帮助信息")
|
flag.BoolVar(&help, "help", false, "帮助信息")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -519,34 +514,21 @@ func HandleCmd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU()) // 设置最大CPU核数
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
HandleCmd()
|
HandleCmd()
|
||||||
|
|
||||||
CheckCommandExists("iptables")
|
CheckCommandExists("iptables")
|
||||||
CheckCommandExists("ipset")
|
CheckCommandExists("ipset")
|
||||||
|
|
||||||
embed_ip2region()
|
embed_ip2region()
|
||||||
|
|
||||||
// 加载白名单
|
|
||||||
err := LoadWhiteList("whitelist.txt")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf(" whiteList Map 加载白名单失败: %v", err)
|
|
||||||
} else {
|
|
||||||
log.Println(" whiteList Map 白名单加载成功")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 守护进程模式
|
|
||||||
if *daemon {
|
if *daemon {
|
||||||
StartDaemon()
|
StartDaemon()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 子进程逻辑
|
|
||||||
if *child {
|
if *child {
|
||||||
RunChildProcess() // 子进程逻辑
|
RunChildProcess()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
InitMap()
|
InitMap()
|
||||||
RunMainProcess() // 主进程逻辑
|
RunMainProcess()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user