优化命令执行相关代码

This commit is contained in:
GoEdgeLab
2022-09-15 11:14:33 +08:00
parent a38a69e388
commit b93734b395
17 changed files with 371 additions and 235 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables" "github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
@@ -21,6 +22,7 @@ import (
"net" "net"
"os/exec" "os/exec"
"strings" "strings"
"time"
) )
var SharedDDoSProtectionManager = NewDDoSProtectionManager() var SharedDDoSProtectionManager = NewDDoSProtectionManager()
@@ -263,23 +265,21 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
// 添加新规则 // 添加新规则
for _, port := range ports { for _, port := range ports {
if maxConnections > 0 { if maxConnections > 0 {
var cmd = exec.Command(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, 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 stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} }
// TODO 让用户选择是drop还是reject // TODO 让用户选择是drop还是reject
if maxConnectionsPerIP > 0 { if maxConnectionsPerIP > 0 {
var cmd = exec.Command(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, 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 stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} }
@@ -287,20 +287,18 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
// TODO 让用户选择是drop还是reject // TODO 让用户选择是drop还是reject
if newConnectionsMinutelyRate > 0 { if newConnectionsMinutelyRate > 0 {
if newConnectionsMinutelyRateBlockTimeout > 0 { if newConnectionsMinutelyRateBlockTimeout > 0 {
var cmd = exec.Command(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, 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 stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} else { } else {
var cmd = exec.Command(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, 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 stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} }
} }
@@ -309,20 +307,18 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
// TODO 让用户选择是drop还是reject // TODO 让用户选择是drop还是reject
if newConnectionsSecondlyRate > 0 { if newConnectionsSecondlyRate > 0 {
if newConnectionsSecondlyRateBlockTimeout > 0 { if newConnectionsSecondlyRateBlockTimeout > 0 {
var cmd = exec.Command(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, 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 stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} else { } else {
var cmd = exec.Command(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, 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 stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + stderr.String() + ")") return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
} }
} }
} }

View File

@@ -7,13 +7,15 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/conns" "github.com/TeaOSLab/EdgeNode/internal/conns"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"os/exec" "os/exec"
"strings" "strings"
"time"
) )
type firewalldCmd struct { type firewalldCmd struct {
cmd *exec.Cmd cmd *executils.Cmd
denyIP string denyIP string
} }
@@ -32,7 +34,7 @@ func NewFirewalld() *Firewalld {
path, err := exec.LookPath("firewall-cmd") path, err := exec.LookPath("firewall-cmd")
if err == nil && len(path) > 0 { if err == nil && len(path) > 0 {
var cmd = exec.Command(path, "--state") var cmd = executils.NewTimeoutCmd(3*time.Second, path, "--state")
err := cmd.Run() err := cmd.Run()
if err == nil { if err == nil {
firewalld.exe = path firewalld.exe = path
@@ -85,7 +87,7 @@ func (this *Firewalld) AllowPort(port int, protocol string) error {
if !this.isReady { if !this.isReady {
return nil return nil
} }
var cmd = exec.Command(this.exe, "--add-port="+types.String(port)+"/"+protocol) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+types.String(port)+"/"+protocol)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
return nil return nil
} }
@@ -95,12 +97,12 @@ func (this *Firewalld) AllowPortRangesPermanently(portRanges [][2]int, protocol
var port = this.PortRangeString(portRange, protocol) var port = this.PortRangeString(portRange, protocol)
{ {
var cmd = exec.Command(this.exe, "--add-port="+port, "--permanent") var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port, "--permanent")
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
{ {
var cmd = exec.Command(this.exe, "--add-port="+port) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
} }
@@ -112,7 +114,7 @@ func (this *Firewalld) RemovePort(port int, protocol string) error {
if !this.isReady { if !this.isReady {
return nil return nil
} }
var cmd = exec.Command(this.exe, "--remove-port="+types.String(port)+"/"+protocol) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+types.String(port)+"/"+protocol)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
return nil return nil
} }
@@ -121,12 +123,12 @@ func (this *Firewalld) RemovePortRangePermanently(portRange [2]int, protocol str
var port = this.PortRangeString(portRange, protocol) var port = this.PortRangeString(portRange, protocol)
{ {
var cmd = exec.Command(this.exe, "--remove-port="+port, "--permanent") var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port, "--permanent")
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
{ {
var cmd = exec.Command(this.exe, "--remove-port="+port) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
@@ -159,7 +161,7 @@ func (this *Firewalld) RejectSourceIP(ip string, timeoutSeconds int) error {
if timeoutSeconds > 0 { if timeoutSeconds > 0 {
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s") args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
} }
var cmd = exec.Command(this.exe, args...) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
this.pushCmd(cmd, ip) this.pushCmd(cmd, ip)
return nil return nil
} }
@@ -182,7 +184,7 @@ func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int, async bool) e
if timeoutSeconds > 0 { if timeoutSeconds > 0 {
args = append(args, "--timeout="+types.String(timeoutSeconds)+"s") args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
} }
var cmd = exec.Command(this.exe, args...) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
if async { if async {
this.pushCmd(cmd, ip) this.pushCmd(cmd, ip)
return nil return nil
@@ -209,13 +211,13 @@ func (this *Firewalld) RemoveSourceIP(ip string) error {
} }
for _, action := range []string{"reject", "drop"} { for _, action := range []string{"reject", "drop"} {
var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action} var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action}
var cmd = exec.Command(this.exe, args...) var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
this.pushCmd(cmd, "") this.pushCmd(cmd, "")
} }
return nil return nil
} }
func (this *Firewalld) pushCmd(cmd *exec.Cmd, denyIP string) { func (this *Firewalld) pushCmd(cmd *executils.Cmd, denyIP string) {
select { select {
case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}: case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}:
default: default:

View File

@@ -5,7 +5,6 @@
package firewalls package firewalls
import ( import (
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeNode/internal/conns" "github.com/TeaOSLab/EdgeNode/internal/conns"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
@@ -13,6 +12,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables" "github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"net" "net"
"os/exec" "os/exec"
@@ -432,15 +432,14 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
// 读取版本号 // 读取版本号
func (this *NFTablesFirewall) readVersion(nftPath string) string { func (this *NFTablesFirewall) readVersion(nftPath string) string {
var cmd = exec.Command(nftPath, "--version") var cmd = executils.NewTimeoutCmd(10*time.Second, nftPath, "--version")
var output = &bytes.Buffer{} cmd.WithStdout()
cmd.Stdout = output
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return "" return ""
} }
var outputString = output.String() var outputString = cmd.Stdout()
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString) var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
if len(versionMatches) <= 1 { if len(versionMatches) <= 1 {
return "" return ""

View File

@@ -1,11 +1,11 @@
package iplibrary package iplibrary
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"os/exec" "os/exec"
"runtime" "runtime"
"time" "time"
@@ -13,9 +13,9 @@ import (
// FirewalldAction Firewalld动作管理 // FirewalldAction Firewalld动作管理
// 常用命令: // 常用命令:
// - 查询列表: firewall-cmd --list-all // - 查询列表: firewall-cmd --list-all
// - 添加IPfirewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s // - 添加IPfirewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
// - 删除IPfirewall-cmd --remove-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s // - 删除IPfirewall-cmd --remove-rich-rule="rule family='ipv4' source address='192.168.2.32' reject" --timeout=30s
type FirewalldAction struct { type FirewalldAction struct {
BaseAction BaseAction
@@ -144,12 +144,11 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
// MAC OS直接返回 // MAC OS直接返回
return nil return nil
} }
cmd := exec.Command(path, args...) cmd := executils.NewTimeoutCmd(30*time.Second, path, args...)
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return errors.New(err.Error() + ", output: " + string(stderr.Bytes())) return errors.New(err.Error() + ", output: " + cmd.Stderr())
} }
return nil return nil
} }

View File

@@ -1,10 +1,10 @@
package iplibrary package iplibrary
import ( import (
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"os/exec" "os/exec"
"runtime" "runtime"
@@ -16,12 +16,12 @@ import (
// IPSetAction IPSet动作 // IPSetAction IPSet动作
// 相关命令: // 相关命令:
// - 利用Firewalld管理set // - 利用Firewalld管理set
// - 添加firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0" // - 添加firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0"
// - 删除firewall-cmd --permanent --delete-ipset=edge_ip_list // - 删除firewall-cmd --permanent --delete-ipset=edge_ip_list
// - 重载firewall-cmd --reload // - 重载firewall-cmd --reload
// - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject" // - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject"
// - 利用IPTables管理set // - 利用IPTables管理set
// - 添加iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT // - 添加iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT
// - 添加Itemipset add edge_ip_list 192.168.2.32 timeout 30 // - 添加Itemipset add edge_ip_list 192.168.2.32 timeout 30
// - 删除Item: ipset del edge_ip_list 192.168.2.32 // - 删除Item: ipset del edge_ip_list 192.168.2.32
// - 创建setipset create edge_ip_list hash:ip timeout 0 // - 创建setipset create edge_ip_list hash:ip timeout 0
@@ -30,16 +30,13 @@ import (
type IPSetAction struct { type IPSetAction struct {
BaseAction BaseAction
config *firewallconfigs.FirewallActionIPSetConfig config *firewallconfigs.FirewallActionIPSetConfig
errorBuf *bytes.Buffer
ipsetNotfound bool ipsetNotfound bool
} }
func NewIPSetAction() *IPSetAction { func NewIPSetAction() *IPSetAction {
return &IPSetAction{ return &IPSetAction{}
errorBuf: &bytes.Buffer{},
}
} }
func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error { func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error {
@@ -68,14 +65,13 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
var cmd = exec.Command(path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() var output = cmd.Stderr()
if !bytes.Contains(output, []byte("already exists")) { if !strings.Contains(output, "already exists") {
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output)) return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
} else { } else {
err = nil err = nil
} }
@@ -87,14 +83,13 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
var cmd = exec.Command(path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() var output = cmd.Stderr()
if !bytes.Contains(output, []byte("already exists")) { if !strings.Contains(output, "already exists") {
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + string(output)) return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
} else { } else {
err = nil err = nil
} }
@@ -114,16 +109,15 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000")
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
output := stderr.Bytes() var output = cmd.Stderr()
if bytes.Contains(output, []byte("NAME_CONFLICT")) { if strings.Contains(output, "NAME_CONFLICT") {
err = nil err = nil
} else { } else {
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output)) return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
} }
} }
} }
@@ -133,16 +127,15 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
cmd := exec.Command(path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000")
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() var output = cmd.Stderr()
if bytes.Contains(output, []byte("NAME_CONFLICT")) { if strings.Contains(output, "NAME_CONFLICT") {
err = nil err = nil
} else { } else {
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + string(output)) return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
} }
} }
} }
@@ -152,13 +145,11 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
} }
} }
@@ -167,25 +158,21 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
if len(listName) == 0 { if len(listName) == 0 {
continue continue
} }
var cmd = exec.Command(path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + string(output))
} }
} }
// reload // reload
{ {
cmd := exec.Command(path, "--reload") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--reload")
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + string(output))
} }
} }
} }
@@ -204,19 +191,17 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
} }
// 检查规则是否存在 // 检查规则是否存在
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
err := cmd.Run() err := cmd.Run()
var exists = err == nil var exists = err == nil
// 添加规则 // 添加规则
if !exists { if !exists {
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
} }
} }
} }
@@ -228,18 +213,16 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
} }
// 检查规则是否存在 // 检查规则是否存在
var cmd = exec.Command(path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
err := cmd.Run() err := cmd.Run()
var exists = err == nil var exists = err == nil
if !exists { if !exists {
var cmd = exec.Command(path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT") var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT")
var stderr = bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
var output = stderr.Bytes() return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
return errors.New("iptables add rule: " + err.Error() + ", output: " + string(output))
} }
} }
} }
@@ -361,12 +344,11 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
return nil return nil
} }
this.errorBuf.Reset() var cmd = executils.NewTimeoutCmd(30*time.Second, path, args...)
var cmd = exec.Command(path, args...) cmd.WithStderr()
cmd.Stderr = this.errorBuf
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
var errString = this.errorBuf.String() var errString = cmd.Stderr()
if action == "deleteItem" && strings.Contains(errString, "not added") { if action == "deleteItem" && strings.Contains(errString, "not added") {
return nil return nil
} }

View File

@@ -1,20 +1,23 @@
package iplibrary package iplibrary
import ( import (
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"os/exec" "os/exec"
"runtime" "runtime"
"strings"
"time"
) )
// IPTablesAction IPTables动作 // IPTablesAction IPTables动作
// 相关命令: // 相关命令:
// iptables -A INPUT -s "192.168.2.32" -j ACCEPT //
// iptables -A INPUT -s "192.168.2.32" -j REJECT // iptables -A INPUT -s "192.168.2.32" -j ACCEPT
// iptables -D INPUT ... // iptables -A INPUT -s "192.168.2.32" -j REJECT
// iptables -F INPUT // iptables -D INPUT ...
// iptables -F INPUT
type IPTablesAction struct { type IPTablesAction struct {
BaseAction BaseAction
@@ -110,16 +113,15 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
return nil return nil
} }
cmd := exec.Command(path, args...) var cmd = executils.NewTimeoutCmd(30*time.Second, path, args...)
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
output := stderr.Bytes() var output = cmd.Stderr()
if bytes.Contains(output, []byte("No chain/target/match")) { if strings.Contains(output, "No chain/target/match") {
err = nil err = nil
} else { } else {
return errors.New(err.Error() + ", output: " + string(output)) return errors.New(err.Error() + ", output: " + output)
} }
} }
return nil return nil

View File

@@ -1,13 +1,13 @@
package iplibrary package iplibrary
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"os/exec" executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"path/filepath" "path/filepath"
"time"
) )
// ScriptAction 脚本命令动作 // ScriptAction 脚本命令动作
@@ -45,25 +45,24 @@ func (this *ScriptAction) DeleteItem(listType IPListType, item *pb.IPItem) error
func (this *ScriptAction) runAction(action string, listType IPListType, item *pb.IPItem) error { func (this *ScriptAction) runAction(action string, listType IPListType, item *pb.IPItem) error {
// TODO 智能支持 .sh 脚本文件 // TODO 智能支持 .sh 脚本文件
cmd := exec.Command(this.config.Path) var cmd = executils.NewTimeoutCmd(30*time.Second, this.config.Path)
cmd.Env = []string{ cmd.WithEnv([]string{
"ACTION=" + action, "ACTION=" + action,
"TYPE=" + item.Type, "TYPE=" + item.Type,
"IP_FROM=" + item.IpFrom, "IP_FROM=" + item.IpFrom,
"IP_TO=" + item.IpTo, "IP_TO=" + item.IpTo,
"EXPIRED_AT=" + fmt.Sprintf("%d", item.ExpiredAt), "EXPIRED_AT=" + fmt.Sprintf("%d", item.ExpiredAt),
"LIST_TYPE=" + listType, "LIST_TYPE=" + listType,
} })
if len(this.config.Cwd) > 0 { if len(this.config.Cwd) > 0 {
cmd.Dir = this.config.Cwd cmd.WithDir(this.config.Cwd)
} else { } else {
cmd.Dir = filepath.Dir(this.config.Path) cmd.WithDir(filepath.Dir(this.config.Path))
} }
stderr := bytes.NewBuffer([]byte{}) cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New(err.Error() + ", output: " + string(stderr.Bytes())) return errors.New(err.Error() + ", output: " + cmd.Stderr())
} }
return nil return nil
} }

View File

@@ -1,7 +1,6 @@
package nodes package nodes
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -17,7 +16,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils" executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"net/url" "net/url"
@@ -347,15 +346,15 @@ func (this *APIStream) handleCheckSystemdService(message *pb.NodeStreamMessage)
return nil return nil
} }
var cmd = utils.NewCommandExecutor() var shortName = teaconst.SystemdServiceName
shortName := teaconst.SystemdServiceName var cmd = executils.NewTimeoutCmd(10*time.Second, systemctl, "is-enabled", shortName)
cmd.Add(systemctl, "is-enabled", shortName) cmd.WithStdout()
output, err := cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
this.replyFail(message.RequestId, "'systemctl' command error: "+err.Error()) this.replyFail(message.RequestId, "'systemctl' command error: "+err.Error())
return nil return nil
} }
if output == "enabled" { if cmd.Stdout() == "enabled" {
this.replyOk(message.RequestId, "ok") this.replyOk(message.RequestId, "ok")
} else { } else {
this.replyFail(message.RequestId, "not installed") this.replyFail(message.RequestId, "not installed")
@@ -385,16 +384,15 @@ func (this *APIStream) handleCheckLocalFirewall(message *pb.NodeStreamMessage) e
return nil return nil
} }
var cmd = exec.Command(nft, "--version") var cmd = executils.NewTimeoutCmd(10*time.Second, nft, "--version")
var output = &bytes.Buffer{} cmd.WithStdout()
cmd.Stdout = output
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
this.replyFail(message.RequestId, "get version failed: "+err.Error()) this.replyFail(message.RequestId, "get version failed: "+err.Error())
return nil return nil
} }
var outputString = output.String() var outputString = cmd.Stdout()
var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString) var versionMatches = regexp.MustCompile(`nftables v([\d.]+)`).FindStringSubmatch(outputString)
if len(versionMatches) <= 1 { if len(versionMatches) <= 1 {
this.replyFail(message.RequestId, "can not get nft version") this.replyFail(message.RequestId, "can not get nft version")

View File

@@ -1,7 +1,6 @@
package nodes package nodes
import ( import (
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
@@ -9,6 +8,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
@@ -213,15 +213,14 @@ func (this *ListenerManager) findProcessNameWithPort(isUdp bool, port string) st
option = "u" option = "u"
} }
var cmd = exec.Command(path, "-"+option+"lpn", "sport = :"+port) var cmd = executils.NewTimeoutCmd(10*time.Second, path, "-"+option+"lpn", "sport = :"+port)
var output = &bytes.Buffer{} cmd.WithStdout()
cmd.Stdout = output
err = cmd.Run() err = cmd.Run()
if err != nil { if err != nil {
return "" return ""
} }
var matches = regexp.MustCompile(`(?U)\(\("(.+)",pid=\d+,fd=\d+\)\)`).FindStringSubmatch(output.String()) var matches = regexp.MustCompile(`(?U)\(\("(.+)",pid=\d+,fd=\d+\)\)`).FindStringSubmatch(cmd.Stdout())
if len(matches) > 1 { if len(matches) > 1 {
return matches[1] return matches[1]
} }

View File

@@ -10,6 +10,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"os" "os"
"os/exec" "os/exec"
@@ -84,10 +85,10 @@ func (this *SystemServiceManager) setupSystemd(params maps.Map) error {
if len(systemctl) == 0 { if len(systemctl) == 0 {
return errors.New("can not find 'systemctl' on the system") return errors.New("can not find 'systemctl' on the system")
} }
cmd := utils.NewCommandExecutor()
shortName := teaconst.SystemdServiceName shortName := teaconst.SystemdServiceName
cmd.Add(systemctl, "is-enabled", shortName) var cmd = executils.NewTimeoutCmd(10*time.Second, systemctl, "is-enabled", shortName)
output, err := cmd.Run() cmd.WithStdout()
err = cmd.Run()
if err != nil { if err != nil {
return err return err
} }
@@ -100,10 +101,10 @@ func (this *SystemServiceManager) setupSystemd(params maps.Map) error {
// 启动Service // 启动Service
goman.New(func() { goman.New(func() {
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
_ = exec.Command(systemctl, "start", teaconst.SystemdServiceName).Start() _ = executils.NewTimeoutCmd(30*time.Second, systemctl, "start", teaconst.SystemdServiceName).Start()
}) })
if output == "enabled" { if cmd.Stdout() == "enabled" {
// 检查文件路径是否变化 // 检查文件路径是否变化
data, err := os.ReadFile("/etc/systemd/system/" + teaconst.SystemdServiceName + ".service") data, err := os.ReadFile("/etc/systemd/system/" + teaconst.SystemdServiceName + ".service")
if err == nil && bytes.Index(data, []byte(exe)) > 0 { if err == nil && bytes.Index(data, []byte(exe)) > 0 {

View File

@@ -1,14 +1,15 @@
package nodes package nodes
import ( import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"net" "net"
"os" "os"
"os/exec"
"strings" "strings"
"time" "time"
) )
@@ -61,12 +62,16 @@ func (this *TOAManager) Run(config *nodeconfigs.TOAConfig) error {
} }
remotelogs.Println("TOA", "starting ...") remotelogs.Println("TOA", "starting ...")
remotelogs.Println("TOA", "args: "+strings.Join(config.AsArgs(), " ")) remotelogs.Println("TOA", "args: "+strings.Join(config.AsArgs(), " "))
cmd := exec.Command(binPath, config.AsArgs()...) cmd := executils.NewCmd(binPath, config.AsArgs()...)
err = cmd.Start() err = cmd.Start()
if err != nil { if err != nil {
return err return err
} }
this.pid = cmd.Process.Pid var process = cmd.Process()
if process == nil {
return errors.New("start failed")
}
this.pid = process.Pid
goman.New(func() { goman.New(func() {
_ = cmd.Wait() _ = cmd.Wait()

View File

@@ -12,11 +12,11 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
stringutil "github.com/iwind/TeaGo/utils/string" stringutil "github.com/iwind/TeaGo/utils/string"
"github.com/iwind/gosock/pkg/gosock" "github.com/iwind/gosock/pkg/gosock"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"time" "time"
@@ -252,7 +252,7 @@ func (this *UpgradeManager) restart() error {
// 启动 // 启动
exe = filepath.Dir(exe) + "/" + teaconst.ProcessName exe = filepath.Dir(exe) + "/" + teaconst.ProcessName
var cmd = exec.Command(exe, "start") var cmd = executils.NewCmd(exe, "start")
err = cmd.Start() err = cmd.Start()
if err != nil { if err != nil {
return err return err

View File

@@ -3,9 +3,9 @@
package clock package clock
import ( import (
"bytes"
"errors" "errors"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"os/exec" "os/exec"
"runtime" "runtime"
"time" "time"
@@ -46,12 +46,11 @@ func Sync() error {
} }
func syncNtpdate(ntpdate string) error { func syncNtpdate(ntpdate string) error {
var cmd = exec.Command(ntpdate, "pool.ntp.org") var cmd = executils.NewTimeoutCmd(30*time.Second, ntpdate, "pool.ntp.org")
var stderr = &bytes.Buffer{} cmd.WithStderr()
cmd.Stderr = stderr
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return errors.New(err.Error() + ": " + stderr.String()) return errors.New(err.Error() + ": " + cmd.Stderr())
} }
return nil return nil

View File

@@ -1,7 +0,0 @@
package utils
// 命令定义
type Command struct {
Name string
Args []string
}

View File

@@ -1,61 +0,0 @@
package utils
import (
"bytes"
"errors"
"os/exec"
)
// 命令执行器
type CommandExecutor struct {
commands []*Command
}
// 获取新对象
func NewCommandExecutor() *CommandExecutor {
return &CommandExecutor{}
}
// 添加命令
func (this *CommandExecutor) Add(command string, arg ...string) {
this.commands = append(this.commands, &Command{
Name: command,
Args: arg,
})
}
// 执行命令
func (this *CommandExecutor) Run() (output string, err error) {
if len(this.commands) == 0 {
return "", errors.New("no commands no run")
}
var lastCmd *exec.Cmd = nil
var lastData []byte = nil
for _, command := range this.commands {
cmd := exec.Command(command.Name, command.Args...)
stdout := bytes.NewBuffer([]byte{})
cmd.Stdout = stdout
if lastCmd != nil {
cmd.Stdin = bytes.NewBuffer(lastData)
}
err = cmd.Start()
if err != nil {
return "", err
}
err = cmd.Wait()
if err != nil {
_, ok := err.(*exec.ExitError)
if ok {
return "", nil
}
return "", err
}
lastData = stdout.Bytes()
lastCmd = cmd
}
return string(bytes.TrimSpace(lastData)), nil
}

162
internal/utils/exec/cmd.go Normal file
View File

@@ -0,0 +1,162 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package executils
import (
"bytes"
"context"
"os"
"os/exec"
"strings"
"time"
)
type Cmd struct {
name string
args []string
env []string
dir string
ctx context.Context
timeout time.Duration
cancelFunc func()
captureStdout bool
captureStderr bool
stdout *bytes.Buffer
stderr *bytes.Buffer
rawCmd *exec.Cmd
}
func NewCmd(name string, args ...string) *Cmd {
return &Cmd{
name: name,
args: args,
}
}
func NewTimeoutCmd(timeout time.Duration, name string, args ...string) *Cmd {
return (&Cmd{
name: name,
args: args,
}).WithTimeout(timeout)
}
func (this *Cmd) WithTimeout(timeout time.Duration) *Cmd {
this.timeout = timeout
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
this.ctx = ctx
this.cancelFunc = cancelFunc
return this
}
func (this *Cmd) WithStdout() *Cmd {
this.captureStdout = true
return this
}
func (this *Cmd) WithStderr() *Cmd {
this.captureStderr = true
return this
}
func (this *Cmd) WithEnv(env []string) *Cmd {
this.env = env
return this
}
func (this *Cmd) WithDir(dir string) *Cmd {
this.dir = dir
return this
}
func (this *Cmd) Start() error {
var cmd = this.compose()
return cmd.Start()
}
func (this *Cmd) Wait() error {
var cmd = this.compose()
return cmd.Wait()
}
func (this *Cmd) Run() error {
if this.cancelFunc != nil {
defer this.cancelFunc()
}
var cmd = this.compose()
return cmd.Run()
}
func (this *Cmd) RawStdout() string {
if this.stdout != nil {
return this.stdout.String()
}
return ""
}
func (this *Cmd) Stdout() string {
return strings.TrimSpace(this.RawStdout())
}
func (this *Cmd) RawStderr() string {
if this.stderr != nil {
return this.stderr.String()
}
return ""
}
func (this *Cmd) Stderr() string {
return strings.TrimSpace(this.RawStderr())
}
func (this *Cmd) String() string {
if this.rawCmd != nil {
return this.rawCmd.String()
}
var newCmd = exec.Command(this.name, this.args...)
return newCmd.String()
}
func (this *Cmd) Process() *os.Process {
if this.rawCmd != nil {
return this.rawCmd.Process
}
return nil
}
func (this *Cmd) compose() *exec.Cmd {
if this.rawCmd != nil {
return this.rawCmd
}
if this.ctx != nil {
this.rawCmd = exec.CommandContext(this.ctx, this.name, this.args...)
} else {
this.rawCmd = exec.Command(this.name, this.args...)
}
if this.env != nil {
this.rawCmd.Env = this.env
}
if len(this.dir) > 0 {
this.rawCmd.Dir = this.dir
}
if this.captureStdout {
this.stdout = &bytes.Buffer{}
this.rawCmd.Stdout = this.stdout
}
if this.captureStderr {
this.stderr = &bytes.Buffer{}
this.rawCmd.Stderr = this.stderr
}
return this.rawCmd
}

View File

@@ -0,0 +1,61 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
package executils_test
import (
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"testing"
"time"
)
func TestNewTimeoutCmd_Sleep(t *testing.T) {
var cmd = executils.NewTimeoutCmd(1*time.Second, "sleep", "3")
cmd.WithStdout()
cmd.WithStderr()
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("stderr:", cmd.Stderr())
}
func TestNewTimeoutCmd_Echo(t *testing.T) {
var cmd = executils.NewTimeoutCmd(10*time.Second, "echo", "-n", "hello")
cmd.WithStdout()
cmd.WithStderr()
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("stderr:", cmd.Stderr())
}
func TestNewTimeoutCmd_Echo2(t *testing.T) {
var cmd = executils.NewCmd("echo", "hello")
cmd.WithStdout()
cmd.WithStderr()
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("raw stdout:", cmd.RawStdout())
t.Log("stderr:", cmd.Stderr())
t.Log("raw stderr:", cmd.RawStderr())
}
func TestNewTimeoutCmd_Echo3(t *testing.T) {
var cmd = executils.NewCmd("echo", "-n", "hello")
err := cmd.Run()
t.Log("error:", err)
t.Log("stdout:", cmd.Stdout())
t.Log("stderr:", cmd.Stderr())
}
func TestCmd_Process(t *testing.T) {
var cmd = executils.NewCmd("echo", "-n", "hello")
err := cmd.Run()
t.Log("error:", err)
t.Log(cmd.Process())
}
func TestNewTimeoutCmd_String(t *testing.T) {
var cmd = executils.NewCmd("echo", "-n", "hello")
t.Log("stdout:", cmd.String())
}