istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/ipset/nldeps_linux.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ipset
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"net"
    21  	"net/netip"
    22  
    23  	"github.com/vishvananda/netlink"
    24  	"github.com/vishvananda/netlink/nl"
    25  	"golang.org/x/sys/unix"
    26  )
    27  
    28  func RealNlDeps() NetlinkIpsetDeps {
    29  	return &realDeps{}
    30  }
    31  
    32  type realDeps struct{}
    33  
    34  func (m *realDeps) ipsetIPHashCreate(name string, v6 bool) error {
    35  	var family uint8
    36  
    37  	if v6 {
    38  		family = unix.AF_INET6
    39  	} else {
    40  		family = unix.AF_INET
    41  	}
    42  	err := netlink.IpsetCreate(name, "hash:ip", netlink.IpsetCreateOptions{Comments: true, Replace: true, Family: family})
    43  	if ipsetErr, ok := err.(nl.IPSetError); ok && ipsetErr == nl.IPSET_ERR_EXIST {
    44  		return nil
    45  	}
    46  
    47  	return err
    48  }
    49  
    50  func (m *realDeps) destroySet(name string) error {
    51  	err := netlink.IpsetDestroy(name)
    52  	return err
    53  }
    54  
    55  func (m *realDeps) addIP(name string, ip netip.Addr, ipProto uint8, comment string, replace bool) error {
    56  	err := netlink.IpsetAdd(name, &netlink.IPSetEntry{
    57  		Comment:  comment,
    58  		IP:       net.IP(ip.AsSlice()),
    59  		Protocol: &ipProto,
    60  		Replace:  replace,
    61  	})
    62  	if err != nil {
    63  		return fmt.Errorf("failed to add IP %s with and proto %d to ipset %s: %w", ip, ipProto, name, err)
    64  	}
    65  	return nil
    66  }
    67  
    68  func (m *realDeps) deleteIP(name string, ip netip.Addr, ipProto uint8) error {
    69  	err := netlink.IpsetDel(name, &netlink.IPSetEntry{
    70  		IP:       net.IP(ip.AsSlice()),
    71  		Protocol: &ipProto,
    72  	})
    73  	if err != nil {
    74  		return fmt.Errorf("failed to delete IP %s with and proto %d from ipset %s: %w", ip, ipProto, name, err)
    75  	}
    76  	return nil
    77  }
    78  
    79  func (m *realDeps) flush(name string) error {
    80  	err := netlink.IpsetFlush(name)
    81  	if err != nil {
    82  		return fmt.Errorf("failed to flush ipset %s: %w", name, err)
    83  	}
    84  	return nil
    85  }
    86  
    87  // Alpine and some distros struggles with this - ipset CLI utilities support this, but
    88  // the kernel can be out of sync with the CLI utility, leading to errors like:
    89  //
    90  // ipset v7.10: Argument `comment' is supported in the kernel module of the set type hash:ip
    91  // starting from the revision 3 and you have installed revision 1 only.
    92  // Your kernel is behind your ipset utility.
    93  //
    94  // This happens with kernels as recent as Fedora38, e.g: 6.4.11-200.fc38.aarch64
    95  func (m *realDeps) clearEntriesWithComment(name, comment string) error {
    96  	res, err := netlink.IpsetList(name)
    97  	if err != nil {
    98  		return fmt.Errorf("failed to list ipset %s: %w", name, err)
    99  	}
   100  	for _, entry := range res.Entries {
   101  		if entry.Comment == comment {
   102  			err := netlink.IpsetDel(name, &entry)
   103  			if err != nil {
   104  				return fmt.Errorf("failed to delete IP %s from ipset %s: %w", entry.IP, name, err)
   105  			}
   106  		}
   107  	}
   108  	return nil
   109  }
   110  
   111  func (m *realDeps) clearEntriesWithIP(name string, ip netip.Addr) error {
   112  	delIP := net.IP(ip.AsSlice())
   113  	res, err := netlink.IpsetList(name)
   114  	if err != nil {
   115  		return fmt.Errorf("failed to list ipset %s: %w", name, err)
   116  	}
   117  
   118  	var delErrs []error
   119  
   120  	for _, entry := range res.Entries {
   121  		if entry.IP.Equal(delIP) {
   122  			err := netlink.IpsetDel(name, &entry)
   123  			if err != nil {
   124  				delErrs = append(delErrs, fmt.Errorf("failed to delete IP %s from ipset %s: %w", entry.IP, name, err))
   125  			}
   126  		}
   127  	}
   128  
   129  	return errors.Join(delErrs...)
   130  }
   131  
   132  func (m *realDeps) listEntriesByIP(name string) ([]netip.Addr, error) {
   133  	var ipList []netip.Addr
   134  
   135  	res, err := netlink.IpsetList(name)
   136  	if err != nil {
   137  		return ipList, fmt.Errorf("failed to list ipset %s: %w", name, err)
   138  	}
   139  
   140  	for _, entry := range res.Entries {
   141  		addr, _ := netip.AddrFromSlice(entry.IP)
   142  		ipList = append(ipList, addr)
   143  	}
   144  
   145  	return ipList, nil
   146  }