github.com/TeaOSLab/EdgeNode@v1.3.8/internal/firewalls/firewall_firewalld.go (about)

     1  // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
     2  
     3  package firewalls
     4  
     5  import (
     6  	"fmt"
     7  	"github.com/TeaOSLab/EdgeNode/internal/conns"
     8  	"github.com/TeaOSLab/EdgeNode/internal/goman"
     9  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    10  	executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
    11  	"github.com/iwind/TeaGo/types"
    12  	"strings"
    13  	"time"
    14  )
    15  
    16  type firewalldCmd struct {
    17  	cmd    *executils.Cmd
    18  	denyIP string
    19  }
    20  
    21  type Firewalld struct {
    22  	BaseFirewall
    23  
    24  	isReady  bool
    25  	exe      string
    26  	cmdQueue chan *firewalldCmd
    27  }
    28  
    29  func NewFirewalld() *Firewalld {
    30  	var firewalld = &Firewalld{
    31  		cmdQueue: make(chan *firewalldCmd, 4096),
    32  	}
    33  
    34  	path, err := executils.LookPath("firewall-cmd")
    35  	if err == nil && len(path) > 0 {
    36  		var cmd = executils.NewTimeoutCmd(3*time.Second, path, "--state")
    37  		err := cmd.Run()
    38  		if err == nil {
    39  			firewalld.exe = path
    40  			// TODO check firewalld status with 'firewall-cmd --state' (running or not running),
    41  			//      but we should recover the state when firewalld state changes, maybe check it every minutes
    42  
    43  			firewalld.isReady = true
    44  			firewalld.init()
    45  		}
    46  	}
    47  
    48  	return firewalld
    49  }
    50  
    51  func (this *Firewalld) init() {
    52  	goman.New(func() {
    53  		for c := range this.cmdQueue {
    54  			var cmd = c.cmd
    55  			err := cmd.Run()
    56  			if err != nil {
    57  				if strings.HasPrefix(err.Error(), "Warning:") {
    58  					continue
    59  				}
    60  				remotelogs.Warn("FIREWALL", "run command failed '"+cmd.String()+"': "+err.Error())
    61  			} else {
    62  				// 关闭连接
    63  				if len(c.denyIP) > 0 {
    64  					conns.SharedMap.CloseIPConns(c.denyIP)
    65  				}
    66  			}
    67  		}
    68  	})
    69  }
    70  
    71  // Name 名称
    72  func (this *Firewalld) Name() string {
    73  	return "firewalld"
    74  }
    75  
    76  func (this *Firewalld) IsReady() bool {
    77  	return this.isReady
    78  }
    79  
    80  // IsMock 是否为模拟
    81  func (this *Firewalld) IsMock() bool {
    82  	return false
    83  }
    84  
    85  func (this *Firewalld) AllowPort(port int, protocol string) error {
    86  	if !this.isReady {
    87  		return nil
    88  	}
    89  	var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+types.String(port)+"/"+protocol)
    90  	this.pushCmd(cmd, "")
    91  	return nil
    92  }
    93  
    94  func (this *Firewalld) AllowPortRangesPermanently(portRanges [][2]int, protocol string) error {
    95  	for _, portRange := range portRanges {
    96  		var port = this.PortRangeString(portRange, protocol)
    97  
    98  		{
    99  			var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port, "--permanent")
   100  			this.pushCmd(cmd, "")
   101  		}
   102  
   103  		{
   104  			var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--add-port="+port)
   105  			this.pushCmd(cmd, "")
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func (this *Firewalld) RemovePort(port int, protocol string) error {
   113  	if !this.isReady {
   114  		return nil
   115  	}
   116  	var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+types.String(port)+"/"+protocol)
   117  	this.pushCmd(cmd, "")
   118  	return nil
   119  }
   120  
   121  func (this *Firewalld) RemovePortRangePermanently(portRange [2]int, protocol string) error {
   122  	var port = this.PortRangeString(portRange, protocol)
   123  
   124  	{
   125  		var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port, "--permanent")
   126  		this.pushCmd(cmd, "")
   127  	}
   128  
   129  	{
   130  		var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, "--remove-port="+port)
   131  		this.pushCmd(cmd, "")
   132  	}
   133  
   134  	return nil
   135  }
   136  
   137  func (this *Firewalld) PortRangeString(portRange [2]int, protocol string) string {
   138  	if portRange[0] == portRange[1] {
   139  		return types.String(portRange[0]) + "/" + protocol
   140  	} else {
   141  		return types.String(portRange[0]) + "-" + types.String(portRange[1]) + "/" + protocol
   142  	}
   143  }
   144  
   145  func (this *Firewalld) RejectSourceIP(ip string, timeoutSeconds int) error {
   146  	if !this.isReady {
   147  		return nil
   148  	}
   149  
   150  	// 避免短时间内重复添加
   151  	if this.checkLatestIP(ip) {
   152  		return nil
   153  	}
   154  
   155  	var family = "ipv4"
   156  	if strings.Contains(ip, ":") {
   157  		family = "ipv6"
   158  	}
   159  	var args = []string{"--add-rich-rule=rule family='" + family + "' source address='" + ip + "' reject"}
   160  	if timeoutSeconds > 0 {
   161  		args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
   162  	}
   163  	var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
   164  	this.pushCmd(cmd, ip)
   165  	return nil
   166  }
   167  
   168  func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int, async bool) error {
   169  	if !this.isReady {
   170  		return nil
   171  	}
   172  
   173  	// 避免短时间内重复添加
   174  	if async && this.checkLatestIP(ip) {
   175  		return nil
   176  	}
   177  
   178  	var family = "ipv4"
   179  	if strings.Contains(ip, ":") {
   180  		family = "ipv6"
   181  	}
   182  	var args = []string{"--add-rich-rule=rule family='" + family + "' source address='" + ip + "' drop"}
   183  	if timeoutSeconds > 0 {
   184  		args = append(args, "--timeout="+types.String(timeoutSeconds)+"s")
   185  	}
   186  	var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
   187  	if async {
   188  		this.pushCmd(cmd, ip)
   189  		return nil
   190  	}
   191  
   192  	// 关闭连接
   193  	defer conns.SharedMap.CloseIPConns(ip)
   194  
   195  	err := cmd.Run()
   196  	if err != nil {
   197  		return fmt.Errorf("run command failed '%s': %w", cmd.String(), err)
   198  	}
   199  	return nil
   200  }
   201  
   202  func (this *Firewalld) RemoveSourceIP(ip string) error {
   203  	if !this.isReady {
   204  		return nil
   205  	}
   206  
   207  	var family = "ipv4"
   208  	if strings.Contains(ip, ":") {
   209  		family = "ipv6"
   210  	}
   211  	for _, action := range []string{"reject", "drop"} {
   212  		var args = []string{"--remove-rich-rule=rule family='" + family + "' source address='" + ip + "' " + action}
   213  		var cmd = executils.NewTimeoutCmd(10*time.Second, this.exe, args...)
   214  		this.pushCmd(cmd, "")
   215  	}
   216  	return nil
   217  }
   218  
   219  func (this *Firewalld) pushCmd(cmd *executils.Cmd, denyIP string) {
   220  	select {
   221  	case this.cmdQueue <- &firewalldCmd{cmd: cmd, denyIP: denyIP}:
   222  	default:
   223  		// we discard the command
   224  	}
   225  }