github.com/TeaOSLab/EdgeNode@v1.3.8/internal/iplibrary/action_ipset.go (about) 1 package iplibrary 2 3 import ( 4 "errors" 5 "fmt" 6 "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" 7 "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" 8 executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec" 9 "github.com/iwind/TeaGo/types" 10 "runtime" 11 "strconv" 12 "strings" 13 "time" 14 ) 15 16 // IPSetAction IPSet动作 17 // 相关命令: 18 // - 利用Firewalld管理set: 19 // - 添加:firewall-cmd --permanent --new-ipset=edge_ip_list --type=hash:ip --option="timeout=0" 20 // - 删除:firewall-cmd --permanent --delete-ipset=edge_ip_list 21 // - 重载:firewall-cmd --reload 22 // - firewalld+ipset: firewall-cmd --permanent --add-rich-rule="rule source ipset='edge_ip_list' reject" 23 // - 利用IPTables管理set: 24 // - 添加:iptables -A INPUT -m set --match-set edge_ip_list src -j REJECT 25 // - 添加Item:ipset add edge_ip_list 192.168.2.32 timeout 30 26 // - 删除Item: ipset del edge_ip_list 192.168.2.32 27 // - 创建set:ipset create edge_ip_list hash:ip timeout 0 28 // - 查看统计:ipset -t list edge_black_list 29 // - 删除set:ipset destroy edge_black_list 30 type IPSetAction struct { 31 BaseAction 32 33 config *firewallconfigs.FirewallActionIPSetConfig 34 35 ipsetNotfound bool 36 } 37 38 func NewIPSetAction() *IPSetAction { 39 return &IPSetAction{} 40 } 41 42 func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) error { 43 this.config = &firewallconfigs.FirewallActionIPSetConfig{} 44 err := this.convertParams(config.Params, this.config) 45 if err != nil { 46 return err 47 } 48 49 if len(this.config.WhiteName) == 0 { 50 return NewFataError("white list name should not be empty") 51 } 52 if len(this.config.BlackName) == 0 { 53 return NewFataError("black list name should not be empty") 54 } 55 56 // 创建ipset 57 { 58 path, err := executils.LookPath("ipset") 59 if err != nil { 60 return err 61 } 62 63 // ipv4 64 for _, listName := range []string{this.config.WhiteName, this.config.BlackName} { 65 if len(listName) == 0 { 66 continue 67 } 68 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "timeout", "0", "maxelem", "1000000") 69 cmd.WithStderr() 70 err := cmd.Run() 71 if err != nil { 72 var output = cmd.Stderr() 73 if !strings.Contains(output, "already exists") { 74 return fmt.Errorf("create ipset '%s': %w, output: %s", listName, err, output) 75 } else { 76 err = nil 77 } 78 } 79 } 80 81 // ipv6 82 for _, listName := range []string{this.config.WhiteNameIPv6, this.config.BlackNameIPv6} { 83 if len(listName) == 0 { 84 continue 85 } 86 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "create", listName, "hash:ip", "family", "inet6", "timeout", "0", "maxelem", "1000000") 87 cmd.WithStderr() 88 err := cmd.Run() 89 if err != nil { 90 var output = cmd.Stderr() 91 if !strings.Contains(output, "already exists") { 92 return fmt.Errorf("create ipset '%s': %w, output: %s", listName, err, output) 93 } else { 94 err = nil 95 } 96 } 97 } 98 } 99 100 // firewalld 101 if this.config.AutoAddToFirewalld { 102 path, err := executils.LookPath("firewall-cmd") 103 if err != nil { 104 return err 105 } 106 107 // ipv4 108 for _, listName := range []string{this.config.WhiteName, this.config.BlackName} { 109 if len(listName) == 0 { 110 continue 111 } 112 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=timeout=0", "--option=maxelem=1000000") 113 cmd.WithStderr() 114 err := cmd.Run() 115 if err != nil { 116 var output = cmd.Stderr() 117 if strings.Contains(output, "NAME_CONFLICT") { 118 err = nil 119 } else { 120 return fmt.Errorf("firewall-cmd add ipset '%s': %w, output: %s", listName, err, output) 121 } 122 } 123 } 124 125 // ipv6 126 for _, listName := range []string{this.config.WhiteNameIPv6, this.config.BlackNameIPv6} { 127 if len(listName) == 0 { 128 continue 129 } 130 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--new-ipset="+listName, "--type=hash:ip", "--option=family=inet6", "--option=timeout=0", "--option=maxelem=1000000") 131 cmd.WithStderr() 132 err := cmd.Run() 133 if err != nil { 134 var output = cmd.Stderr() 135 if strings.Contains(output, "NAME_CONFLICT") { 136 err = nil 137 } else { 138 return fmt.Errorf("firewall-cmd add ipset '%s': %w, output: %s", listName, err, output) 139 } 140 } 141 } 142 143 // accept 144 for _, listName := range []string{this.config.WhiteName, this.config.WhiteNameIPv6} { 145 if len(listName) == 0 { 146 continue 147 } 148 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' accept") 149 cmd.WithStderr() 150 err := cmd.Run() 151 if err != nil { 152 return fmt.Errorf("firewall-cmd add rich rule '%s': %w, output: %s", listName, err, cmd.Stderr()) 153 } 154 } 155 156 // reject 157 for _, listName := range []string{this.config.BlackName, this.config.BlackNameIPv6} { 158 if len(listName) == 0 { 159 continue 160 } 161 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--permanent", "--add-rich-rule=rule source ipset='"+listName+"' reject") 162 cmd.WithStderr() 163 err := cmd.Run() 164 if err != nil { 165 return fmt.Errorf("firewall-cmd add rich rule '%s': %w, output: %s", listName, err, cmd.Stderr()) 166 } 167 } 168 169 // reload 170 { 171 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "--reload") 172 cmd.WithStderr() 173 err := cmd.Run() 174 if err != nil { 175 return fmt.Errorf("firewall-cmd reload: %w, output: %s", err, cmd.Stderr()) 176 } 177 } 178 } 179 180 // iptables 181 if this.config.AutoAddToIPTables { 182 path, err := executils.LookPath("iptables") 183 if err != nil { 184 return err 185 } 186 187 // accept 188 for _, listName := range []string{this.config.WhiteName, this.config.WhiteNameIPv6} { 189 if len(listName) == 0 { 190 continue 191 } 192 193 // 检查规则是否存在 194 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT") 195 err := cmd.Run() 196 var exists = err == nil 197 198 // 添加规则 199 if !exists { 200 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "ACCEPT") 201 cmd.WithStderr() 202 err := cmd.Run() 203 if err != nil { 204 return fmt.Errorf("iptables add rule: %w, output: %s", err, cmd.Stderr()) 205 } 206 } 207 } 208 209 // reject 210 for _, listName := range []string{this.config.BlackName, this.config.BlackNameIPv6} { 211 if len(listName) == 0 { 212 continue 213 } 214 215 // 检查规则是否存在 216 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-C", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT") 217 err := cmd.Run() 218 var exists = err == nil 219 220 if !exists { 221 var cmd = executils.NewTimeoutCmd(30*time.Second, path, "-A", "INPUT", "-m", "set", "--match-set", listName, "src", "-j", "REJECT") 222 cmd.WithStderr() 223 err := cmd.Run() 224 if err != nil { 225 return fmt.Errorf("iptables add rule: %w, output: %s", err, cmd.Stderr()) 226 } 227 } 228 } 229 } 230 231 return nil 232 } 233 234 func (this *IPSetAction) AddItem(listType IPListType, item *pb.IPItem) error { 235 return this.runAction("addItem", listType, item) 236 } 237 238 func (this *IPSetAction) DeleteItem(listType IPListType, item *pb.IPItem) error { 239 return this.runAction("deleteItem", listType, item) 240 } 241 242 func (this *IPSetAction) runAction(action string, listType IPListType, item *pb.IPItem) error { 243 if item.Type == "all" { 244 return nil 245 } 246 if len(item.IpTo) == 0 { 247 return this.runActionSingleIP(action, listType, item) 248 } 249 cidrList, err := iPv4RangeToCIDRRange(item.IpFrom, item.IpTo) 250 if err != nil { 251 // 不合法的范围不予处理即可 252 return nil 253 } 254 if len(cidrList) == 0 { 255 return nil 256 } 257 for _, cidr := range cidrList { 258 var index = strings.Index(cidr, "/") 259 if index <= 0 { 260 continue 261 } 262 263 // 只支持/24以下的 264 if types.Int(cidr[index+1:]) < 24 { 265 continue 266 } 267 268 item.IpFrom = cidr 269 item.IpTo = "" 270 err := this.runActionSingleIP(action, listType, item) 271 if err != nil { 272 return err 273 } 274 } 275 return nil 276 } 277 278 func (this *IPSetAction) SetConfig(config *firewallconfigs.FirewallActionIPSetConfig) { 279 this.config = config 280 } 281 282 func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, item *pb.IPItem) error { 283 if item.Type == "all" { 284 return nil 285 } 286 287 var listName string 288 var isIPv6 = strings.Contains(item.IpFrom, ":") 289 290 switch listType { 291 case IPListTypeWhite: 292 if isIPv6 { 293 listName = this.config.WhiteNameIPv6 294 } else { 295 listName = this.config.WhiteName 296 } 297 case IPListTypeBlack: 298 if isIPv6 { 299 listName = this.config.BlackNameIPv6 300 } else { 301 listName = this.config.BlackName 302 } 303 default: 304 // 不支持的类型 305 return nil 306 } 307 if len(listName) == 0 { 308 return nil 309 } 310 311 var path = this.config.Path 312 var err error 313 if len(path) == 0 { 314 path, err = executils.LookPath("ipset") 315 if err != nil { 316 // 找不到ipset命令错误只提示一次 317 if this.ipsetNotfound { 318 return nil 319 } 320 this.ipsetNotfound = true 321 return err 322 } 323 } 324 325 // ipset add edge_ip_list 192.168.2.32 timeout 30 326 var args = []string{} 327 switch action { 328 case "addItem": 329 args = append(args, "add") 330 case "deleteItem": 331 args = append(args, "del") 332 } 333 334 args = append(args, listName, item.IpFrom) 335 if action == "addItem" { 336 var timestamp = time.Now().Unix() 337 if item.ExpiredAt > timestamp { 338 args = append(args, "timeout", strconv.FormatInt(item.ExpiredAt-timestamp, 10)) 339 } 340 } 341 342 if runtime.GOOS == "darwin" { 343 // MAC OS直接返回 344 return nil 345 } 346 347 var cmd = executils.NewTimeoutCmd(30*time.Second, path, args...) 348 cmd.WithStderr() 349 err = cmd.Run() 350 if err != nil { 351 var errString = cmd.Stderr() 352 if action == "deleteItem" && strings.Contains(errString, "not added") { 353 return nil 354 } 355 return errors.New(strings.TrimSpace(errString)) 356 } 357 return nil 358 }