mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-09-19 09:32:29 +08:00
+101
-168
@@ -9,8 +9,6 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/simplifiedchinese"
|
||||
)
|
||||
|
||||
type Charset string
|
||||
@@ -20,29 +18,26 @@ const (
|
||||
GB18030 = Charset("GB18030")
|
||||
)
|
||||
|
||||
const ACTION_ALLOW string = "allow" //,allow 表示允许连接,block 表示阻止连接,bypass 表示只允许安全连接。 =
|
||||
const ACTION_ALLOW string = "allow"
|
||||
const ACTION_BLOCK string = "block"
|
||||
const ACTION_BYPASS string = "bypass"
|
||||
|
||||
const (
|
||||
RULE_PREFIX = "SamWAF_Block_" // 规则名称前缀
|
||||
PROTOCOL_ANY = "any" // 任意协议
|
||||
PROTOCOL_TCP = "TCP" // TCP 协议
|
||||
PROTOCOL_UDP = "UDP" // UDP 协议
|
||||
DIRECTION_IN = "in" // 入站
|
||||
DIRECTION_OUT = "out" // 出站
|
||||
DIRECTION_BOTH = "both" // 双向
|
||||
PROTOCOL_ANY = "any" // 任意协议
|
||||
PROTOCOL_TCP = "TCP" // TCP 协议
|
||||
PROTOCOL_UDP = "UDP" // UDP 协议
|
||||
DIRECTION_IN = "in" // 入站
|
||||
DIRECTION_OUT = "out" // 出站
|
||||
DIRECTION_BOTH = "both" // 双向
|
||||
)
|
||||
|
||||
type FireWallEngine struct {
|
||||
}
|
||||
type FireWallEngine struct{}
|
||||
|
||||
// IPBlockInfo IP封禁信息结构
|
||||
type IPBlockInfo struct {
|
||||
IP string // IP地址
|
||||
RuleName string // 规则名称
|
||||
Reason string // 封禁原因(预留字段,实际存储在数据库)
|
||||
BlockTime time.Time // 封禁时间(预留字段,实际存储在数据库)
|
||||
Reason string // 封禁原因
|
||||
BlockTime time.Time // 封禁时间
|
||||
Protocol string // 协议类型
|
||||
Direction string // 方向
|
||||
}
|
||||
@@ -58,161 +53,136 @@ func (fw *FireWallEngine) IsFirewallEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (fw *FireWallEngine) executeCommand(cmd *exec.Cmd) (error error, printstr string) {
|
||||
func (fw *FireWallEngine) executeCommand(cmd *exec.Cmd) (error, string) {
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err, err.Error()
|
||||
}
|
||||
cmd.Start()
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err, err.Error()
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
fmt.Println(err)
|
||||
return err, err.Error()
|
||||
}
|
||||
var printstr string
|
||||
in := bufio.NewScanner(stdout)
|
||||
printstr = ""
|
||||
for in.Scan() {
|
||||
cmdRe := ConvertByte2String(in.Bytes(), "GB18030")
|
||||
//fmt.Println(cmdRe)
|
||||
printstr += cmdRe
|
||||
printstr += in.Text() + "\n"
|
||||
}
|
||||
errScanner := bufio.NewScanner(stderr)
|
||||
for errScanner.Scan() {
|
||||
printstr += errScanner.Text() + "\n"
|
||||
}
|
||||
waitErr := cmd.Wait()
|
||||
if waitErr != nil {
|
||||
return waitErr, printstr
|
||||
}
|
||||
cmd.Wait()
|
||||
return nil, printstr
|
||||
}
|
||||
|
||||
func (fw *FireWallEngine) AddRule(ruleName, ipToAdd, action, proc, localport string) error {
|
||||
// iptables -I INPUT 1 -s <ip> -j DROP
|
||||
// 使用 -I 插入到链的开头,确保规则优先执行
|
||||
fmt.Printf("[DEBUG] 添加防火墙规则: ip=%s\n", ipToAdd)
|
||||
|
||||
cmd := exec.Command("iptables", "-I", "INPUT", "1", "-s", ipToAdd, "-j", "DROP")
|
||||
fmt.Printf("[DEBUG] 执行命令: iptables -I INPUT 1 -s %s -j DROP\n", ipToAdd)
|
||||
|
||||
err, output := fw.executeCommand(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] 添加规则失败: %v, 输出: %s\n", err, output)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] 添加规则成功, 输出: %s\n", output)
|
||||
return err
|
||||
}
|
||||
|
||||
func (fw *FireWallEngine) EditRule(ruleNum int, newRule string) error {
|
||||
return fmt.Errorf("editRule is not supported on Windows")
|
||||
}
|
||||
|
||||
func (fw *FireWallEngine) DeleteRule(ruleName string) (bool, error) {
|
||||
// iptables -D INPUT -s <ip> -j DROP
|
||||
// 从规则名中提取IP
|
||||
fmt.Printf("[DEBUG] 删除防火墙规则: name=%s\n", ruleName)
|
||||
|
||||
ip := extractIPFromRuleName(ruleName)
|
||||
if ip == "" {
|
||||
fmt.Printf("[ERROR] 无效的规则名: %s\n", ruleName)
|
||||
return false, fmt.Errorf("invalid rule name: %s", ruleName)
|
||||
}
|
||||
|
||||
cmd := exec.Command("iptables", "-D", "INPUT", "-s", ip, "-j", "DROP")
|
||||
fmt.Printf("[DEBUG] 执行命令: iptables -D INPUT -s %s -j DROP\n", ip)
|
||||
|
||||
err, output := fw.executeCommand(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] 删除规则失败: %v, 输出: %s\n", err, output)
|
||||
return false, fmt.Errorf("failed to delete rule: %s, output: %s", err, output)
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] 删除规则成功\n")
|
||||
return true, nil
|
||||
}
|
||||
func (fw *FireWallEngine) IsRuleExists(ruleName string) (bool, error) {
|
||||
// 从规则名中提取IP
|
||||
fmt.Printf("[DEBUG] 检查规则是否存在: name=%s\n", ruleName)
|
||||
|
||||
ip := extractIPFromRuleName(ruleName)
|
||||
if ip == "" {
|
||||
fmt.Printf("[ERROR] 无效的规则名: %s\n", ruleName)
|
||||
return false, fmt.Errorf("invalid rule name: %s", ruleName)
|
||||
}
|
||||
|
||||
// isIPInRules 直接用原始 IP 检查 iptables 中是否存在 DROP 规则,支持单 IP 和 CIDR
|
||||
func (fw *FireWallEngine) isIPInRules(ip string) (bool, error) {
|
||||
cmd := exec.Command("iptables-save")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] 获取iptables规则失败: %v\n", err)
|
||||
return false, fmt.Errorf("failed to list iptables rules: %s, output: %s", err, string(output))
|
||||
return false, fmt.Errorf("failed to list iptables rules: %v, output: %s", err, string(output))
|
||||
}
|
||||
|
||||
// 查找DROP规则
|
||||
exists := strings.Contains(string(output), "-A INPUT -s "+ip+" -j DROP") ||
|
||||
strings.Contains(string(output), "-A INPUT -s "+ip+"/32 -j DROP")
|
||||
|
||||
if exists {
|
||||
fmt.Printf("[DEBUG] 规则存在: %s (IP: %s)\n", ruleName, ip)
|
||||
} else {
|
||||
fmt.Printf("[DEBUG] 规则不存在: %s (IP: %s)\n", ruleName, ip)
|
||||
}
|
||||
|
||||
outputStr := string(output)
|
||||
// 单 IP 在 iptables-save 中会带 /32 后缀,CIDR 原样存储
|
||||
exists := strings.Contains(outputStr, "-A INPUT -s "+ip+" -j DROP") ||
|
||||
strings.Contains(outputStr, "-A INPUT -s "+ip+"/32 -j DROP")
|
||||
return exists, nil
|
||||
}
|
||||
func ConvertByte2String(byte []byte, charset Charset) string {
|
||||
var str string
|
||||
switch charset {
|
||||
case GB18030:
|
||||
var decodeBytes, _ = simplifiedchinese.GB18030.NewDecoder().Bytes(byte)
|
||||
str = string(decodeBytes)
|
||||
case UTF8:
|
||||
fallthrough
|
||||
default:
|
||||
str = string(byte)
|
||||
|
||||
// AddRule Linux 下忽略规则名,直接用 ipToAdd 执行 iptables 封禁
|
||||
func (fw *FireWallEngine) AddRule(ruleName, ipToAdd, action, proc, localport string) error {
|
||||
fmt.Printf("[DEBUG] 添加防火墙规则: ip=%s\n", ipToAdd)
|
||||
cmd := exec.Command("iptables", "-I", "INPUT", "1", "-s", ipToAdd, "-j", "DROP")
|
||||
fmt.Printf("[DEBUG] 执行命令: iptables -I INPUT 1 -s %s -j DROP\n", ipToAdd)
|
||||
err, output := fw.executeCommand(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] 添加规则失败: %v, 输出: %s\n", err, output)
|
||||
return fmt.Errorf("failed to add rule for %s: %v, output: %s", ipToAdd, err, output)
|
||||
}
|
||||
return str
|
||||
fmt.Printf("[DEBUG] 添加规则成功, 输出: %s\n", output)
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlockIP 封禁指定IP地址
|
||||
// ip: 要封禁的IP地址,支持单个IP或CIDR格式
|
||||
// reason: 封禁原因(可选,后续会存储到数据库)
|
||||
func (fw *FireWallEngine) EditRule(ruleNum int, newRule string) error {
|
||||
return fmt.Errorf("editRule is not supported on Linux")
|
||||
}
|
||||
|
||||
// DeleteRule Linux 下直接将 ruleName 当作 IP 执行 iptables 删除
|
||||
// 生产代码不直接调用此方法(通过 UnblockIP),此方法保留供测试使用
|
||||
func (fw *FireWallEngine) DeleteRule(ruleName string) (bool, error) {
|
||||
fmt.Printf("[DEBUG] 删除防火墙规则: ip=%s\n", ruleName)
|
||||
cmd := exec.Command("iptables", "-D", "INPUT", "-s", ruleName, "-j", "DROP")
|
||||
fmt.Printf("[DEBUG] 执行命令: iptables -D INPUT -s %s -j DROP\n", ruleName)
|
||||
err, output := fw.executeCommand(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] 删除规则失败: %v, 输出: %s\n", err, output)
|
||||
return false, fmt.Errorf("failed to delete rule for %s: %v, output: %s", ruleName, err, output)
|
||||
}
|
||||
fmt.Printf("[DEBUG] 删除规则成功\n")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IsRuleExists Linux 下直接将 ruleName 当作 IP 检查 iptables 规则
|
||||
// 生产代码不直接调用此方法,此方法保留供测试使用
|
||||
func (fw *FireWallEngine) IsRuleExists(ruleName string) (bool, error) {
|
||||
fmt.Printf("[DEBUG] 检查规则是否存在: ip=%s\n", ruleName)
|
||||
return fw.isIPInRules(ruleName)
|
||||
}
|
||||
|
||||
// BlockIP 封禁指定IP地址,支持单个IP或CIDR格式
|
||||
func (fw *FireWallEngine) BlockIP(ip string, reason string) error {
|
||||
fmt.Printf("[INFO] 开始封禁IP: %s, 原因: %s\n", ip, reason)
|
||||
|
||||
// 生成规则名称
|
||||
ruleName := generateRuleName(ip)
|
||||
fmt.Printf("[DEBUG] 生成规则名称: %s\n", ruleName)
|
||||
|
||||
// 检查规则是否已存在
|
||||
exists, _ := fw.IsRuleExists(ruleName)
|
||||
exists, err := fw.isIPInRules(ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查IP状态失败: %v", err)
|
||||
}
|
||||
if exists {
|
||||
fmt.Printf("[WARN] IP %s 已经被封禁\n", ip)
|
||||
return fmt.Errorf("IP %s already blocked", ip)
|
||||
}
|
||||
|
||||
// 添加iptables规则: iptables -I INPUT 1 -s <ip> -j DROP
|
||||
// 使用 -I 插入到链的开头,确保规则在 ESTABLISHED 连接规则之前执行
|
||||
cmd := exec.Command("iptables", "-I", "INPUT", "1", "-s", ip, "-j", "DROP")
|
||||
err, output := fw.executeCommand(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] 封禁IP失败: %s, error: %v, output: %s\n", ip, err, output)
|
||||
return fmt.Errorf("failed to block IP %s: %v, output: %s", ip, err, output)
|
||||
fmt.Printf("[DEBUG] 执行命令: iptables -I INPUT 1 -s %s -j DROP\n", ip)
|
||||
execErr, output := fw.executeCommand(cmd)
|
||||
if execErr != nil {
|
||||
fmt.Printf("[ERROR] 封禁IP失败: %s, error: %v, output: %s\n", ip, execErr, output)
|
||||
return fmt.Errorf("failed to block IP %s: %v, output: %s", ip, execErr, output)
|
||||
}
|
||||
|
||||
fmt.Printf("[INFO] 成功封禁IP: %s\n", ip)
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnblockIP 解除对指定IP的封禁
|
||||
// UnblockIP 解除对指定IP的封禁,支持单个IP或CIDR格式
|
||||
func (fw *FireWallEngine) UnblockIP(ip string) error {
|
||||
fmt.Printf("[INFO] 开始解除IP封禁: %s\n", ip)
|
||||
|
||||
ruleName := generateRuleName(ip)
|
||||
|
||||
// 检查规则是否存在
|
||||
exists, _ := fw.IsRuleExists(ruleName)
|
||||
exists, err := fw.isIPInRules(ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查IP状态失败: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
fmt.Printf("[WARN] IP %s 未被封禁\n", ip)
|
||||
return fmt.Errorf("IP %s is not blocked", ip)
|
||||
}
|
||||
|
||||
// 删除iptables规则: iptables -D INPUT -s <ip> -j DROP
|
||||
cmd := exec.Command("iptables", "-D", "INPUT", "-s", ip, "-j", "DROP")
|
||||
err, output := fw.executeCommand(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] 解除IP封禁失败: %s, error: %v, output: %s\n", ip, err, output)
|
||||
return fmt.Errorf("failed to unblock IP %s: %v, output: %s", ip, err, output)
|
||||
fmt.Printf("[DEBUG] 执行命令: iptables -D INPUT -s %s -j DROP\n", ip)
|
||||
execErr, output := fw.executeCommand(cmd)
|
||||
if execErr != nil {
|
||||
fmt.Printf("[ERROR] 解除IP封禁失败: %s, error: %v, output: %s\n", ip, execErr, output)
|
||||
return fmt.Errorf("failed to unblock IP %s: %v, output: %s", ip, execErr, output)
|
||||
}
|
||||
|
||||
fmt.Printf("[INFO] 成功解除IP封禁: %s\n", ip)
|
||||
@@ -222,8 +192,7 @@ func (fw *FireWallEngine) UnblockIP(ip string) error {
|
||||
// IsIPBlocked 检查IP是否已被封禁
|
||||
func (fw *FireWallEngine) IsIPBlocked(ip string) (bool, error) {
|
||||
fmt.Printf("[DEBUG] 检查IP是否被封禁: %s\n", ip)
|
||||
ruleName := generateRuleName(ip)
|
||||
blocked, err := fw.IsRuleExists(ruleName)
|
||||
blocked, err := fw.isIPInRules(ip)
|
||||
if blocked {
|
||||
fmt.Printf("[DEBUG] IP %s 已被封禁\n", ip)
|
||||
} else {
|
||||
@@ -233,46 +202,34 @@ func (fw *FireWallEngine) IsIPBlocked(ip string) (bool, error) {
|
||||
}
|
||||
|
||||
// BlockIPList 批量封禁IP列表
|
||||
// ips: IP地址列表
|
||||
// 返回成功数量、失败的IP列表和错误信息
|
||||
func (fw *FireWallEngine) BlockIPList(ips []string) (successCount int, failedIPs []string, err error) {
|
||||
successCount = 0
|
||||
failedIPs = []string{}
|
||||
|
||||
for _, ip := range ips {
|
||||
err := fw.BlockIP(ip, "")
|
||||
if err != nil {
|
||||
if e := fw.BlockIP(ip, ""); e != nil {
|
||||
failedIPs = append(failedIPs, ip)
|
||||
} else {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
if len(failedIPs) > 0 {
|
||||
return successCount, failedIPs, fmt.Errorf("failed to block %d IPs", len(failedIPs))
|
||||
}
|
||||
|
||||
return successCount, failedIPs, nil
|
||||
}
|
||||
|
||||
// UnblockIPList 批量解除IP封禁
|
||||
func (fw *FireWallEngine) UnblockIPList(ips []string) (successCount int, failedIPs []string, err error) {
|
||||
successCount = 0
|
||||
failedIPs = []string{}
|
||||
|
||||
for _, ip := range ips {
|
||||
err := fw.UnblockIP(ip)
|
||||
if err != nil {
|
||||
if e := fw.UnblockIP(ip); e != nil {
|
||||
failedIPs = append(failedIPs, ip)
|
||||
} else {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
if len(failedIPs) > 0 {
|
||||
return successCount, failedIPs, fmt.Errorf("failed to unblock %d IPs", len(failedIPs))
|
||||
}
|
||||
|
||||
return successCount, failedIPs, nil
|
||||
}
|
||||
|
||||
@@ -286,16 +243,14 @@ func (fw *FireWallEngine) GetBlockedIPList() ([]string, error) {
|
||||
|
||||
blockedIPs := []string{}
|
||||
lines := strings.Split(string(output), "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
// 查找DROP规则: -A INPUT -s <ip> -j DROP
|
||||
if strings.Contains(line, "-A INPUT -s") && strings.Contains(line, "-j DROP") {
|
||||
parts := strings.Fields(line)
|
||||
for i, part := range parts {
|
||||
if part == "-s" && i+1 < len(parts) {
|
||||
ip := parts[i+1]
|
||||
// 移除CIDR后缀(如果有)
|
||||
// 单个IP在 iptables-save 中会带 /32 后缀,去掉还原原始格式;CIDR 不受影响
|
||||
ip = strings.TrimSuffix(ip, "/32")
|
||||
blockedIPs = append(blockedIPs, ip)
|
||||
break
|
||||
@@ -303,42 +258,20 @@ func (fw *FireWallEngine) GetBlockedIPList() ([]string, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blockedIPs, nil
|
||||
}
|
||||
|
||||
// ClearAllBlockedIPs 清除所有封禁规则(谨慎使用)
|
||||
// ClearAllBlockedIPs 清除所有封禁规则
|
||||
func (fw *FireWallEngine) ClearAllBlockedIPs() (int, error) {
|
||||
blockedIPs, err := fw.GetBlockedIPList()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
count := 0
|
||||
for _, ip := range blockedIPs {
|
||||
err := fw.UnblockIP(ip)
|
||||
if err == nil {
|
||||
if fw.UnblockIP(ip) == nil {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// generateRuleName 生成规则名称
|
||||
func generateRuleName(ip string) string {
|
||||
// 将IP中的点替换为下划线,避免命令行解析问题
|
||||
safeName := strings.ReplaceAll(ip, ".", "_")
|
||||
safeName = strings.ReplaceAll(safeName, "/", "_")
|
||||
return RULE_PREFIX + safeName
|
||||
}
|
||||
|
||||
// extractIPFromRuleName 从规则名中提取IP
|
||||
func extractIPFromRuleName(ruleName string) string {
|
||||
if !strings.HasPrefix(ruleName, RULE_PREFIX) {
|
||||
return ""
|
||||
}
|
||||
safeName := strings.TrimPrefix(ruleName, RULE_PREFIX)
|
||||
ip := strings.ReplaceAll(safeName, "_", ".")
|
||||
return ip
|
||||
}
|
||||
|
||||
@@ -68,15 +68,30 @@ func (fw *FireWallEngine) executeCommand(cmd *exec.Cmd) (error error, printstr s
|
||||
fmt.Println(err)
|
||||
return err, err.Error()
|
||||
}
|
||||
cmd.Start()
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err, err.Error()
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
fmt.Println(err)
|
||||
return err, err.Error()
|
||||
}
|
||||
in := bufio.NewScanner(stdout)
|
||||
printstr = ""
|
||||
for in.Scan() {
|
||||
cmdRe := ConvertByte2String(in.Bytes(), "GB18030")
|
||||
//fmt.Println(cmdRe)
|
||||
printstr += cmdRe
|
||||
}
|
||||
cmd.Wait()
|
||||
errScanner := bufio.NewScanner(stderr)
|
||||
for errScanner.Scan() {
|
||||
cmdRe := ConvertByte2String(errScanner.Bytes(), "GB18030")
|
||||
printstr += cmdRe
|
||||
}
|
||||
waitErr := cmd.Wait()
|
||||
if waitErr != nil {
|
||||
return waitErr, printstr
|
||||
}
|
||||
return nil, printstr
|
||||
}
|
||||
|
||||
@@ -106,11 +121,20 @@ func (fw *FireWallEngine) AddRule(ruleName, ipToAdd, action, proc, localport str
|
||||
err, output := fw.executeCommand(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("[ERROR] 添加规则失败: %v, 输出: %s\n", err, output)
|
||||
return err
|
||||
return fmt.Errorf("%s", output)
|
||||
}
|
||||
|
||||
if strings.Contains(output, "请求的操作需要提升") || strings.Contains(output, "requires elevation") {
|
||||
fmt.Printf("[ERROR] 添加规则失败,需要管理员权限: %s\n", output)
|
||||
return fmt.Errorf("需要管理员权限,请以管理员身份运行: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "确定") && !strings.Contains(output, "Ok") {
|
||||
fmt.Printf("[ERROR] 添加规则可能失败, 输出: %s\n", output)
|
||||
return fmt.Errorf("添加规则失败: %s", output)
|
||||
}
|
||||
|
||||
fmt.Printf("[DEBUG] 添加规则成功, 输出: %s\n", output)
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fw *FireWallEngine) EditRule(ruleNum int, newRule string) error {
|
||||
|
||||
Reference in New Issue
Block a user