...

Source file src/k8s.io/kubernetes/pkg/proxy/ipvs/ipset/ipset.go

Documentation: k8s.io/kubernetes/pkg/proxy/ipvs/ipset

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

View as plain text