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 }