113 lines
2.6 KiB
Go
113 lines
2.6 KiB
Go
package firewall
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"os/exec"
|
||
"runtime"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
const (
|
||
backendWindows = "windows"
|
||
backendUFW = "ufw"
|
||
backendFirewalld = "firewalld"
|
||
)
|
||
|
||
type manager struct {
|
||
goos string
|
||
runner Runner
|
||
}
|
||
|
||
type execRunner struct{}
|
||
|
||
func NewManager() Manager {
|
||
return NewManagerForOS(runtime.GOOS, execRunner{})
|
||
}
|
||
|
||
func NewManagerForOS(goos string, runner Runner) Manager {
|
||
if runner == nil {
|
||
runner = execRunner{}
|
||
}
|
||
return &manager{goos: strings.ToLower(strings.TrimSpace(goos)), runner: runner}
|
||
}
|
||
|
||
func (execRunner) LookPath(name string) (string, error) {
|
||
return exec.LookPath(name)
|
||
}
|
||
|
||
func (execRunner) Run(ctx context.Context, name string, args ...string) (CommandResult, error) {
|
||
command := exec.CommandContext(ctx, name, args...)
|
||
command.Env = append(os.Environ(), "LC_ALL=C", "LANG=C")
|
||
output, err := command.CombinedOutput()
|
||
if err == nil {
|
||
return CommandResult{Output: output, ExitCode: 0}, nil
|
||
}
|
||
var exitErr *exec.ExitError
|
||
if errors.As(err, &exitErr) {
|
||
return CommandResult{Output: output, ExitCode: exitErr.ExitCode()}, nil
|
||
}
|
||
return CommandResult{Output: output, ExitCode: -1}, err
|
||
}
|
||
|
||
func (m *manager) OpenTCP(ctx context.Context, port int) (Result, error) {
|
||
if err := validatePort(port); err != nil {
|
||
return Result{}, err
|
||
}
|
||
switch m.goos {
|
||
case "windows":
|
||
return m.openWindows(ctx, port)
|
||
case "linux":
|
||
return m.openLinux(ctx, port)
|
||
default:
|
||
return Result{
|
||
Backend: BackendNone, Port: port,
|
||
Message: fmt.Sprintf("当前操作系统 %s 不在自动防火墙配置范围内,已跳过本机规则创建", m.goos),
|
||
}, nil
|
||
}
|
||
}
|
||
|
||
func (m *manager) Rollback(ctx context.Context, result Result) error {
|
||
if !result.Created {
|
||
return nil
|
||
}
|
||
if err := validatePort(result.Port); err != nil {
|
||
return err
|
||
}
|
||
switch result.Backend {
|
||
case backendWindows:
|
||
return m.rollbackWindows(ctx, result)
|
||
case backendUFW:
|
||
return m.rollbackUFW(ctx, result)
|
||
case backendFirewalld:
|
||
return m.rollbackFirewalld(ctx, result)
|
||
default:
|
||
return fmt.Errorf("无法回滚未知防火墙后端:%s", result.Backend)
|
||
}
|
||
}
|
||
|
||
func validatePort(port int) error {
|
||
if port < 1 || port > 65535 {
|
||
return fmt.Errorf("防火墙端口无效:%d", port)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func ruleName(port int) string {
|
||
return "Scert Web TCP " + strconv.Itoa(port)
|
||
}
|
||
|
||
func commandError(action, name string, result CommandResult, err error) error {
|
||
if err != nil {
|
||
return fmt.Errorf("%s失败,无法执行 %s:%w", action, name, err)
|
||
}
|
||
message := strings.TrimSpace(string(result.Output))
|
||
if message == "" {
|
||
message = fmt.Sprintf("退出码 %d", result.ExitCode)
|
||
}
|
||
return fmt.Errorf("%s失败:%s", action, message)
|
||
}
|