k8s.io/kubernetes@v1.29.3/pkg/proxy/ipvs/ipset/ipset.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package ipset 18 19 import ( 20 "bytes" 21 "fmt" 22 "regexp" 23 "strconv" 24 "strings" 25 26 "k8s.io/klog/v2" 27 utilexec "k8s.io/utils/exec" 28 netutils "k8s.io/utils/net" 29 ) 30 31 var validationError = fmt.Errorf("failed to validate entry for ipset") 32 33 // Interface is an injectable interface for running ipset commands. Implementations must be goroutine-safe. 34 type Interface interface { 35 // FlushSet deletes all entries from a named set. 36 FlushSet(set string) error 37 // DestroySet deletes a named set. 38 DestroySet(set string) error 39 // DestroyAllSets deletes all sets. 40 DestroyAllSets() error 41 // CreateSet creates a new set. It will ignore error when the set already exists if ignoreExistErr=true. 42 CreateSet(set *IPSet, ignoreExistErr bool) error 43 // AddEntry adds a new entry to the named set. It will ignore error when the entry already exists if ignoreExistErr=true. 44 AddEntry(entry string, set *IPSet, ignoreExistErr bool) error 45 // DelEntry deletes one entry from the named set 46 DelEntry(entry string, set string) error 47 // Test test if an entry exists in the named set 48 TestEntry(entry string, set string) (bool, error) 49 // ListEntries lists all the entries from a named set 50 ListEntries(set string) ([]string, error) 51 // ListSets list all set names from kernel 52 ListSets() ([]string, error) 53 // GetVersion returns the "X.Y" version string for ipset. 54 GetVersion() (string, error) 55 } 56 57 // IPSetCmd represents the ipset util. We use ipset command for ipset execute. 58 const IPSetCmd = "ipset" 59 60 // EntryMemberPattern is the regular expression pattern of ipset member list. 61 // The raw output of ipset command `ipset list {set}` is similar to, 62 // Name: foobar 63 // Type: hash:ip,port 64 // Revision: 2 65 // Header: family inet hashsize 1024 maxelem 65536 66 // Size in memory: 16592 67 // References: 0 68 // Members: 69 // 192.168.1.2,tcp:8080 70 // 192.168.1.1,udp:53 71 var EntryMemberPattern = "(?m)^(.*\n)*Members:\n" 72 73 // VersionPattern is the regular expression pattern of ipset version string. 74 // ipset version output is similar to "v6.10". 75 var VersionPattern = "v[0-9]+\\.[0-9]+" 76 77 // IPSet implements an Interface to a set. 78 type IPSet struct { 79 // Name is the set name. 80 Name string 81 // SetType specifies the ipset type. 82 SetType Type 83 // HashFamily specifies the protocol family of the IP addresses to be stored in the set. 84 // The default is inet, i.e IPv4. If users want to use IPv6, they should specify inet6. 85 HashFamily string 86 // HashSize specifies the hash table size of ipset. 87 HashSize int 88 // MaxElem specifies the max element number of ipset. 89 MaxElem int 90 // PortRange specifies the port range of bitmap:port type ipset. 91 PortRange string 92 // comment message for ipset 93 Comment string 94 } 95 96 // Validate checks if a given ipset is valid or not. 97 func (set *IPSet) Validate() error { 98 // Check if protocol is valid for `HashIPPort`, `HashIPPortIP` and `HashIPPortNet` type set. 99 if set.SetType == HashIPPort || set.SetType == HashIPPortIP || set.SetType == HashIPPortNet { 100 if err := validateHashFamily(set.HashFamily); err != nil { 101 return err 102 } 103 } 104 // check set type 105 if err := validateIPSetType(set.SetType); err != nil { 106 return err 107 } 108 // check port range for bitmap type set 109 if set.SetType == BitmapPort { 110 if err := validatePortRange(set.PortRange); err != nil { 111 return err 112 } 113 } 114 // check hash size value of ipset 115 if set.HashSize <= 0 { 116 return fmt.Errorf("invalid HashSize: %d", set.HashSize) 117 } 118 // check max elem value of ipset 119 if set.MaxElem <= 0 { 120 return fmt.Errorf("invalid MaxElem %d", set.MaxElem) 121 } 122 123 return nil 124 } 125 126 // setIPSetDefaults sets some IPSet fields if not present to their default values. 127 func (set *IPSet) setIPSetDefaults() { 128 // Setting default values if not present 129 if set.HashSize == 0 { 130 set.HashSize = 1024 131 } 132 if set.MaxElem == 0 { 133 set.MaxElem = 65536 134 } 135 // Default protocol is IPv4 136 if set.HashFamily == "" { 137 set.HashFamily = ProtocolFamilyIPV4 138 } 139 // Default ipset type is "hash:ip,port" 140 if len(set.SetType) == 0 { 141 set.SetType = HashIPPort 142 } 143 if len(set.PortRange) == 0 { 144 set.PortRange = DefaultPortRange 145 } 146 } 147 148 // Entry represents a ipset entry. 149 type Entry struct { 150 // IP is the entry's IP. The IP address protocol corresponds to the HashFamily of IPSet. 151 // All entries' IP addresses in the same ip set has same the protocol, IPv4 or IPv6. 152 IP string 153 // Port is the entry's Port. 154 Port int 155 // Protocol is the entry's Protocol. The protocols of entries in the same ip set are all 156 // the same. The accepted protocols are TCP, UDP and SCTP. 157 Protocol string 158 // Net is the entry's IP network address. Network address with zero prefix size can NOT 159 // be stored. 160 Net string 161 // IP2 is the entry's second IP. IP2 may not be empty for `hash:ip,port,ip` type ip set. 162 IP2 string 163 // SetType is the type of ipset where the entry exists. 164 SetType Type 165 } 166 167 // Validate checks if a given ipset entry is valid or not. The set parameter is the ipset that entry belongs to. 168 func (e *Entry) Validate(set *IPSet) bool { 169 if e.Port < 0 { 170 klog.ErrorS(validationError, "port number should be >=0", "entry", e, "port", e.Port, "ipset", set) 171 return false 172 } 173 switch e.SetType { 174 case HashIP: 175 //check if IP of Entry is valid. 176 if valid := e.checkIP(set); !valid { 177 return false 178 } 179 case HashIPPort: 180 //check if IP and Protocol of Entry is valid. 181 if valid := e.checkIPandProtocol(set); !valid { 182 return false 183 } 184 case HashIPPortIP: 185 //check if IP and Protocol of Entry is valid. 186 if valid := e.checkIPandProtocol(set); !valid { 187 return false 188 } 189 190 // IP2 can not be empty for `hash:ip,port,ip` type ip set 191 if netutils.ParseIPSloppy(e.IP2) == nil { 192 klog.ErrorS(validationError, "error parsing second ip address", "entry", e, "ip", e.IP2, "ipset", set) 193 return false 194 } 195 case HashIPPortNet: 196 //check if IP and Protocol of Entry is valid. 197 if valid := e.checkIPandProtocol(set); !valid { 198 return false 199 } 200 201 // Net can not be empty for `hash:ip,port,net` type ip set 202 if _, ipNet, err := netutils.ParseCIDRSloppy(e.Net); ipNet == nil { 203 klog.ErrorS(err, "error parsing ip net", "entry", e, "net", e.Net, "set", set) 204 return false 205 } 206 case BitmapPort: 207 // check if port number satisfies its ipset's requirement of port range 208 if set == nil { 209 klog.ErrorS(validationError, "unable to reference ip set where the entry exists", "entry", e) 210 return false 211 } 212 begin, end, err := parsePortRange(set.PortRange) 213 if err != nil { 214 klog.ErrorS(err, "failed to parse set port range", "ipset", set, "portRange", set.PortRange) 215 return false 216 } 217 if e.Port < begin || e.Port > end { 218 klog.ErrorS(validationError, "port number is not in the port range of its ipset", "entry", e, "port", e.Port, "portRange", set.PortRange, "ipset", set) 219 return false 220 } 221 } 222 223 return true 224 } 225 226 // String returns the string format for ipset entry. 227 func (e *Entry) String() string { 228 switch e.SetType { 229 case HashIP: 230 // Entry{192.168.1.1} -> 192.168.1.1 231 return fmt.Sprintf("%s", e.IP) 232 case HashIPPort: 233 // Entry{192.168.1.1, udp, 53} -> 192.168.1.1,udp:53 234 // Entry{192.168.1.2, tcp, 8080} -> 192.168.1.2,tcp:8080 235 return fmt.Sprintf("%s,%s:%s", e.IP, e.Protocol, strconv.Itoa(e.Port)) 236 case HashIPPortIP: 237 // Entry{192.168.1.1, udp, 53, 10.0.0.1} -> 192.168.1.1,udp:53,10.0.0.1 238 // Entry{192.168.1.2, tcp, 8080, 192.168.1.2} -> 192.168.1.2,tcp:8080,192.168.1.2 239 return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.IP2) 240 case HashIPPortNet: 241 // Entry{192.168.1.2, udp, 80, 10.0.1.0/24} -> 192.168.1.2,udp:80,10.0.1.0/24 242 // Entry{192.168.2,25, tcp, 8080, 10.1.0.0/16} -> 192.168.2,25,tcp:8080,10.1.0.0/16 243 return fmt.Sprintf("%s,%s:%s,%s", e.IP, e.Protocol, strconv.Itoa(e.Port), e.Net) 244 case BitmapPort: 245 // Entry{53} -> 53 246 // Entry{8080} -> 8080 247 return strconv.Itoa(e.Port) 248 } 249 return "" 250 } 251 252 // checkIPandProtocol checks if IP and Protocol of Entry is valid. 253 func (e *Entry) checkIPandProtocol(set *IPSet) bool { 254 // set default protocol to tcp if empty 255 if len(e.Protocol) == 0 { 256 e.Protocol = ProtocolTCP 257 } else if !validateProtocol(e.Protocol) { 258 return false 259 } 260 return e.checkIP(set) 261 } 262 263 // checkIP checks if IP of Entry is valid. 264 func (e *Entry) checkIP(set *IPSet) bool { 265 if netutils.ParseIPSloppy(e.IP) == nil { 266 klog.ErrorS(validationError, "error parsing ip address", "entry", e, "ip", e.IP, "ipset", set) 267 return false 268 } 269 270 return true 271 } 272 273 type runner struct { 274 exec utilexec.Interface 275 } 276 277 // New returns a new Interface which will exec ipset. 278 func New(exec utilexec.Interface) Interface { 279 return &runner{ 280 exec: exec, 281 } 282 } 283 284 // CreateSet creates a new set, it will ignore error when the set already exists if ignoreExistErr=true. 285 func (runner *runner) CreateSet(set *IPSet, ignoreExistErr bool) error { 286 // sets some IPSet fields if not present to their default values. 287 set.setIPSetDefaults() 288 289 // Validate ipset before creating 290 if err := set.Validate(); err != nil { 291 return err 292 } 293 return runner.createSet(set, ignoreExistErr) 294 } 295 296 // If ignoreExistErr is set to true, then the -exist option of ipset will be specified, ipset ignores the error 297 // otherwise raised when the same set (setname and create parameters are identical) already exists. 298 func (runner *runner) createSet(set *IPSet, ignoreExistErr bool) error { 299 args := []string{"create", set.Name, string(set.SetType)} 300 if set.SetType == HashIPPortIP || set.SetType == HashIPPort || set.SetType == HashIPPortNet || set.SetType == HashIP { 301 args = append(args, 302 "family", set.HashFamily, 303 "hashsize", strconv.Itoa(set.HashSize), 304 "maxelem", strconv.Itoa(set.MaxElem), 305 ) 306 } 307 if set.SetType == BitmapPort { 308 args = append(args, "range", set.PortRange) 309 } 310 if ignoreExistErr { 311 args = append(args, "-exist") 312 } 313 if _, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil { 314 return fmt.Errorf("error creating ipset %s, error: %v", set.Name, err) 315 } 316 return nil 317 } 318 319 // AddEntry adds a new entry to the named set. 320 // If the -exist option is specified, ipset ignores the error otherwise raised when 321 // the same set (setname and create parameters are identical) already exists. 322 func (runner *runner) AddEntry(entry string, set *IPSet, ignoreExistErr bool) error { 323 args := []string{"add", set.Name, entry} 324 if ignoreExistErr { 325 args = append(args, "-exist") 326 } 327 if out, err := runner.exec.Command(IPSetCmd, args...).CombinedOutput(); err != nil { 328 return fmt.Errorf("error adding entry %s, error: %v (%s)", entry, err, out) 329 } 330 return nil 331 } 332 333 // DelEntry is used to delete the specified entry from the set. 334 func (runner *runner) DelEntry(entry string, set string) error { 335 if out, err := runner.exec.Command(IPSetCmd, "del", set, entry).CombinedOutput(); err != nil { 336 return fmt.Errorf("error deleting entry %s: from set: %s, error: %v (%s)", entry, set, err, out) 337 } 338 return nil 339 } 340 341 // TestEntry is used to check whether the specified entry is in the set or not. 342 func (runner *runner) TestEntry(entry string, set string) (bool, error) { 343 if out, err := runner.exec.Command(IPSetCmd, "test", set, entry).CombinedOutput(); err == nil { 344 reg, e := regexp.Compile("is NOT in set " + set) 345 if e == nil && reg.MatchString(string(out)) { 346 return false, nil 347 } else if e == nil { 348 return true, nil 349 } else { 350 return false, fmt.Errorf("error testing entry: %s, error: %v", entry, e) 351 } 352 } else { 353 return false, fmt.Errorf("error testing entry %s: %v (%s)", entry, err, out) 354 } 355 } 356 357 // FlushSet deletes all entries from a named set. 358 func (runner *runner) FlushSet(set string) error { 359 if _, err := runner.exec.Command(IPSetCmd, "flush", set).CombinedOutput(); err != nil { 360 return fmt.Errorf("error flushing set: %s, error: %v", set, err) 361 } 362 return nil 363 } 364 365 // DestroySet is used to destroy a named set. 366 func (runner *runner) DestroySet(set string) error { 367 if out, err := runner.exec.Command(IPSetCmd, "destroy", set).CombinedOutput(); err != nil { 368 return fmt.Errorf("error destroying set %s, error: %v(%s)", set, err, out) 369 } 370 return nil 371 } 372 373 // DestroyAllSets is used to destroy all sets. 374 func (runner *runner) DestroyAllSets() error { 375 if _, err := runner.exec.Command(IPSetCmd, "destroy").CombinedOutput(); err != nil { 376 return fmt.Errorf("error destroying all sets, error: %v", err) 377 } 378 return nil 379 } 380 381 // ListSets list all set names from kernel 382 func (runner *runner) ListSets() ([]string, error) { 383 out, err := runner.exec.Command(IPSetCmd, "list", "-n").CombinedOutput() 384 if err != nil { 385 return nil, fmt.Errorf("error listing all sets, error: %v", err) 386 } 387 return strings.Split(string(out), "\n"), nil 388 } 389 390 // ListEntries lists all the entries from a named set. 391 func (runner *runner) ListEntries(set string) ([]string, error) { 392 if len(set) == 0 { 393 return nil, fmt.Errorf("set name can't be nil") 394 } 395 out, err := runner.exec.Command(IPSetCmd, "list", set).CombinedOutput() 396 if err != nil { 397 return nil, fmt.Errorf("error listing set: %s, error: %v", set, err) 398 } 399 memberMatcher := regexp.MustCompile(EntryMemberPattern) 400 list := memberMatcher.ReplaceAllString(string(out[:]), "") 401 strs := strings.Split(list, "\n") 402 results := make([]string, 0) 403 for i := range strs { 404 if len(strs[i]) > 0 { 405 results = append(results, strs[i]) 406 } 407 } 408 return results, nil 409 } 410 411 // GetVersion returns the version string. 412 func (runner *runner) GetVersion() (string, error) { 413 return getIPSetVersionString(runner.exec) 414 } 415 416 // getIPSetVersionString runs "ipset --version" to get the version string 417 // in the form of "X.Y", i.e "6.19" 418 func getIPSetVersionString(exec utilexec.Interface) (string, error) { 419 cmd := exec.Command(IPSetCmd, "--version") 420 cmd.SetStdin(bytes.NewReader([]byte{})) 421 bytes, err := cmd.CombinedOutput() 422 if err != nil { 423 return "", err 424 } 425 versionMatcher := regexp.MustCompile(VersionPattern) 426 match := versionMatcher.FindStringSubmatch(string(bytes)) 427 if match == nil { 428 return "", fmt.Errorf("no ipset version found in string: %s", bytes) 429 } 430 return match[0], nil 431 } 432 433 // checks if port range is valid. The begin port number is not necessarily less than 434 // end port number - ipset util can accept it. It means both 1-100 and 100-1 are valid. 435 func validatePortRange(portRange string) error { 436 strs := strings.Split(portRange, "-") 437 if len(strs) != 2 { 438 return fmt.Errorf("invalid PortRange: %q", portRange) 439 } 440 for i := range strs { 441 num, err := strconv.Atoi(strs[i]) 442 if err != nil { 443 return fmt.Errorf("invalid PortRange: %q", portRange) 444 } 445 if num < 0 { 446 return fmt.Errorf("invalid PortRange: %q", portRange) 447 } 448 } 449 return nil 450 } 451 452 // checks if the given ipset type is valid. 453 func validateIPSetType(set Type) error { 454 for _, valid := range ValidIPSetTypes { 455 if set == valid { 456 return nil 457 } 458 } 459 return fmt.Errorf("unsupported SetType: %q", set) 460 } 461 462 // checks if given hash family is supported in ipset 463 func validateHashFamily(family string) error { 464 if family == ProtocolFamilyIPV4 || family == ProtocolFamilyIPV6 { 465 return nil 466 } 467 return fmt.Errorf("unsupported HashFamily %q", family) 468 } 469 470 // IsNotFoundError returns true if the error indicates "not found". It parses 471 // the error string looking for known values, which is imperfect but works in 472 // practice. 473 func IsNotFoundError(err error) bool { 474 es := err.Error() 475 if strings.Contains(es, "does not exist") { 476 // set with the same name already exists 477 // xref: https://github.com/Olipro/ipset/blob/master/lib/errcode.c#L32-L33 478 return true 479 } 480 if strings.Contains(es, "element is missing") { 481 // entry is missing from the set 482 // xref: https://github.com/Olipro/ipset/blob/master/lib/parse.c#L1904 483 // https://github.com/Olipro/ipset/blob/master/lib/parse.c#L1925 484 return true 485 } 486 return false 487 } 488 489 // checks if given protocol is supported in entry 490 func validateProtocol(protocol string) bool { 491 if protocol == ProtocolTCP || protocol == ProtocolUDP || protocol == ProtocolSCTP { 492 return true 493 } 494 klog.ErrorS(validationError, "invalid protocol", "protocol", protocol, "supportedProtocols", []string{ProtocolTCP, ProtocolUDP, ProtocolSCTP}) 495 return false 496 } 497 498 // parsePortRange parse the begin and end port from a raw string(format: a-b). beginPort <= endPort 499 // in the return value. 500 func parsePortRange(portRange string) (beginPort int, endPort int, err error) { 501 if len(portRange) == 0 { 502 portRange = DefaultPortRange 503 } 504 505 strs := strings.Split(portRange, "-") 506 if len(strs) != 2 { 507 // port number -1 indicates invalid 508 return -1, -1, fmt.Errorf("port range should be in the format of `a-b`") 509 } 510 for i := range strs { 511 num, err := strconv.Atoi(strs[i]) 512 if err != nil { 513 // port number -1 indicates invalid 514 return -1, -1, err 515 } 516 if num < 0 { 517 // port number -1 indicates invalid 518 return -1, -1, fmt.Errorf("port number %d should be >=0", num) 519 } 520 if i == 0 { 521 beginPort = num 522 continue 523 } 524 endPort = num 525 // switch when first port number > second port number 526 if beginPort > endPort { 527 endPort = beginPort 528 beginPort = num 529 } 530 } 531 return beginPort, endPort, nil 532 } 533 534 var _ = Interface(&runner{})