diff --git a/internal/firewalls/ddos_protection.go b/internal/firewalls/ddos_protection.go index 9a2afb6..54b904f 100644 --- a/internal/firewalls/ddos_protection.go +++ b/internal/firewalls/ddos_protection.go @@ -1,6 +1,5 @@ // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. //go:build linux -// +build linux package firewalls @@ -55,19 +54,13 @@ func init() { // DDoSProtectionManager DDoS防护 type DDoSProtectionManager struct { - nftPath string - lastAllowIPList []string lastConfig []byte } // NewDDoSProtectionManager 获取新对象 func NewDDoSProtectionManager() *DDoSProtectionManager { - nftPath, _ := exec.LookPath("nft") - - return &DDoSProtectionManager{ - nftPath: nftPath, - } + return &DDoSProtectionManager{} } // Apply 应用配置 @@ -93,7 +86,7 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e } remotelogs.Println("FIREWALL", "change DDoS protection config") - if len(this.nftPath) == 0 { + if len(this.nftExe()) == 0 { return errors.New("can not find nft command") } @@ -156,6 +149,11 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e // 添加TCP规则 func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) error { + var nftExe = this.nftExe() + if len(nftExe) == 0 { + return nil + } + // 检查nft版本不能小于0.9 if len(nftablesInstance.version) > 0 && stringutil.VersionCompare("0.9", nftablesInstance.version) > 0 { return nil @@ -265,7 +263,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) // 添加新规则 for _, port := range ports { if maxConnections > 0 { - var cmd = executils.NewTimeoutCmd(10*time.Second, this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)})) + var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)})) cmd.WithStderr() err := cmd.Run() if err != nil { @@ -275,7 +273,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) // TODO 让用户选择是drop还是reject if maxConnectionsPerIP > 0 { - var cmd = executils.NewTimeoutCmd(10*time.Second, this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "meter", "meter-"+protocol+"-"+types.String(port)+"-max-connections", "{ "+protocol+" saddr ct count over "+types.String(maxConnectionsPerIP)+" }", "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnectionsPerIP", types.String(maxConnectionsPerIP)})) + var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "meter", "meter-"+protocol+"-"+types.String(port)+"-max-connections", "{ "+protocol+" saddr ct count over "+types.String(maxConnectionsPerIP)+" }", "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnectionsPerIP", types.String(maxConnectionsPerIP)})) cmd.WithStderr() err := cmd.Run() if err != nil { @@ -287,14 +285,14 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) // TODO 让用户选择是drop还是reject if newConnectionsMinutelyRate > 0 { if newConnectionsMinutelyRateBlockTimeout > 0 { - var cmd = executils.NewTimeoutCmd(10*time.Second, this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsMinutelyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", types.String(newConnectionsMinutelyRate), types.String(newConnectionsMinutelyRateBlockTimeout)})) + var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsMinutelyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", types.String(newConnectionsMinutelyRate), types.String(newConnectionsMinutelyRateBlockTimeout)})) cmd.WithStderr() err := cmd.Run() if err != nil { return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")") } } else { - var cmd = executils.NewTimeoutCmd(10*time.Second, this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"})) + var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"})) cmd.WithStderr() err := cmd.Run() if err != nil { @@ -307,14 +305,14 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig) // TODO 让用户选择是drop还是reject if newConnectionsSecondlyRate > 0 { if newConnectionsSecondlyRateBlockTimeout > 0 { - var cmd = executils.NewTimeoutCmd(10*time.Second, this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsSecondlyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", types.String(newConnectionsSecondlyRate), types.String(newConnectionsSecondlyRateBlockTimeout)})) + var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }", "add", "@deny_set", "{"+protocol+" saddr timeout "+types.String(newConnectionsSecondlyRateBlockTimeout)+"s}", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", types.String(newConnectionsSecondlyRate), types.String(newConnectionsSecondlyRateBlockTimeout)})) cmd.WithStderr() err := cmd.Run() if err != nil { return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")") } } else { - var cmd = executils.NewTimeoutCmd(10*time.Second, this.nftPath, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"})) + var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"})) cmd.WithStderr() err := cmd.Run() if err != nil { @@ -551,3 +549,8 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error { return nil } + +func (this *DDoSProtectionManager) nftExe() string { + path, _ := exec.LookPath("nft") + return path +} diff --git a/internal/firewalls/nftables/installer.go b/internal/firewalls/nftables/installer.go new file mode 100644 index 0000000..8283c0a --- /dev/null +++ b/internal/firewalls/nftables/installer.go @@ -0,0 +1,108 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package nftables + +import ( + "errors" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" + "github.com/TeaOSLab/EdgeNode/internal/events" + "github.com/TeaOSLab/EdgeNode/internal/goman" + "github.com/TeaOSLab/EdgeNode/internal/remotelogs" + executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec" + "github.com/iwind/TeaGo/logs" + "os" + "os/exec" + "runtime" + "time" +) + +func init() { + events.On(events.EventReload, func() { + // linux only + if runtime.GOOS != "linux" { + return + } + + nodeConfig, err := nodeconfigs.SharedNodeConfig() + if err != nil { + return + } + + if nodeConfig == nil || !nodeConfig.AutoInstallNftables { + return + } + + if os.Getgid() == 0 { // root user only + _, err := exec.LookPath("nft") + if err == nil { + return + } + goman.New(func() { + err := NewInstaller().Install() + if err != nil { + // 不需要传到API节点 + logs.Println("[NFTABLES]install nftables failed: " + err.Error()) + } + }) + } + }) +} + +type Installer struct { +} + +func NewInstaller() *Installer { + return &Installer{} +} + +func (this *Installer) Install() error { + // linux only + if runtime.GOOS != "linux" { + return nil + } + + // 检查是否已经存在 + _, err := exec.LookPath("nft") + if err == nil { + return nil + } + + var cmd *executils.Cmd + + // check dnf + dnfExe, err := exec.LookPath("dnf") + if err == nil { + cmd = executils.NewCmd(dnfExe, "-y", "install", "nftables") + } + + // check apt + if cmd == nil { + aptExe, err := exec.LookPath("apt") + if err == nil { + cmd = executils.NewCmd(aptExe, "install", "nftables") + } + } + + // check yum + if cmd == nil { + yumExe, err := exec.LookPath("yum") + if err == nil { + cmd = executils.NewCmd(yumExe, "-y", "install", "nftables") + } + } + + if cmd == nil { + return nil + } + + cmd.WithTimeout(10 * time.Minute) + cmd.WithStderr() + err = cmd.Run() + if err != nil { + return errors.New(err.Error() + ": " + cmd.Stderr()) + } + + remotelogs.Println("NFTABLES", "installed nftables with command '"+cmd.String()+"' successfully") + + return nil +}