github.com/AbhinandanKurakure/podman/v3@v3.4.10/libpod/network/cni/cni_conversion.go (about) 1 // +build linux 2 3 package cni 4 5 import ( 6 "encoding/json" 7 "io/ioutil" 8 "net" 9 "os" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/containernetworking/cni/libcni" 17 "github.com/containernetworking/cni/pkg/version" 18 "github.com/containers/podman/v3/libpod/network/types" 19 "github.com/containers/podman/v3/libpod/network/util" 20 pkgutil "github.com/containers/podman/v3/pkg/util" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 ) 24 25 func createNetworkFromCNIConfigList(conf *libcni.NetworkConfigList, confPath string) (*types.Network, error) { 26 network := types.Network{ 27 Name: conf.Name, 28 ID: getNetworkIDFromName(conf.Name), 29 Labels: map[string]string{}, 30 Options: map[string]string{}, 31 IPAMOptions: map[string]string{}, 32 } 33 34 cniJSON := make(map[string]interface{}) 35 err := json.Unmarshal(conf.Bytes, &cniJSON) 36 if err != nil { 37 return nil, errors.Wrapf(err, "failed to unmarshal network config %s", conf.Name) 38 } 39 if args, ok := cniJSON["args"]; ok { 40 if key, ok := args.(map[string]interface{}); ok { 41 // read network labels and options from the conf file 42 network.Labels = getNetworkArgsFromConfList(key, podmanLabelKey) 43 network.Options = getNetworkArgsFromConfList(key, podmanOptionsKey) 44 } 45 } 46 47 f, err := os.Stat(confPath) 48 if err != nil { 49 return nil, err 50 } 51 stat := f.Sys().(*syscall.Stat_t) 52 network.Created = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) 53 54 firstPlugin := conf.Plugins[0] 55 network.Driver = firstPlugin.Network.Type 56 57 switch firstPlugin.Network.Type { 58 case types.BridgeNetworkDriver: 59 var bridge hostLocalBridge 60 err := json.Unmarshal(firstPlugin.Bytes, &bridge) 61 if err != nil { 62 return nil, errors.Wrapf(err, "failed to unmarshal the bridge plugin config in %s", confPath) 63 } 64 network.NetworkInterface = bridge.BrName 65 66 // if isGateway is false we have an internal network 67 if !bridge.IsGW { 68 network.Internal = true 69 } 70 71 // set network options 72 if bridge.MTU != 0 { 73 network.Options["mtu"] = strconv.Itoa(bridge.MTU) 74 } 75 if bridge.Vlan != 0 { 76 network.Options["vlan"] = strconv.Itoa(bridge.Vlan) 77 } 78 79 err = convertIPAMConfToNetwork(&network, bridge.IPAM, confPath) 80 if err != nil { 81 return nil, err 82 } 83 84 case types.MacVLANNetworkDriver: 85 var macvlan macVLANConfig 86 err := json.Unmarshal(firstPlugin.Bytes, &macvlan) 87 if err != nil { 88 return nil, errors.Wrapf(err, "failed to unmarshal the macvlan plugin config in %s", confPath) 89 } 90 network.NetworkInterface = macvlan.Master 91 92 // set network options 93 if macvlan.MTU != 0 { 94 network.Options["mtu"] = strconv.Itoa(macvlan.MTU) 95 } 96 97 err = convertIPAMConfToNetwork(&network, macvlan.IPAM, confPath) 98 if err != nil { 99 return nil, err 100 } 101 102 default: 103 // A warning would be good but users would get this warning everytime so keep this at info level. 104 logrus.Infof("unsupported CNI config type %s in %s, this network can still be used but inspect or list cannot show all information", 105 firstPlugin.Network.Type, confPath) 106 } 107 108 // check if the dnsname plugin is configured 109 network.DNSEnabled = findPluginByName(conf.Plugins, "dnsname") 110 111 return &network, nil 112 } 113 114 func findPluginByName(plugins []*libcni.NetworkConfig, name string) bool { 115 for _, plugin := range plugins { 116 if plugin.Network.Type == name { 117 return true 118 } 119 } 120 return false 121 } 122 123 // convertIPAMConfToNetwork converts A cni IPAMConfig to libpod network subnets. 124 // It returns an array of subnets and an extra bool if dhcp is configured. 125 func convertIPAMConfToNetwork(network *types.Network, ipam ipamConfig, confPath string) error { 126 if ipam.PluginType == types.DHCPIPAMDriver { 127 network.IPAMOptions["driver"] = types.DHCPIPAMDriver 128 return nil 129 } 130 131 if ipam.PluginType != types.HostLocalIPAMDriver { 132 return errors.Errorf("unsupported ipam plugin %s in %s", ipam.PluginType, confPath) 133 } 134 135 network.IPAMOptions["driver"] = types.HostLocalIPAMDriver 136 for _, r := range ipam.Ranges { 137 for _, ipam := range r { 138 s := types.Subnet{} 139 140 // Do not use types.ParseCIDR() because we want the ip to be 141 // the network address and not a random ip in the sub. 142 _, sub, err := net.ParseCIDR(ipam.Subnet) 143 if err != nil { 144 return err 145 } 146 s.Subnet = types.IPNet{IPNet: *sub} 147 148 // gateway 149 var gateway net.IP 150 if ipam.Gateway != "" { 151 gateway = net.ParseIP(ipam.Gateway) 152 if gateway == nil { 153 return errors.Errorf("failed to parse gateway ip %s", ipam.Gateway) 154 } 155 // convert to 4 byte if ipv4 156 ipv4 := gateway.To4() 157 if ipv4 != nil { 158 gateway = ipv4 159 } 160 } else if !network.Internal { 161 // only add a gateway address if the network is not internal 162 gateway, err = util.FirstIPInSubnet(sub) 163 if err != nil { 164 return errors.Errorf("failed to get first ip in subnet %s", sub.String()) 165 } 166 } 167 s.Gateway = gateway 168 169 var rangeStart net.IP 170 var rangeEnd net.IP 171 if ipam.RangeStart != "" { 172 rangeStart = net.ParseIP(ipam.RangeStart) 173 if rangeStart == nil { 174 return errors.Errorf("failed to parse range start ip %s", ipam.RangeStart) 175 } 176 } 177 if ipam.RangeEnd != "" { 178 rangeEnd = net.ParseIP(ipam.RangeEnd) 179 if rangeEnd == nil { 180 return errors.Errorf("failed to parse range end ip %s", ipam.RangeEnd) 181 } 182 } 183 if rangeStart != nil || rangeEnd != nil { 184 s.LeaseRange = &types.LeaseRange{} 185 s.LeaseRange.StartIP = rangeStart 186 s.LeaseRange.EndIP = rangeEnd 187 } 188 network.Subnets = append(network.Subnets, s) 189 } 190 } 191 return nil 192 } 193 194 // getNetworkArgsFromConfList returns the map of args in a conflist, argType should be labels or options 195 func getNetworkArgsFromConfList(args map[string]interface{}, argType string) map[string]string { 196 if args, ok := args[argType]; ok { 197 if labels, ok := args.(map[string]interface{}); ok { 198 result := make(map[string]string, len(labels)) 199 for k, v := range labels { 200 if v, ok := v.(string); ok { 201 result[k] = v 202 } 203 } 204 return result 205 } 206 } 207 return nil 208 } 209 210 // createCNIConfigListFromNetwork will create a cni config file from the given network. 211 // It returns the cni config and the path to the file where the config was written. 212 // Set writeToDisk to false to only add this network into memory. 213 func (n *cniNetwork) createCNIConfigListFromNetwork(network *types.Network, writeToDisk bool) (*libcni.NetworkConfigList, string, error) { 214 var ( 215 routes []ipamRoute 216 ipamRanges [][]ipamLocalHostRangeConf 217 ipamConf ipamConfig 218 err error 219 ) 220 if len(network.Subnets) > 0 { 221 for _, subnet := range network.Subnets { 222 route, err := newIPAMDefaultRoute(util.IsIPv6(subnet.Subnet.IP)) 223 if err != nil { 224 return nil, "", err 225 } 226 routes = append(routes, route) 227 ipam := newIPAMLocalHostRange(subnet.Subnet, subnet.LeaseRange, subnet.Gateway) 228 ipamRanges = append(ipamRanges, []ipamLocalHostRangeConf{*ipam}) 229 } 230 ipamConf = newIPAMHostLocalConf(routes, ipamRanges) 231 } else { 232 ipamConf = ipamConfig{PluginType: "dhcp"} 233 } 234 235 vlan := 0 236 mtu := 0 237 for k, v := range network.Options { 238 switch k { 239 case "mtu": 240 mtu, err = parseMTU(v) 241 if err != nil { 242 return nil, "", err 243 } 244 245 case "vlan": 246 vlan, err = parseVlan(v) 247 if err != nil { 248 return nil, "", err 249 } 250 251 default: 252 return nil, "", errors.Errorf("unsupported network option %s", k) 253 } 254 } 255 256 isGateway := true 257 ipMasq := true 258 if network.Internal { 259 isGateway = false 260 ipMasq = false 261 } 262 // create CNI plugin configuration 263 ncList := newNcList(network.Name, version.Current(), network.Labels, network.Options) 264 var plugins []interface{} 265 266 switch network.Driver { 267 case types.BridgeNetworkDriver: 268 bridge := newHostLocalBridge(network.NetworkInterface, isGateway, ipMasq, mtu, vlan, ipamConf) 269 plugins = append(plugins, bridge, newPortMapPlugin(), newFirewallPlugin(), newTuningPlugin()) 270 // if we find the dnsname plugin we add configuration for it 271 if hasDNSNamePlugin(n.cniPluginDirs) && network.DNSEnabled { 272 // Note: in the future we might like to allow for dynamic domain names 273 plugins = append(plugins, newDNSNamePlugin(defaultPodmanDomainName)) 274 } 275 // Add the podman-machine CNI plugin if we are in a machine 276 if n.isMachine { 277 plugins = append(plugins, newPodmanMachinePlugin()) 278 } 279 280 case types.MacVLANNetworkDriver: 281 plugins = append(plugins, newMacVLANPlugin(network.NetworkInterface, mtu, ipamConf)) 282 283 default: 284 return nil, "", errors.Errorf("driver %q is not supported by cni", network.Driver) 285 } 286 ncList["plugins"] = plugins 287 b, err := json.MarshalIndent(ncList, "", " ") 288 if err != nil { 289 return nil, "", err 290 } 291 cniPathName := "" 292 if writeToDisk { 293 cniPathName = filepath.Join(n.cniConfigDir, network.Name+".conflist") 294 err = ioutil.WriteFile(cniPathName, b, 0644) 295 if err != nil { 296 return nil, "", err 297 } 298 f, err := os.Stat(cniPathName) 299 if err != nil { 300 return nil, "", err 301 } 302 stat := f.Sys().(*syscall.Stat_t) 303 network.Created = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)) 304 } else { 305 network.Created = time.Now() 306 } 307 config, err := libcni.ConfListFromBytes(b) 308 if err != nil { 309 return nil, "", err 310 } 311 return config, cniPathName, nil 312 } 313 314 // parseMTU parses the mtu option 315 func parseMTU(mtu string) (int, error) { 316 if mtu == "" { 317 return 0, nil // default 318 } 319 m, err := strconv.Atoi(mtu) 320 if err != nil { 321 return 0, err 322 } 323 if m < 0 { 324 return 0, errors.Errorf("mtu %d is less than zero", m) 325 } 326 return m, nil 327 } 328 329 // parseVlan parses the vlan option 330 func parseVlan(vlan string) (int, error) { 331 if vlan == "" { 332 return 0, nil // default 333 } 334 v, err := strconv.Atoi(vlan) 335 if err != nil { 336 return 0, err 337 } 338 if v < 0 || v > 4094 { 339 return 0, errors.Errorf("vlan ID %d must be between 0 and 4094", v) 340 } 341 return v, nil 342 } 343 344 func convertSpecgenPortsToCNIPorts(ports []types.PortMapping) ([]cniPortMapEntry, error) { 345 cniPorts := make([]cniPortMapEntry, 0, len(ports)) 346 for _, port := range ports { 347 if port.Protocol == "" { 348 return nil, errors.New("port protocol should not be empty") 349 } 350 protocols := strings.Split(port.Protocol, ",") 351 352 for _, protocol := range protocols { 353 if !pkgutil.StringInSlice(protocol, []string{"tcp", "udp", "sctp"}) { 354 return nil, errors.Errorf("unknown port protocol %s", protocol) 355 } 356 cniPort := cniPortMapEntry{ 357 HostPort: int(port.HostPort), 358 ContainerPort: int(port.ContainerPort), 359 HostIP: port.HostIP, 360 Protocol: protocol, 361 } 362 cniPorts = append(cniPorts, cniPort) 363 for i := 1; i < int(port.Range); i++ { 364 cniPort := cniPortMapEntry{ 365 HostPort: int(port.HostPort) + i, 366 ContainerPort: int(port.ContainerPort) + i, 367 HostIP: port.HostIP, 368 Protocol: protocol, 369 } 370 cniPorts = append(cniPorts, cniPort) 371 } 372 } 373 } 374 return cniPorts, nil 375 }