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{})