diff --git a/internal/firewalls/firewall_firewalld.go b/internal/firewalls/firewall_firewalld.go index a25b304..19d88a9 100644 --- a/internal/firewalls/firewall_firewalld.go +++ b/internal/firewalls/firewall_firewalld.go @@ -75,6 +75,24 @@ func (this *Firewalld) AllowPort(port int, protocol string) error { return nil } +func (this *Firewalld) AllowPortRangesPermanently(portRanges [][2]int, protocol string) error { + for _, portRange := range portRanges { + var port = this.PortRangeString(portRange, protocol) + + { + var cmd = exec.Command(this.exe, "--add-port="+port, "--permanent") + this.pushCmd(cmd) + } + + { + var cmd = exec.Command(this.exe, "--add-port="+port) + this.pushCmd(cmd) + } + } + + return nil +} + func (this *Firewalld) RemovePort(port int, protocol string) error { if !this.isReady { return nil @@ -84,6 +102,30 @@ func (this *Firewalld) RemovePort(port int, protocol string) error { return nil } +func (this *Firewalld) RemovePortRangePermanently(portRange [2]int, protocol string) error { + var port = this.PortRangeString(portRange, protocol) + + { + var cmd = exec.Command(this.exe, "--remove-port="+port, "--permanent") + this.pushCmd(cmd) + } + + { + var cmd = exec.Command(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 diff --git a/internal/nodes/listener_manager.go b/internal/nodes/listener_manager.go index 7ed8771..c76d216 100644 --- a/internal/nodes/listener_manager.go +++ b/internal/nodes/listener_manager.go @@ -5,11 +5,14 @@ import ( "errors" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" + "github.com/TeaOSLab/EdgeNode/internal/firewalls" "github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/remotelogs" + "github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/maps" + "github.com/iwind/TeaGo/types" "net/url" "os/exec" "regexp" @@ -31,15 +34,19 @@ type ListenerManager struct { retryListenerMap map[string]*Listener // 需要重试的监听器 addr => Listener ticker *time.Ticker - lastPortStrings string + firewalld *firewalls.Firewalld + lastPortStrings string + lastTCPPortRanges [][2]int + lastUDPPortRanges [][2]int } // NewListenerManager 获取新对象 func NewListenerManager() *ListenerManager { - manager := &ListenerManager{ + var manager = &ListenerManager{ listenersMap: map[string]*Listener{}, retryListenerMap: map[string]*Listener{}, ticker: time.NewTicker(1 * time.Minute), + firewalld: firewalls.NewFirewalld(), } // 提升测试效率 @@ -147,7 +154,7 @@ func (this *ListenerManager) Start(node *nodeconfigs.NodeConfig) error { } // 加入到firewalld - this.addToFirewalld(groupAddrs) + go this.addToFirewalld(groupAddrs) return nil } @@ -226,8 +233,14 @@ func (this *ListenerManager) addToFirewalld(groupAddrs []string) { return } + if this.firewalld == nil || !this.firewalld.IsReady() { + return + } + // 组合端口号 - var ports = []string{} + var portStrings = []string{} + var udpPorts = []int{} + var tcpPorts = []int{} for _, addr := range groupAddrs { var protocol = "tcp" if strings.HasPrefix(addr, "udp") { @@ -237,52 +250,72 @@ func (this *ListenerManager) addToFirewalld(groupAddrs []string) { var lastIndex = strings.LastIndex(addr, ":") if lastIndex > 0 { var portString = addr[lastIndex+1:] - ports = append(ports, portString+"/"+protocol) + portStrings = append(portStrings, portString+"/"+protocol) + + switch protocol { + case "tcp": + tcpPorts = append(tcpPorts, types.Int(portString)) + case "udp": + udpPorts = append(udpPorts, types.Int(portString)) + } } } - if len(ports) == 0 { + if len(portStrings) == 0 { return } // 检查是否有变化 - sort.Strings(ports) - var newPortStrings = strings.Join(ports, ",") + sort.Strings(portStrings) + var newPortStrings = strings.Join(portStrings, ",") if newPortStrings == this.lastPortStrings { return } this.lastPortStrings = newPortStrings - firewallCmd, err := exec.LookPath("firewall-cmd") - if err != nil || len(firewallCmd) == 0 { - return + remotelogs.Println("FIREWALLD", "opening ports automatically ...") + defer func() { + remotelogs.Println("FIREWALLD", "open ports successfully") + }() + + // 合并端口 + var tcpPortRanges = utils.MergePorts(tcpPorts) + var udpPortRanges = utils.MergePorts(udpPorts) + + defer func() { + this.lastTCPPortRanges = tcpPortRanges + this.lastUDPPortRanges = udpPortRanges + }() + + // 删除老的不存在的端口 + var tcpPortRangesMap = map[string]bool{} + var udpPortRangesMap = map[string]bool{} + for _, portRange := range tcpPortRanges { + tcpPortRangesMap[this.firewalld.PortRangeString(portRange, "tcp")] = true + } + for _, portRange := range udpPortRanges { + udpPortRangesMap[this.firewalld.PortRangeString(portRange, "udp")] = true } - // 检查状态 - err = exec.Command(firewallCmd, "--state").Run() - if err != nil { - return - } - - remotelogs.Println("FIREWALLD", "open ports automatically") - for _, port := range ports { - { - // TODO 需要支持sudo - var cmd = exec.Command(firewallCmd, "--add-port="+port, "--permanent") - err = cmd.Run() - if err != nil { - remotelogs.Warn("FIREWALLD", "'"+cmd.String()+"': "+err.Error()) - return - } - } - - { - // TODO 需要支持sudo - var cmd = exec.Command(firewallCmd, "--add-port="+port) - err = cmd.Run() - if err != nil { - remotelogs.Warn("FIREWALLD", "'"+cmd.String()+"': "+err.Error()) - return - } + for _, portRange := range this.lastTCPPortRanges { + var s = this.firewalld.PortRangeString(portRange, "tcp") + _, ok := tcpPortRangesMap[s] + if ok { + continue } + remotelogs.Println("FIREWALLD", "remove port '"+s+"'") + _ = this.firewalld.RemovePortRangePermanently(portRange, "tcp") } + for _, portRange := range this.lastUDPPortRanges { + var s = this.firewalld.PortRangeString(portRange, "udp") + _, ok := udpPortRangesMap[s] + if ok { + continue + } + remotelogs.Println("FIREWALLD", "remove port '"+s+"'") + _ = this.firewalld.RemovePortRangePermanently(portRange, "udp") + } + + // 添加新的 + _ = this.firewalld.AllowPortRangesPermanently(tcpPortRanges, "tcp") + _ = this.firewalld.AllowPortRangesPermanently(udpPortRanges, "udp") } diff --git a/internal/utils/net.go b/internal/utils/net.go index d702bc4..52112b9 100644 --- a/internal/utils/net.go +++ b/internal/utils/net.go @@ -7,6 +7,7 @@ import ( "context" "github.com/iwind/TeaGo/logs" "net" + "sort" "syscall" ) @@ -38,3 +39,34 @@ func ParseAddrHost(addr string) string { } return host } + +// MergePorts 聚合端口 +// 返回 [ [fromPort, toPort], ... ] +func MergePorts(ports []int) [][2]int { + if len(ports) == 0 { + return nil + } + + sort.Ints(ports) + + var result = [][2]int{} + var lastRange = [2]int{0, 0} + var lastPort = -1 + for _, port := range ports { + if port <= 0 /** 只处理有效的端口 **/ || port == lastPort /** 去重 **/ { + continue + } + + if lastPort < 0 || port != lastPort+1 { + lastRange = [2]int{port, port} + result = append(result, lastRange) + } else { // 如果是连续的 + lastRange[1] = port + result[len(result)-1] = lastRange + } + + lastPort = port + } + + return result +} diff --git a/internal/utils/net_test.go b/internal/utils/net_test.go index 513c668..3428ed3 100644 --- a/internal/utils/net_test.go +++ b/internal/utils/net_test.go @@ -12,3 +12,15 @@ func TestParseAddrHost(t *testing.T) { t.Log(addr + " => " + utils.ParseAddrHost(addr)) } } + +func TestMergePorts(t *testing.T) { + for _, ports := range [][]int{ + {}, + {80}, + {80, 83, 85}, + {80, 81, 83, 85, 86, 87, 88, 90}, + {0, 0, 1, 1, 2, 2, 2, 3, 3, 3}, + } { + t.Log(ports, "=>", utils.MergePorts(ports)) + } +}