github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/libpod/network/create.go (about) 1 package network 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 10 "github.com/containernetworking/cni/pkg/version" 11 "github.com/containers/common/pkg/config" 12 "github.com/containers/podman/v2/pkg/domain/entities" 13 "github.com/containers/podman/v2/pkg/rootless" 14 "github.com/containers/podman/v2/pkg/util" 15 "github.com/pkg/errors" 16 ) 17 18 // Create the CNI network 19 func Create(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (*entities.NetworkCreateReport, error) { 20 var fileName string 21 if err := isSupportedDriver(options.Driver); err != nil { 22 return nil, err 23 } 24 // Acquire a lock for CNI 25 l, err := acquireCNILock(filepath.Join(runtimeConfig.Engine.TmpDir, LockFileName)) 26 if err != nil { 27 return nil, err 28 } 29 defer l.releaseCNILock() 30 if len(options.MacVLAN) > 0 { 31 fileName, err = createMacVLAN(name, options, runtimeConfig) 32 } else { 33 fileName, err = createBridge(name, options, runtimeConfig) 34 } 35 if err != nil { 36 return nil, err 37 } 38 return &entities.NetworkCreateReport{Filename: fileName}, nil 39 } 40 41 // validateBridgeOptions validate the bridge networking options 42 func validateBridgeOptions(options entities.NetworkCreateOptions) error { 43 subnet := &options.Subnet 44 ipRange := &options.Range 45 gateway := options.Gateway 46 // if IPv6 is set an IPv6 subnet MUST be specified 47 if options.IPv6 && ((subnet.IP == nil) || (subnet.IP != nil && !IsIPv6(subnet.IP))) { 48 return errors.Errorf("ipv6 option requires an IPv6 --subnet to be provided") 49 } 50 // range and gateway depend on subnet 51 if subnet.IP == nil && (ipRange.IP != nil || gateway != nil) { 52 return errors.Errorf("every ip-range or gateway must have a corresponding subnet") 53 } 54 55 // if a range is given, we need to ensure it is "in" the network range. 56 if ipRange.IP != nil { 57 firstIP, err := FirstIPInSubnet(ipRange) 58 if err != nil { 59 return errors.Wrapf(err, "failed to get first IP address from ip-range") 60 } 61 lastIP, err := LastIPInSubnet(ipRange) 62 if err != nil { 63 return errors.Wrapf(err, "failed to get last IP address from ip-range") 64 } 65 if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) { 66 return errors.Errorf("the ip range %s does not fall within the subnet range %s", ipRange.String(), subnet.String()) 67 } 68 } 69 70 // if network is provided and if gateway is provided, make sure it is "in" network 71 if gateway != nil && !subnet.Contains(gateway) { 72 return errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String()) 73 } 74 75 return nil 76 77 } 78 79 // createBridge creates a CNI network 80 func createBridge(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) { 81 var ( 82 ipamRanges [][]IPAMLocalHostRangeConf 83 err error 84 routes []IPAMRoute 85 ) 86 isGateway := true 87 ipMasq := true 88 89 // validate options 90 if err := validateBridgeOptions(options); err != nil { 91 return "", err 92 } 93 94 // For compatibility with the docker implementation: 95 // if IPv6 is enabled (it really means dual-stack) then an IPv6 subnet has to be provided, and one free network is allocated for IPv4 96 // if IPv6 is not specified the subnet may be specified and can be either IPv4 or IPv6 (podman, unlike docker, allows IPv6 only networks) 97 // If not subnet is specified an IPv4 subnet will be allocated 98 subnet := &options.Subnet 99 ipRange := &options.Range 100 gateway := options.Gateway 101 if subnet.IP != nil { 102 // if network is provided, does it conflict with existing CNI or live networks 103 err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet) 104 if err != nil { 105 return "", err 106 } 107 // obtain CNI subnet default route 108 defaultRoute, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP)) 109 if err != nil { 110 return "", err 111 } 112 routes = append(routes, defaultRoute) 113 // obtain CNI range 114 ipamRange, err := NewIPAMLocalHostRange(subnet, ipRange, gateway) 115 if err != nil { 116 return "", err 117 } 118 ipamRanges = append(ipamRanges, ipamRange) 119 } 120 // if no network is provided or IPv6 flag used, figure out the IPv4 network 121 if options.IPv6 || len(routes) == 0 { 122 subnetV4, err := GetFreeNetwork(runtimeConfig) 123 if err != nil { 124 return "", err 125 } 126 // obtain IPv4 default route 127 defaultRoute, err := NewIPAMDefaultRoute(false) 128 if err != nil { 129 return "", err 130 } 131 routes = append(routes, defaultRoute) 132 // the CNI bridge plugin does not need to set 133 // the range or gateway options explicitly 134 ipamRange, err := NewIPAMLocalHostRange(subnetV4, nil, nil) 135 if err != nil { 136 return "", err 137 } 138 ipamRanges = append(ipamRanges, ipamRange) 139 } 140 141 // create CNI config 142 ipamConfig, err := NewIPAMHostLocalConf(routes, ipamRanges) 143 if err != nil { 144 return "", err 145 } 146 147 if options.Internal { 148 isGateway = false 149 ipMasq = false 150 } 151 152 // obtain host bridge name 153 bridgeDeviceName, err := GetFreeDeviceName(runtimeConfig) 154 if err != nil { 155 return "", err 156 } 157 158 if len(name) > 0 { 159 netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig) 160 if err != nil { 161 return "", err 162 } 163 if util.StringInSlice(name, netNames) { 164 return "", errors.Errorf("the network name %s is already used", name) 165 } 166 } else { 167 // If no name is given, we give the name of the bridge device 168 name = bridgeDeviceName 169 } 170 171 // create CNI plugin configuration 172 ncList := NewNcList(name, version.Current()) 173 var plugins []CNIPlugins 174 // TODO need to iron out the role of isDefaultGW and IPMasq 175 bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig) 176 plugins = append(plugins, bridge) 177 plugins = append(plugins, NewPortMapPlugin()) 178 plugins = append(plugins, NewFirewallPlugin()) 179 plugins = append(plugins, NewTuningPlugin()) 180 // if we find the dnsname plugin or are rootless, we add configuration for it 181 // the rootless-cni-infra container has the dnsname plugin always installed 182 if (HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) || rootless.IsRootless()) && !options.DisableDNS { 183 // Note: in the future we might like to allow for dynamic domain names 184 plugins = append(plugins, NewDNSNamePlugin(DefaultPodmanDomainName)) 185 } 186 ncList["plugins"] = plugins 187 b, err := json.MarshalIndent(ncList, "", " ") 188 if err != nil { 189 return "", err 190 } 191 if err := os.MkdirAll(GetCNIConfDir(runtimeConfig), 0755); err != nil { 192 return "", err 193 } 194 cniPathName := filepath.Join(GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name)) 195 err = ioutil.WriteFile(cniPathName, b, 0644) 196 return cniPathName, err 197 } 198 199 func createMacVLAN(name string, options entities.NetworkCreateOptions, runtimeConfig *config.Config) (string, error) { 200 var ( 201 plugins []CNIPlugins 202 ) 203 liveNetNames, err := GetLiveNetworkNames() 204 if err != nil { 205 return "", err 206 } 207 208 // Make sure the host-device exists 209 if !util.StringInSlice(options.MacVLAN, liveNetNames) { 210 return "", errors.Errorf("failed to find network interface %q", options.MacVLAN) 211 } 212 if len(name) > 0 { 213 netNames, err := GetNetworkNamesFromFileSystem(runtimeConfig) 214 if err != nil { 215 return "", err 216 } 217 if util.StringInSlice(name, netNames) { 218 return "", errors.Errorf("the network name %s is already used", name) 219 } 220 } else { 221 name, err = GetFreeDeviceName(runtimeConfig) 222 if err != nil { 223 return "", err 224 } 225 } 226 ncList := NewNcList(name, version.Current()) 227 macvlan := NewMacVLANPlugin(options.MacVLAN) 228 plugins = append(plugins, macvlan) 229 ncList["plugins"] = plugins 230 b, err := json.MarshalIndent(ncList, "", " ") 231 if err != nil { 232 return "", err 233 } 234 cniPathName := filepath.Join(GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name)) 235 err = ioutil.WriteFile(cniPathName, b, 0644) 236 return cniPathName, err 237 }