github.com/moby/docker@v26.1.3+incompatible/libnetwork/drivers/ipvlan/ipvlan_network.go (about)

     1  //go:build linux
     2  
     3  package ipvlan
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  
     9  	"github.com/containerd/log"
    10  	"github.com/docker/docker/libnetwork/driverapi"
    11  	"github.com/docker/docker/libnetwork/netlabel"
    12  	"github.com/docker/docker/libnetwork/ns"
    13  	"github.com/docker/docker/libnetwork/options"
    14  	"github.com/docker/docker/libnetwork/types"
    15  	"github.com/docker/docker/pkg/parsers/kernel"
    16  	"github.com/docker/docker/pkg/stringid"
    17  )
    18  
    19  // CreateNetwork the network for the specified driver type
    20  func (d *driver) CreateNetwork(nid string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
    21  	kv, err := kernel.GetKernelVersion()
    22  	if err != nil {
    23  		return fmt.Errorf("failed to check kernel version for ipvlan driver support: %v", err)
    24  	}
    25  	// ensure Kernel version is >= v4.2 for ipvlan support
    26  	if kv.Kernel < ipvlanKernelVer || (kv.Kernel == ipvlanKernelVer && kv.Major < ipvlanMajorVer) {
    27  		return fmt.Errorf("kernel version failed to meet the minimum ipvlan kernel requirement of %d.%d, found %d.%d.%d",
    28  			ipvlanKernelVer, ipvlanMajorVer, kv.Kernel, kv.Major, kv.Minor)
    29  	}
    30  	// reject a null v4 network
    31  	if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {
    32  		return fmt.Errorf("ipv4 pool is empty")
    33  	}
    34  	// parse and validate the config and bind to networkConfiguration
    35  	config, err := parseNetworkOptions(nid, option)
    36  	if err != nil {
    37  		return err
    38  	}
    39  	config.processIPAM(ipV4Data, ipV6Data)
    40  
    41  	// if parent interface not specified, create a dummy type link to use named dummy+net_id
    42  	if config.Parent == "" {
    43  		config.Parent = getDummyName(stringid.TruncateID(config.ID))
    44  		config.Internal = true
    45  	}
    46  	foundExisting, err := d.createNetwork(config)
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	if foundExisting {
    52  		return types.InternalMaskableErrorf("restoring existing network %s", config.ID)
    53  	}
    54  
    55  	// update persistent db, rollback on fail
    56  	err = d.storeUpdate(config)
    57  	if err != nil {
    58  		d.deleteNetwork(config.ID)
    59  		log.G(context.TODO()).Debugf("encountered an error rolling back a network create for %s : %v", config.ID, err)
    60  		return err
    61  	}
    62  
    63  	return nil
    64  }
    65  
    66  // createNetwork is used by new network callbacks and persistent network cache
    67  func (d *driver) createNetwork(config *configuration) (bool, error) {
    68  	foundExisting := false
    69  	networkList := d.getNetworks()
    70  	for _, nw := range networkList {
    71  		if config.Parent == nw.config.Parent {
    72  			if config.ID != nw.config.ID {
    73  				return false, fmt.Errorf("network %s is already using parent interface %s",
    74  					getDummyName(stringid.TruncateID(nw.config.ID)), config.Parent)
    75  			}
    76  			log.G(context.TODO()).Debugf("Create Network for the same ID %s\n", config.ID)
    77  			foundExisting = true
    78  			break
    79  		}
    80  	}
    81  	if !parentExists(config.Parent) {
    82  		// Create a dummy link if a dummy name is set for parent
    83  		if dummyName := getDummyName(stringid.TruncateID(config.ID)); dummyName == config.Parent {
    84  			err := createDummyLink(config.Parent, dummyName)
    85  			if err != nil {
    86  				return false, err
    87  			}
    88  			config.CreatedSlaveLink = true
    89  
    90  			// notify the user in logs that they have limited communications
    91  			log.G(context.TODO()).Debugf("Empty -o parent= flags limit communications to other containers inside of network: %s",
    92  				config.Parent)
    93  		} else {
    94  			// if the subinterface parent_iface.vlan_id checks do not pass, return err.
    95  			//  a valid example is 'eth0.10' for a parent iface 'eth0' with a vlan id '10'
    96  			err := createVlanLink(config.Parent)
    97  			if err != nil {
    98  				return false, err
    99  			}
   100  			// if driver created the networks slave link, record it for future deletion
   101  			config.CreatedSlaveLink = true
   102  		}
   103  	}
   104  	if !foundExisting {
   105  		n := &network{
   106  			id:        config.ID,
   107  			driver:    d,
   108  			endpoints: endpointTable{},
   109  			config:    config,
   110  		}
   111  		// add the network
   112  		d.addNetwork(n)
   113  	}
   114  
   115  	return foundExisting, nil
   116  }
   117  
   118  // DeleteNetwork deletes the network for the specified driver type
   119  func (d *driver) DeleteNetwork(nid string) error {
   120  	n := d.network(nid)
   121  	if n == nil {
   122  		return fmt.Errorf("network id %s not found", nid)
   123  	}
   124  	// if the driver created the slave interface, delete it, otherwise leave it
   125  	if ok := n.config.CreatedSlaveLink; ok {
   126  		// if the interface exists, only delete if it matches iface.vlan or dummy.net_id naming
   127  		if ok := parentExists(n.config.Parent); ok {
   128  			// only delete the link if it is named the net_id
   129  			if n.config.Parent == getDummyName(stringid.TruncateID(nid)) {
   130  				err := delDummyLink(n.config.Parent)
   131  				if err != nil {
   132  					log.G(context.TODO()).Debugf("link %s was not deleted, continuing the delete network operation: %v",
   133  						n.config.Parent, err)
   134  				}
   135  			} else {
   136  				// only delete the link if it matches iface.vlan naming
   137  				err := delVlanLink(n.config.Parent)
   138  				if err != nil {
   139  					log.G(context.TODO()).Debugf("link %s was not deleted, continuing the delete network operation: %v",
   140  						n.config.Parent, err)
   141  				}
   142  			}
   143  		}
   144  	}
   145  	for _, ep := range n.endpoints {
   146  		if link, err := ns.NlHandle().LinkByName(ep.srcName); err == nil {
   147  			if err := ns.NlHandle().LinkDel(link); err != nil {
   148  				log.G(context.TODO()).WithError(err).Warnf("Failed to delete interface (%s)'s link on endpoint (%s) delete", ep.srcName, ep.id)
   149  			}
   150  		}
   151  
   152  		if err := d.storeDelete(ep); err != nil {
   153  			log.G(context.TODO()).Warnf("Failed to remove ipvlan endpoint %.7s from store: %v", ep.id, err)
   154  		}
   155  	}
   156  	// delete the *network
   157  	d.deleteNetwork(nid)
   158  	// delete the network record from persistent cache
   159  	err := d.storeDelete(n.config)
   160  	if err != nil {
   161  		return fmt.Errorf("error deleting id %s from datastore: %v", nid, err)
   162  	}
   163  	return nil
   164  }
   165  
   166  // parseNetworkOptions parses docker network options
   167  func parseNetworkOptions(id string, option options.Generic) (*configuration, error) {
   168  	var (
   169  		err    error
   170  		config = &configuration{}
   171  	)
   172  	// parse generic labels first
   173  	if genData, ok := option[netlabel.GenericData]; ok && genData != nil {
   174  		if config, err = parseNetworkGenericOptions(genData); err != nil {
   175  			return nil, err
   176  		}
   177  	}
   178  	if val, ok := option[netlabel.Internal]; ok {
   179  		if internal, ok := val.(bool); ok && internal {
   180  			config.Internal = true
   181  		}
   182  	}
   183  
   184  	// verify the ipvlan mode from -o ipvlan_mode option
   185  	switch config.IpvlanMode {
   186  	case "":
   187  		// default to ipvlan L2 mode if -o ipvlan_mode is empty
   188  		config.IpvlanMode = modeL2
   189  	case modeL2, modeL3, modeL3S:
   190  		// valid option
   191  	default:
   192  		return nil, fmt.Errorf("requested ipvlan mode '%s' is not valid, 'l2' mode is the ipvlan driver default", config.IpvlanMode)
   193  	}
   194  
   195  	// verify the ipvlan flag from -o ipvlan_flag option
   196  	switch config.IpvlanFlag {
   197  	case "":
   198  		// default to bridge if -o ipvlan_flag is empty
   199  		config.IpvlanFlag = flagBridge
   200  	case flagBridge, flagPrivate, flagVepa:
   201  		// valid option
   202  	default:
   203  		return nil, fmt.Errorf("requested ipvlan flag '%s' is not valid, 'bridge' is the ipvlan driver default", config.IpvlanFlag)
   204  	}
   205  
   206  	// loopback is not a valid parent link
   207  	if config.Parent == "lo" {
   208  		return nil, fmt.Errorf("loopback interface is not a valid ipvlan parent link")
   209  	}
   210  
   211  	config.ID = id
   212  	return config, nil
   213  }
   214  
   215  // parseNetworkGenericOptions parse generic driver docker network options
   216  func parseNetworkGenericOptions(data interface{}) (*configuration, error) {
   217  	switch opt := data.(type) {
   218  	case *configuration:
   219  		return opt, nil
   220  	case map[string]string:
   221  		return newConfigFromLabels(opt), nil
   222  	case options.Generic:
   223  		var config *configuration
   224  		opaqueConfig, err := options.GenerateFromModel(opt, config)
   225  		if err != nil {
   226  			return nil, err
   227  		}
   228  		return opaqueConfig.(*configuration), nil
   229  	default:
   230  		return nil, types.InvalidParameterErrorf("unrecognized network configuration format: %v", opt)
   231  	}
   232  }
   233  
   234  // newConfigFromLabels creates a new configuration from the given labels.
   235  func newConfigFromLabels(labels map[string]string) *configuration {
   236  	config := &configuration{}
   237  	for label, value := range labels {
   238  		switch label {
   239  		case parentOpt:
   240  			// parse driver option '-o parent'
   241  			config.Parent = value
   242  		case driverModeOpt:
   243  			// parse driver option '-o ipvlan_mode'
   244  			config.IpvlanMode = value
   245  		case driverFlagOpt:
   246  			// parse driver option '-o ipvlan_flag'
   247  			config.IpvlanFlag = value
   248  		}
   249  	}
   250  
   251  	return config
   252  }
   253  
   254  // processIPAM parses v4 and v6 IP information and binds it to the network configuration
   255  func (config *configuration) processIPAM(ipamV4Data, ipamV6Data []driverapi.IPAMData) {
   256  	for _, ipd := range ipamV4Data {
   257  		config.Ipv4Subnets = append(config.Ipv4Subnets, &ipSubnet{
   258  			SubnetIP: ipd.Pool.String(),
   259  			GwIP:     ipd.Gateway.String(),
   260  		})
   261  	}
   262  	for _, ipd := range ipamV6Data {
   263  		config.Ipv6Subnets = append(config.Ipv6Subnets, &ipSubnet{
   264  			SubnetIP: ipd.Pool.String(),
   265  			GwIP:     ipd.Gateway.String(),
   266  		})
   267  	}
   268  }