Files
EdgeNode/internal/firewalls/firewall_firewalld.go
GoEdgeLab c19be78e0d v1.4.1
2024-07-27 15:42:50 +08:00

227 lines
5.3 KiB
Go

// Copyright 2022 GoEdge goedge.cdn@gmail.com. All rights reserved.
package firewalls
import (
"fmt"
"strings"
"time"
"github.com/TeaOSLab/EdgeNode/internal/conns"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
"github.com/iwind/TeaGo/types"
)
type firewalldCmd struct {
cmd *executils.Cmd
denyIP string
}
type Firewalld struct {
BaseFirewall
isReady bool
exe string
cmdQueue chan *firewalldCmd
}
func NewFirewalld() *Firewalld {
var firewalld = &Firewalld{
cmdQueue: make(chan *firewalldCmd, 4096),
}
path, err := executils.LookPath("firewall-cmd")
if err == nil && len(path) > 0 {
var cmd = executils.NewTimeoutCmd(3*time.Second, path, "--state")
err := cmd.Run()
if err == nil {
firewalld.exe = path
// TODO check firewalld status with 'firewall-cmd --state' (running or not running),
// but we should recover the state when firewalld state changes, maybe check it every minutes
firewalld.isReady = true
firewalld.init()
}
}
return firewalld
}
func (this *Firewalld) init() {
goman.New(func() {
for c := range this.cmdQueue {
var cmd = c.cmd
err := cmd.Run()
if err != nil {
if strings.HasPrefix(err.Error(), "Warning:") {
continue
}
remotelogs.Warn("FIREWALL", "run command failed '"+cmd.String()+"': "+err.Error())
} else {
// 关闭连接
if len(c.denyIP) > 0 {
conns.SharedMap.CloseIPConns(c.denyIP)
}
}
}
})
}
// Name 名称
func (this *Firewalld) Name() string {
return "firewalld"
}
func (this *Firewalld) IsReady() bool {
return this.isReady
}
// IsMock 是否为模拟
func (this *Firewalld) IsMock() bool {
return false
}
func (this *Firewalld) AllowPort(port int, protocol string) error {
if !this.isReady {
return nil
}
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+types.String(port)+"/"+protocol)
this.pushCmd(cmd, "")
return nil
}
func (this *Firewalld) AllowPortRangesPermanently(portRanges [][2]int, protocol string) error {
for _, portRange := range portRanges {
var port = this.PortRangeString(portRange, protocol)
{
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port, "--permanent")
this.pushCmd(cmd, "")
}
{
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port)
this.pushCmd(cmd, "")
}
}
return nil
}
func (this *Firewalld) RemovePort(port int, protocol string) error {
if !this.isReady {
return nil
}
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+types.String(port)+"/"+protocol)
this.pushCmd(cmd, "")
return nil
}
func (this *Firewalld) RemovePortRangePermanently(portRange [2]int, protocol string) error {
var port = this.PortRangeString(portRange, protocol)
{
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port, "--permanent")
this.pushCmd(cmd, "")
}
{
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port)
this.pushCmd(cmd, "")
}
return nil
}
func (this *Firewalld) PortRangeString(portRange [2]int, protocol string) string {
if portRange[0] == portRange[1] {
return types.String(portRange[0]) + "/" + protocol
} else {
return types.String(portRange[0]) + "-" + types.String(portRange[1]) + "/" + protocol
}
}
func (this *Firewalld) RejectSourceIP(ip string, timeoutSeconds int) error {
if !this.isReady {
return nil
}
// 避免短时间内重复添加
if this.checkLatestIP(ip) {
return nil
}
var family = "ipv4"
if strings.Contains(ip, ":") {
family = "ipv6"
}
var args = []string{"--add-rich-rule=rule family='" + family + "' source address='" + ip + "' reject"}
if timeoutSeconds > 0 {
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
}
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
this.pushCmd(cmd, ip)
return nil
}
func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int, async bool) error {
if !this.isReady {
return nil
}
// 避免短时间内重复添加
if async && this.checkLatestIP(ip) {
return nil
}
var family = "ipv4"
if strings.Contains(ip, ":") {
family = "ipv6"
}
var args = []string{"--add-rich-rule=rule family='" + family + "' source address='" + ip + "' drop"}
if timeoutSeconds > 0 {
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
}
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
if async {
this.pushCmd(cmd, ip)
return nil
}
// 关闭连接
defer conns.SharedMap.CloseIPConns(ip)
err := cmd.Run()
if err != nil {
return fmt.Errorf("run command failed '%s': %w", cmd.String(), err)
}
return nil
}
func (this *Firewalld) RemoveSourceIP(ip string) error {
if !this.isReady {
return nil
}
var family = "ipv4"
if strings.Contains(ip, ":") {
family = "ipv6"
}
for _, action := range []string{"reject", "drop"} {
var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action}
var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
this.pushCmd(cmd, "")
}
return nil
}
func (this *Firewalld) pushCmd(cmd *executils.Cmd, denyIP string) {
select {
case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}:
default:
// we discard the command
}
}