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