// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package allocrunner import ( "fmt" "os" "path" "strings" "syscall" hclog "github.com/hashicorp/go-hclog" clientconfig "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/client/lib/nsutil" "github.com/hashicorp/nomad/client/pluginmanager/drivermanager" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/plugins/drivers" ) func newNetworkManager(alloc *structs.Allocation, driverManager drivermanager.Manager) (nm drivers.DriverNetworkManager, err error) { // The defaultNetworkManager is used if a driver doesn't need to create the network nm = &defaultNetworkManager{} tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup) // default netmode to host, this can be overridden by the task or task group tgNetMode := "host" if len(tg.Networks) > 0 && tg.Networks[0].Mode != "" { tgNetMode = tg.Networks[0].Mode } groupIsolationMode := netModeToIsolationMode(tgNetMode) // Setting the hostname is only possible where the task groups networking // mode is group; meaning bridge or none. if len(tg.Networks) > 0 && (groupIsolationMode != drivers.NetIsolationModeGroup && tg.Networks[0].Hostname != "") { return nil, fmt.Errorf("hostname cannot be set on task group using %q networking mode", groupIsolationMode) } // networkInitiator tracks the task driver which needs to create the network // to check for multiple drivers needing to create the network. var networkInitiator string // driverCaps tracks which drivers we've checked capabilities for so as not // to do extra work driverCaps := make(map[string]struct{}) for _, task := range tg.Tasks { // the task's netmode defaults to the the task group but can be overridden taskNetMode := tgNetMode if len(task.Resources.Networks) > 0 && task.Resources.Networks[0].Mode != "" { taskNetMode = task.Resources.Networks[0].Mode } // netmode host should always work to support backwards compat if taskNetMode == "host" { continue } // check to see if capabilities of this task's driver have already been checked if _, ok := driverCaps[task.Driver]; ok { continue } driver, err := driverManager.Dispense(task.Driver) if err != nil { return nil, fmt.Errorf("failed to dispense driver %s: %v", task.Driver, err) } caps, err := driver.Capabilities() if err != nil { return nil, fmt.Errorf("failed to retrieve capabilities for driver %s: %v", task.Driver, err) } // check that the driver supports the requested network isolation mode netIsolationMode := netModeToIsolationMode(taskNetMode) if !caps.HasNetIsolationMode(netIsolationMode) { return nil, fmt.Errorf("task %s does not support %q networking mode", task.Name, taskNetMode) } // check if the driver needs to create the network and if a different // driver has already claimed it needs to initiate the network if caps.MustInitiateNetwork { if networkInitiator != "" { return nil, fmt.Errorf("tasks %s and %s want to initiate networking but only one driver can do so", networkInitiator, task.Name) } netManager, ok := driver.(drivers.DriverNetworkManager) if !ok { return nil, fmt.Errorf("driver %s does not implement network management RPCs", task.Driver) } nm = netManager networkInitiator = task.Name } else if len(tg.Networks) > 0 && tg.Networks[0].Hostname != "" { // TODO jrasell: remove once the default linux network manager // supports setting the hostname in bridged mode. This currently // indicates only Docker supports this, which is true unless a // custom driver can which means this check still holds as true as // we can tell. // Please see: https://github.com/hashicorp/nomad/issues/11180 return nil, fmt.Errorf("hostname is not currently supported on driver %s", task.Driver) } // mark this driver's capabilities as checked driverCaps[task.Driver] = struct{}{} } return nm, nil } // defaultNetworkManager creates a network namespace for the alloc type defaultNetworkManager struct{} // CreateNetwork is the CreateNetwork implementation of the // drivers.DriverNetworkManager interface function. It does not currently // support setting the hostname of the network namespace. func (*defaultNetworkManager) CreateNetwork(allocID string, _ *drivers.NetworkCreateRequest) (*drivers.NetworkIsolationSpec, bool, error) { netns, err := nsutil.NewNS(allocID) if err != nil { // when a client restarts, the namespace will already exist and // there will be a namespace file in use by the task process if e, ok := err.(*os.PathError); ok && e.Err == syscall.EPERM { nsPath := path.Join(nsutil.NetNSRunDir, allocID) _, err := os.Stat(nsPath) if err == nil { // Let's return a spec that points to the tested nspath, but indicate // that we didn't make the namespace. That will stop the network_hook // from calling its networkConfigurator.Setup function in the reconnect // case, but provide the spec value necessary for the network_hook's // Postrun function to not fast exit. spec := &drivers.NetworkIsolationSpec{ Mode: drivers.NetIsolationModeGroup, Path: nsPath, Labels: make(map[string]string), } return spec, false, nil } } return nil, false, err } spec := &drivers.NetworkIsolationSpec{ Mode: drivers.NetIsolationModeGroup, Path: netns.Path(), Labels: make(map[string]string), } return spec, true, nil } func (*defaultNetworkManager) DestroyNetwork(allocID string, spec *drivers.NetworkIsolationSpec) error { if spec == nil { return nil } return nsutil.UnmountNS(spec.Path) } func netModeToIsolationMode(netMode string) drivers.NetIsolationMode { switch strings.ToLower(netMode) { case "host": return drivers.NetIsolationModeHost case "bridge", "none": return drivers.NetIsolationModeGroup case "driver": return drivers.NetIsolationModeTask default: if strings.HasPrefix(strings.ToLower(netMode), "cni/") { return drivers.NetIsolationModeGroup } return drivers.NetIsolationModeHost } } func newNetworkConfigurator(log hclog.Logger, alloc *structs.Allocation, config *clientconfig.Config) (NetworkConfigurator, error) { tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup) // Check if network block is given if len(tg.Networks) == 0 { return &hostNetworkConfigurator{}, nil } netMode := strings.ToLower(tg.Networks[0].Mode) ignorePortMappingHostIP := config.BindWildcardDefaultHostNetwork if len(config.HostNetworks) > 0 { ignorePortMappingHostIP = false } switch { case netMode == "bridge": c, err := newBridgeNetworkConfigurator(log, config.BridgeNetworkName, config.BridgeNetworkAllocSubnet, config.BridgeNetworkHairpinMode, config.CNIPath, ignorePortMappingHostIP) if err != nil { return nil, err } return &synchronizedNetworkConfigurator{c}, nil case strings.HasPrefix(netMode, "cni/"): c, err := newCNINetworkConfigurator(log, config.CNIPath, config.CNIInterfacePrefix, config.CNIConfigDir, netMode[4:], ignorePortMappingHostIP) if err != nil { return nil, err } return &synchronizedNetworkConfigurator{c}, nil default: return &hostNetworkConfigurator{}, nil } }