github.com/containerd/nerdctl@v1.7.7/pkg/netutil/netutil.go (about)

     1  /*
     2     Copyright The containerd 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 netutil
    18  
    19  import (
    20  	"context"
    21  	"crypto/sha256"
    22  	"encoding/hex"
    23  	"encoding/json"
    24  	"fmt"
    25  	"net"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"sort"
    30  	"strconv"
    31  	"strings"
    32  
    33  	"github.com/containerd/containerd"
    34  	"github.com/containerd/containerd/namespaces"
    35  	"github.com/containerd/errdefs"
    36  	"github.com/containerd/log"
    37  	"github.com/containerd/nerdctl/pkg/labels"
    38  	"github.com/containerd/nerdctl/pkg/lockutil"
    39  	"github.com/containerd/nerdctl/pkg/netutil/nettype"
    40  	subnetutil "github.com/containerd/nerdctl/pkg/netutil/subnet"
    41  	"github.com/containerd/nerdctl/pkg/strutil"
    42  	"github.com/containernetworking/cni/libcni"
    43  )
    44  
    45  type CNIEnv struct {
    46  	Path        string
    47  	NetconfPath string
    48  }
    49  
    50  type CNIEnvOpt func(e *CNIEnv) error
    51  
    52  func UsedNetworks(ctx context.Context, client *containerd.Client) (map[string][]string, error) {
    53  	nsService := client.NamespaceService()
    54  	nsList, err := nsService.List(ctx)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	used := make(map[string][]string)
    59  	for _, ns := range nsList {
    60  		nsCtx := namespaces.WithNamespace(ctx, ns)
    61  		containers, err := client.Containers(nsCtx)
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  		nsUsedN, err := namespaceUsedNetworks(nsCtx, containers)
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  
    70  		// merge
    71  		for k, v := range nsUsedN {
    72  			if value, ok := used[k]; ok {
    73  				used[k] = append(value, v...)
    74  			} else {
    75  				used[k] = v
    76  			}
    77  		}
    78  	}
    79  	return used, nil
    80  }
    81  
    82  func namespaceUsedNetworks(ctx context.Context, containers []containerd.Container) (map[string][]string, error) {
    83  	used := make(map[string][]string)
    84  	for _, c := range containers {
    85  		// Only tasks under the ctx namespace can be obtained here
    86  		task, err := c.Task(ctx, nil)
    87  		if err != nil {
    88  			if errdefs.IsNotFound(err) {
    89  				continue
    90  			}
    91  			return nil, err
    92  		}
    93  		status, err := task.Status(ctx)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  		switch status.Status {
    98  		case containerd.Paused, containerd.Running:
    99  		default:
   100  			continue
   101  		}
   102  		l, err := c.Labels(ctx)
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  		networkJSON, ok := l[labels.Networks]
   107  		if !ok {
   108  			continue
   109  		}
   110  		var networks []string
   111  		if err := json.Unmarshal([]byte(networkJSON), &networks); err != nil {
   112  			return nil, err
   113  		}
   114  		netType, err := nettype.Detect(networks)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		if netType != nettype.CNI {
   119  			continue
   120  		}
   121  		for _, n := range networks {
   122  			used[n] = append(used[n], c.ID())
   123  		}
   124  	}
   125  	return used, nil
   126  }
   127  
   128  func WithDefaultNetwork() CNIEnvOpt {
   129  	return func(e *CNIEnv) error {
   130  		return e.ensureDefaultNetworkConfig()
   131  	}
   132  }
   133  
   134  func NewCNIEnv(cniPath, cniConfPath string, opts ...CNIEnvOpt) (*CNIEnv, error) {
   135  	e := CNIEnv{
   136  		Path:        cniPath,
   137  		NetconfPath: cniConfPath,
   138  	}
   139  	if err := os.MkdirAll(e.NetconfPath, 0755); err != nil {
   140  		return nil, err
   141  	}
   142  
   143  	for _, o := range opts {
   144  		if err := o(&e); err != nil {
   145  			return nil, err
   146  		}
   147  	}
   148  
   149  	return &e, nil
   150  }
   151  
   152  func (e *CNIEnv) NetworkList() ([]*NetworkConfig, error) {
   153  	return e.networkConfigList()
   154  }
   155  
   156  func (e *CNIEnv) NetworkMap() (map[string]*NetworkConfig, error) { //nolint:revive
   157  	networks, err := e.networkConfigList()
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	m := make(map[string]*NetworkConfig, len(networks))
   163  	for _, n := range networks {
   164  		if original, exists := m[n.Name]; exists {
   165  			log.L.Warnf("duplicate network name %q, %#v will get superseded by %#v", n.Name, original, n)
   166  		}
   167  		m[n.Name] = n
   168  		if n.NerdctlID != nil {
   169  			id := *n.NerdctlID
   170  			m[id] = n
   171  			if len(id) > 12 {
   172  				id = id[:12]
   173  				m[id] = n
   174  			}
   175  		}
   176  	}
   177  	return m, nil
   178  }
   179  
   180  func (e *CNIEnv) FilterNetworks(filterf func(*NetworkConfig) bool) ([]*NetworkConfig, error) {
   181  	networkConfigs, err := e.networkConfigList()
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	result := []*NetworkConfig{}
   186  	for _, networkConfig := range networkConfigs {
   187  		if filterf(networkConfig) {
   188  			result = append(result, networkConfig)
   189  		}
   190  	}
   191  	return result, nil
   192  }
   193  
   194  func (e *CNIEnv) getConfigPathForNetworkName(netName string) string {
   195  	return filepath.Join(e.NetconfPath, "nerdctl-"+netName+".conflist")
   196  }
   197  
   198  func (e *CNIEnv) usedSubnets() ([]*net.IPNet, error) {
   199  	usedSubnets, err := subnetutil.GetLiveNetworkSubnets()
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	networkConfigs, err := e.networkConfigList()
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	for _, net := range networkConfigs {
   208  		usedSubnets = append(usedSubnets, net.subnets()...)
   209  	}
   210  	return usedSubnets, nil
   211  }
   212  
   213  type NetworkConfig struct {
   214  	*libcni.NetworkConfigList
   215  	NerdctlID     *string
   216  	NerdctlLabels *map[string]string
   217  	File          string
   218  }
   219  
   220  type cniNetworkConfig struct {
   221  	CNIVersion string            `json:"cniVersion"`
   222  	Name       string            `json:"name"`
   223  	ID         string            `json:"nerdctlID"`
   224  	Labels     map[string]string `json:"nerdctlLabels"`
   225  	Plugins    []CNIPlugin       `json:"plugins"`
   226  }
   227  
   228  type CreateOptions struct {
   229  	Name        string
   230  	Driver      string
   231  	Options     map[string]string
   232  	IPAMDriver  string
   233  	IPAMOptions map[string]string
   234  	Subnets     []string
   235  	Gateway     string
   236  	IPRange     string
   237  	Labels      []string
   238  	IPv6        bool
   239  }
   240  
   241  func (e *CNIEnv) CreateNetwork(opts CreateOptions) (*NetworkConfig, error) { //nolint:revive
   242  	var net *NetworkConfig
   243  	netMap, err := e.NetworkMap()
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	if _, ok := netMap[opts.Name]; ok {
   249  		return nil, errdefs.ErrAlreadyExists
   250  	}
   251  
   252  	fn := func() error {
   253  		ipam, err := e.generateIPAM(opts.IPAMDriver, opts.Subnets, opts.Gateway, opts.IPRange, opts.IPAMOptions, opts.IPv6)
   254  		if err != nil {
   255  			return err
   256  		}
   257  		plugins, err := e.generateCNIPlugins(opts.Driver, opts.Name, ipam, opts.Options, opts.IPv6)
   258  		if err != nil {
   259  			return err
   260  		}
   261  		net, err = e.generateNetworkConfig(opts.Name, opts.Labels, plugins)
   262  		if err != nil {
   263  			return err
   264  		}
   265  		return e.writeNetworkConfig(net)
   266  	}
   267  	err = lockutil.WithDirLock(e.NetconfPath, fn)
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	return net, nil
   272  }
   273  
   274  func (e *CNIEnv) RemoveNetwork(net *NetworkConfig) error {
   275  	fn := func() error {
   276  		if err := os.RemoveAll(net.File); err != nil {
   277  			return err
   278  		}
   279  		return net.clean()
   280  	}
   281  	return lockutil.WithDirLock(e.NetconfPath, fn)
   282  }
   283  
   284  // GetDefaultNetworkConfig checks whether the default network exists
   285  // by first searching for if any network bears the `labels.NerdctlDefaultNetwork`
   286  // label, or falls back to checking whether any network bears the
   287  // `DefaultNetworkName` name.
   288  func (e *CNIEnv) GetDefaultNetworkConfig() (*NetworkConfig, error) {
   289  	// Search for networks bearing the `labels.NerdctlDefaultNetwork` label.
   290  	defaultLabelFilterF := func(nc *NetworkConfig) bool {
   291  		if nc.NerdctlLabels == nil {
   292  			return false
   293  		} else if _, ok := (*nc.NerdctlLabels)[labels.NerdctlDefaultNetwork]; ok {
   294  			return true
   295  		}
   296  		return false
   297  	}
   298  	labelMatches, err := e.FilterNetworks(defaultLabelFilterF)
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  	if len(labelMatches) >= 1 {
   303  		if len(labelMatches) > 1 {
   304  			log.L.Warnf("returning the first network bearing the %q label out of the multiple found: %#v", labels.NerdctlDefaultNetwork, labelMatches)
   305  		}
   306  		return labelMatches[0], nil
   307  	}
   308  
   309  	// Search for networks bearing the DefaultNetworkName.
   310  	defaultNameFilterF := func(nc *NetworkConfig) bool {
   311  		return nc.Name == DefaultNetworkName
   312  	}
   313  	nameMatches, err := e.FilterNetworks(defaultNameFilterF)
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	if len(nameMatches) >= 1 {
   318  		if len(nameMatches) > 1 {
   319  			log.L.Warnf("returning the first network bearing the %q default network name out of the multiple found: %#v", DefaultNetworkName, nameMatches)
   320  		}
   321  
   322  		// Warn the user if the default network was not created by nerdctl.
   323  		match := nameMatches[0]
   324  		_, statErr := os.Stat(e.getConfigPathForNetworkName(DefaultNetworkName))
   325  		if match.NerdctlID == nil || statErr != nil {
   326  			log.L.Warnf("default network named %q does not have an internal nerdctl ID or nerdctl-managed config file, it was most likely NOT created by nerdctl", DefaultNetworkName)
   327  		}
   328  
   329  		return nameMatches[0], nil
   330  	}
   331  
   332  	return nil, nil
   333  }
   334  
   335  func (e *CNIEnv) ensureDefaultNetworkConfig() error {
   336  	defaultNet, err := e.GetDefaultNetworkConfig()
   337  	if err != nil {
   338  		return fmt.Errorf("failed to check for default network: %s", err)
   339  	}
   340  	if defaultNet == nil {
   341  		if err := e.createDefaultNetworkConfig(); err != nil {
   342  			return fmt.Errorf("failed to create default network: %s", err)
   343  		}
   344  	}
   345  	return nil
   346  }
   347  
   348  func (e *CNIEnv) createDefaultNetworkConfig() error {
   349  	filename := e.getConfigPathForNetworkName(DefaultNetworkName)
   350  	if _, err := os.Stat(filename); err == nil {
   351  		return fmt.Errorf("already found existing network config at %q, cannot create new network named %q", filename, DefaultNetworkName)
   352  	}
   353  	opts := CreateOptions{
   354  		Name:       DefaultNetworkName,
   355  		Driver:     DefaultNetworkName,
   356  		Subnets:    []string{DefaultCIDR},
   357  		IPAMDriver: "default",
   358  		Labels:     []string{fmt.Sprintf("%s=true", labels.NerdctlDefaultNetwork)},
   359  	}
   360  	_, err := e.CreateNetwork(opts)
   361  	if err != nil && !errdefs.IsAlreadyExists(err) {
   362  		return err
   363  	}
   364  	return nil
   365  }
   366  
   367  // generateNetworkConfig creates NetworkConfig.
   368  // generateNetworkConfig does not fill "File" field.
   369  func (e *CNIEnv) generateNetworkConfig(name string, labels []string, plugins []CNIPlugin) (*NetworkConfig, error) {
   370  	if name == "" || len(plugins) == 0 {
   371  		return nil, errdefs.ErrInvalidArgument
   372  	}
   373  	for _, f := range plugins {
   374  		p := filepath.Join(e.Path, f.GetPluginType())
   375  		if _, err := exec.LookPath(p); err != nil {
   376  			return nil, fmt.Errorf("needs CNI plugin %q to be installed in CNI_PATH (%q), see https://github.com/containernetworking/plugins/releases: %w", f.GetPluginType(), e.Path, err)
   377  		}
   378  	}
   379  	id := networkID(name)
   380  	labelsMap := strutil.ConvertKVStringsToMap(labels)
   381  
   382  	conf := &cniNetworkConfig{
   383  		CNIVersion: "1.0.0",
   384  		Name:       name,
   385  		ID:         id,
   386  		Labels:     labelsMap,
   387  		Plugins:    plugins,
   388  	}
   389  
   390  	confJSON, err := json.MarshalIndent(conf, "", "  ")
   391  	if err != nil {
   392  		return nil, err
   393  	}
   394  
   395  	l, err := libcni.ConfListFromBytes(confJSON)
   396  	if err != nil {
   397  		return nil, err
   398  	}
   399  	return &NetworkConfig{
   400  		NetworkConfigList: l,
   401  		NerdctlID:         &id,
   402  		NerdctlLabels:     &labelsMap,
   403  		File:              "",
   404  	}, nil
   405  }
   406  
   407  // writeNetworkConfig writes NetworkConfig file to cni config path.
   408  func (e *CNIEnv) writeNetworkConfig(net *NetworkConfig) error {
   409  	filename := e.getConfigPathForNetworkName(net.Name)
   410  	if _, err := os.Stat(filename); err == nil {
   411  		return errdefs.ErrAlreadyExists
   412  	}
   413  	return os.WriteFile(filename, net.Bytes, 0644)
   414  }
   415  
   416  // networkConfigList loads config from dir if dir exists.
   417  func (e *CNIEnv) networkConfigList() ([]*NetworkConfig, error) {
   418  	l := []*NetworkConfig{}
   419  	fileNames, err := libcni.ConfFiles(e.NetconfPath, []string{".conf", ".conflist", ".json"})
   420  	if err != nil {
   421  		return nil, err
   422  	}
   423  	sort.Strings(fileNames)
   424  	for _, fileName := range fileNames {
   425  		var lcl *libcni.NetworkConfigList
   426  		if strings.HasSuffix(fileName, ".conflist") {
   427  			lcl, err = libcni.ConfListFromFile(fileName)
   428  			if err != nil {
   429  				return nil, err
   430  			}
   431  		} else {
   432  			lc, err := libcni.ConfFromFile(fileName)
   433  			if err != nil {
   434  				return nil, err
   435  			}
   436  			lcl, err = libcni.ConfListFromConf(lc)
   437  			if err != nil {
   438  				return nil, err
   439  			}
   440  		}
   441  		id, labels := nerdctlIDLabels(lcl.Bytes)
   442  		l = append(l, &NetworkConfig{
   443  			NetworkConfigList: lcl,
   444  			NerdctlID:         id,
   445  			NerdctlLabels:     labels,
   446  			File:              fileName,
   447  		})
   448  	}
   449  	return l, nil
   450  }
   451  
   452  func nerdctlIDLabels(b []byte) (*string, *map[string]string) {
   453  	type idLabels struct {
   454  		ID     *string            `json:"nerdctlID,omitempty"`
   455  		Labels *map[string]string `json:"nerdctlLabels,omitempty"`
   456  	}
   457  	var idl idLabels
   458  	if err := json.Unmarshal(b, &idl); err != nil {
   459  		return nil, nil
   460  	}
   461  	return idl.ID, idl.Labels
   462  }
   463  
   464  func networkID(name string) string {
   465  	hash := sha256.Sum256([]byte(name))
   466  	return hex.EncodeToString(hash[:])
   467  }
   468  
   469  func (e *CNIEnv) parseSubnet(subnetStr string) (*net.IPNet, error) {
   470  	usedSubnets, err := e.usedSubnets()
   471  	if err != nil {
   472  		return nil, err
   473  	}
   474  	if subnetStr == "" {
   475  		_, defaultSubnet, _ := net.ParseCIDR(StartingCIDR)
   476  		subnet, err := subnetutil.GetFreeSubnet(defaultSubnet, usedSubnets)
   477  		if err != nil {
   478  			return nil, err
   479  		}
   480  		return subnet, nil
   481  	}
   482  
   483  	subnetIP, subnet, err := net.ParseCIDR(subnetStr)
   484  	if err != nil {
   485  		return nil, fmt.Errorf("failed to parse subnet %q", subnetStr)
   486  	}
   487  	if !subnet.IP.Equal(subnetIP) {
   488  		return nil, fmt.Errorf("unexpected subnet %q, maybe you meant %q?", subnetStr, subnet.String())
   489  	}
   490  	if subnetutil.IntersectsWithNetworks(subnet, usedSubnets) {
   491  		return nil, fmt.Errorf("subnet %s overlaps with other one on this address space", subnetStr)
   492  	}
   493  	return subnet, nil
   494  }
   495  
   496  func parseIPAMRange(subnet *net.IPNet, gatewayStr, ipRangeStr string) (*IPAMRange, error) {
   497  	var gateway, rangeStart, rangeEnd net.IP
   498  	if gatewayStr != "" {
   499  		gatewayIP := net.ParseIP(gatewayStr)
   500  		if gatewayIP == nil {
   501  			return nil, fmt.Errorf("failed to parse gateway %q", gatewayStr)
   502  		}
   503  		if !subnet.Contains(gatewayIP) {
   504  			return nil, fmt.Errorf("no matching subnet %q for gateway %q", subnet, gatewayStr)
   505  		}
   506  		gateway = gatewayIP
   507  	} else {
   508  		gateway, _ = subnetutil.FirstIPInSubnet(subnet)
   509  	}
   510  
   511  	res := &IPAMRange{
   512  		Subnet:  subnet.String(),
   513  		Gateway: gateway.String(),
   514  	}
   515  
   516  	if ipRangeStr != "" {
   517  		_, ipRange, err := net.ParseCIDR(ipRangeStr)
   518  		if err != nil {
   519  			return nil, fmt.Errorf("failed to parse ip-range %q", ipRangeStr)
   520  		}
   521  		rangeStart, _ = subnetutil.FirstIPInSubnet(ipRange)
   522  		rangeEnd, _ = subnetutil.LastIPInSubnet(ipRange)
   523  		if !subnet.Contains(rangeStart) || !subnet.Contains(rangeEnd) {
   524  			return nil, fmt.Errorf("no matching subnet %q for ip-range %q", subnet, ipRangeStr)
   525  		}
   526  		res.RangeStart = rangeStart.String()
   527  		res.RangeEnd = rangeEnd.String()
   528  		res.IPRange = ipRangeStr
   529  	}
   530  
   531  	return res, nil
   532  }
   533  
   534  // convert the struct to a map
   535  func structToMap(in interface{}) (map[string]interface{}, error) {
   536  	out := make(map[string]interface{})
   537  	data, err := json.Marshal(in)
   538  	if err != nil {
   539  		return nil, err
   540  	}
   541  	if err := json.Unmarshal(data, &out); err != nil {
   542  		return nil, err
   543  	}
   544  	return out, nil
   545  }
   546  
   547  // ParseMTU parses the mtu option
   548  func ParseMTU(mtu string) (int, error) {
   549  	if mtu == "" {
   550  		return 0, nil // default
   551  	}
   552  	m, err := strconv.Atoi(mtu)
   553  	if err != nil {
   554  		return 0, err
   555  	}
   556  	if m < 0 {
   557  		return 0, fmt.Errorf("mtu %d is less than zero", m)
   558  	}
   559  	return m, nil
   560  }