github.com/containers/podman/v4@v4.9.4/libpod/networking_common.go (about) 1 //go:build !remote && (linux || freebsd) 2 // +build !remote 3 // +build linux freebsd 4 5 package libpod 6 7 import ( 8 "errors" 9 "fmt" 10 "regexp" 11 "sort" 12 13 "github.com/containers/common/libnetwork/etchosts" 14 "github.com/containers/common/libnetwork/types" 15 "github.com/containers/common/pkg/config" 16 "github.com/containers/common/pkg/machine" 17 "github.com/containers/common/pkg/util" 18 "github.com/containers/podman/v4/libpod/define" 19 "github.com/containers/podman/v4/libpod/events" 20 "github.com/containers/podman/v4/pkg/namespaces" 21 "github.com/containers/podman/v4/pkg/rootless" 22 "github.com/containers/storage/pkg/lockfile" 23 "github.com/sirupsen/logrus" 24 ) 25 26 // convertPortMappings will remove the HostIP part from the ports when running inside podman machine. 27 // This is needed because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports. 28 // For machine the HostIP must only be used by gvproxy and never in the VM. 29 func (c *Container) convertPortMappings() []types.PortMapping { 30 if !machine.IsGvProxyBased() || len(c.config.PortMappings) == 0 { 31 return c.config.PortMappings 32 } 33 // if we run in a machine VM we have to ignore the host IP part 34 newPorts := make([]types.PortMapping, 0, len(c.config.PortMappings)) 35 for _, port := range c.config.PortMappings { 36 port.HostIP = "" 37 newPorts = append(newPorts, port) 38 } 39 return newPorts 40 } 41 42 func (c *Container) getNetworkOptions(networkOpts map[string]types.PerNetworkOptions) types.NetworkOptions { 43 nameservers := make([]string, 0, len(c.runtime.config.Containers.DNSServers.Get())+len(c.config.DNSServer)) 44 nameservers = append(nameservers, c.runtime.config.Containers.DNSServers.Get()...) 45 for _, ip := range c.config.DNSServer { 46 nameservers = append(nameservers, ip.String()) 47 } 48 opts := types.NetworkOptions{ 49 ContainerID: c.config.ID, 50 ContainerName: getNetworkPodName(c), 51 DNSServers: nameservers, 52 } 53 opts.PortMappings = c.convertPortMappings() 54 55 // If the container requested special network options use this instead of the config. 56 // This is the case for container restore or network reload. 57 if c.perNetworkOpts != nil { 58 opts.Networks = c.perNetworkOpts 59 } else { 60 opts.Networks = networkOpts 61 } 62 return opts 63 } 64 65 // setUpNetwork will set up the networks, on error it will also tear down the cni 66 // networks. If rootless it will join/create the rootless network namespace. 67 func (r *Runtime) setUpNetwork(ns string, opts types.NetworkOptions) (map[string]types.StatusBlock, error) { 68 rootlessNetNS, err := r.GetRootlessNetNs(true) 69 if err != nil { 70 return nil, err 71 } 72 var results map[string]types.StatusBlock 73 setUpPod := func() error { 74 results, err = r.network.Setup(ns, types.SetupOptions{NetworkOptions: opts}) 75 return err 76 } 77 // rootlessNetNS is nil if we are root 78 if rootlessNetNS != nil { 79 // execute the setup in the rootless net ns 80 err = rootlessNetNS.Do(setUpPod) 81 rootlessNetNS.Lock.Unlock() 82 } else { 83 err = setUpPod() 84 } 85 return results, err 86 } 87 88 // getNetworkPodName return the pod name (hostname) used by dns backend. 89 // If we are in the pod network namespace use the pod name otherwise the container name 90 func getNetworkPodName(c *Container) string { 91 if c.config.NetMode.IsPod() || c.IsInfra() { 92 pod, err := c.runtime.state.Pod(c.PodID()) 93 if err == nil { 94 return pod.Name() 95 } 96 } 97 return c.Name() 98 } 99 100 // Tear down a container's network configuration and joins the 101 // rootless net ns as rootless user 102 func (r *Runtime) teardownNetworkBackend(ns string, opts types.NetworkOptions) error { 103 rootlessNetNS, err := r.GetRootlessNetNs(false) 104 if err != nil { 105 return err 106 } 107 tearDownPod := func() error { 108 if err := r.network.Teardown(ns, types.TeardownOptions{NetworkOptions: opts}); err != nil { 109 return fmt.Errorf("tearing down network namespace configuration for container %s: %w", opts.ContainerID, err) 110 } 111 return nil 112 } 113 114 // rootlessNetNS is nil if we are root 115 if rootlessNetNS != nil { 116 // execute the network setup in the rootless net ns 117 err = rootlessNetNS.Do(tearDownPod) 118 if cerr := rootlessNetNS.Cleanup(r); cerr != nil { 119 logrus.WithError(cerr).Error("failed to clean up rootless netns") 120 } 121 rootlessNetNS.Lock.Unlock() 122 } else { 123 err = tearDownPod() 124 } 125 return err 126 } 127 128 // Tear down a container's network backend configuration, but do not tear down the 129 // namespace itself. 130 func (r *Runtime) teardownNetwork(ctr *Container) error { 131 if ctr.state.NetNS == "" { 132 // The container has no network namespace, we're set 133 return nil 134 } 135 136 logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS, ctr.ID()) 137 138 networks, err := ctr.networks() 139 if err != nil { 140 return err 141 } 142 143 if !ctr.config.NetMode.IsSlirp4netns() && 144 !ctr.config.NetMode.IsPasta() && len(networks) > 0 { 145 netOpts := ctr.getNetworkOptions(networks) 146 return r.teardownNetworkBackend(ctr.state.NetNS, netOpts) 147 } 148 return nil 149 } 150 151 // isBridgeNetMode checks if the given network mode is bridge. 152 // It returns nil when it is set to bridge and an error otherwise. 153 func isBridgeNetMode(n namespaces.NetworkMode) error { 154 if !n.IsBridge() { 155 return fmt.Errorf("%q is not supported: %w", n, define.ErrNetworkModeInvalid) 156 } 157 return nil 158 } 159 160 // Reload only works with containers with a configured network. 161 // It will tear down, and then reconfigure, the network of the container. 162 // This is mainly used when a reload of firewall rules wipes out existing 163 // firewall configuration. 164 // Efforts will be made to preserve MAC and IP addresses. 165 // Only works on containers with bridge networking at present, though in the future we could 166 // extend this to stop + restart slirp4netns 167 func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.StatusBlock, error) { 168 if ctr.state.NetNS == "" { 169 return nil, fmt.Errorf("container %s network is not configured, refusing to reload: %w", ctr.ID(), define.ErrCtrStateInvalid) 170 } 171 if err := isBridgeNetMode(ctr.config.NetMode); err != nil { 172 return nil, err 173 } 174 logrus.Infof("Going to reload container %s network", ctr.ID()) 175 176 err := r.teardownNetwork(ctr) 177 if err != nil { 178 // teardownNetwork will error if the iptables rules do not exist and this is the case after 179 // a firewall reload. The purpose of network reload is to recreate the rules if they do 180 // not exists so we should not log this specific error as error. This would confuse users otherwise. 181 // iptables-legacy and iptables-nft will create different errors. Make sure to match both. 182 b, rerr := regexp.MatchString("Couldn't load target `CNI-[a-f0-9]{24}':No such file or directory|Chain 'CNI-[a-f0-9]{24}' does not exist", err.Error()) 183 if rerr == nil && !b { 184 logrus.Error(err) 185 } else { 186 logrus.Info(err) 187 } 188 } 189 190 networkOpts, err := ctr.networks() 191 if err != nil { 192 return nil, err 193 } 194 195 // Set the same network settings as before.. 196 netStatus := ctr.getNetworkStatus() 197 for network, perNetOpts := range networkOpts { 198 for name, netInt := range netStatus[network].Interfaces { 199 perNetOpts.InterfaceName = name 200 perNetOpts.StaticMAC = netInt.MacAddress 201 for _, netAddress := range netInt.Subnets { 202 perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.IPNet.IP) 203 } 204 // Normally interfaces have a length of 1, only for some special cni configs we could get more. 205 // For now just use the first interface to get the ips this should be good enough for most cases. 206 break 207 } 208 networkOpts[network] = perNetOpts 209 } 210 ctr.perNetworkOpts = networkOpts 211 212 return r.configureNetNS(ctr, ctr.state.NetNS) 213 } 214 215 // Produce an InspectNetworkSettings containing information on the container 216 // network. 217 func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, error) { 218 if c.config.NetNsCtr != "" { 219 netNsCtr, err := c.runtime.GetContainer(c.config.NetNsCtr) 220 if err != nil { 221 return nil, err 222 } 223 // see https://github.com/containers/podman/issues/10090 224 // the container has to be locked for syncContainer() 225 netNsCtr.lock.Lock() 226 defer netNsCtr.lock.Unlock() 227 // Have to sync to ensure that state is populated 228 if err := netNsCtr.syncContainer(); err != nil { 229 return nil, err 230 } 231 logrus.Debugf("Container %s shares network namespace, retrieving network info of container %s", c.ID(), c.config.NetNsCtr) 232 233 return netNsCtr.getContainerNetworkInfo() 234 } 235 236 settings := new(define.InspectNetworkSettings) 237 settings.Ports = makeInspectPorts(c.config.PortMappings, c.config.ExposedPorts) 238 239 networks, err := c.networks() 240 if err != nil { 241 return nil, err 242 } 243 244 setDefaultNetworks := func() { 245 settings.Networks = make(map[string]*define.InspectAdditionalNetwork, 1) 246 name := c.NetworkMode() 247 addedNet := new(define.InspectAdditionalNetwork) 248 addedNet.NetworkID = name 249 settings.Networks[name] = addedNet 250 } 251 252 if c.state.NetNS == "" { 253 if networkNSPath, set := c.joinedNetworkNSPath(); networkNSPath != "" { 254 if result, err := c.inspectJoinedNetworkNS(networkNSPath); err == nil { 255 // fallback to dummy configuration 256 settings.InspectBasicNetworkConfig = resultToBasicNetworkConfig(result) 257 } else { 258 // do not propagate error inspecting a joined network ns 259 logrus.Errorf("Inspecting network namespace: %s of container %s: %v", networkNSPath, c.ID(), err) 260 } 261 return settings, nil 262 } else if set { 263 // network none case, if running allow user to join netns via sandbox key 264 // https://github.com/containers/podman/issues/16716 265 if c.state.PID > 0 { 266 settings.SandboxKey = fmt.Sprintf("/proc/%d/ns/net", c.state.PID) 267 } 268 } 269 // We can't do more if the network is down. 270 // We still want to make dummy configurations for each network 271 // the container joined. 272 if len(networks) > 0 { 273 settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks)) 274 for net, opts := range networks { 275 cniNet := new(define.InspectAdditionalNetwork) 276 cniNet.NetworkID = net 277 cniNet.Aliases = opts.Aliases 278 settings.Networks[net] = cniNet 279 } 280 } else { 281 setDefaultNetworks() 282 } 283 284 return settings, nil 285 } 286 287 // Set network namespace path 288 settings.SandboxKey = c.state.NetNS 289 290 netStatus := c.getNetworkStatus() 291 // If this is empty, we're probably slirp4netns 292 if len(netStatus) == 0 { 293 return settings, nil 294 } 295 296 // If we have networks - handle that here 297 if len(networks) > 0 { 298 if len(networks) != len(netStatus) { 299 return nil, fmt.Errorf("network inspection mismatch: asked to join %d network(s) %v, but have information on %d network(s): %w", len(networks), networks, len(netStatus), define.ErrInternal) 300 } 301 302 settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks)) 303 304 for name, opts := range networks { 305 result := netStatus[name] 306 addedNet := new(define.InspectAdditionalNetwork) 307 addedNet.NetworkID = name 308 addedNet.Aliases = opts.Aliases 309 addedNet.InspectBasicNetworkConfig = resultToBasicNetworkConfig(result) 310 311 settings.Networks[name] = addedNet 312 } 313 314 // if not only the default network is connected we can return here 315 // otherwise we have to populate the InspectBasicNetworkConfig settings 316 _, isDefaultNet := networks[c.runtime.config.Network.DefaultNetwork] 317 if !(len(networks) == 1 && isDefaultNet) { 318 return settings, nil 319 } 320 } else { 321 setDefaultNetworks() 322 } 323 324 // If not joining networks, we should have at most 1 result 325 if len(netStatus) > 1 { 326 return nil, fmt.Errorf("should have at most 1 network status result if not joining networks, instead got %d: %w", len(netStatus), define.ErrInternal) 327 } 328 329 if len(netStatus) == 1 { 330 for _, status := range netStatus { 331 settings.InspectBasicNetworkConfig = resultToBasicNetworkConfig(status) 332 } 333 } 334 return settings, nil 335 } 336 337 // resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI 338 // result 339 func resultToBasicNetworkConfig(result types.StatusBlock) define.InspectBasicNetworkConfig { 340 config := define.InspectBasicNetworkConfig{} 341 interfaceNames := make([]string, 0, len(result.Interfaces)) 342 for interfaceName := range result.Interfaces { 343 interfaceNames = append(interfaceNames, interfaceName) 344 } 345 // ensure consistent inspect results by sorting 346 sort.Strings(interfaceNames) 347 for _, interfaceName := range interfaceNames { 348 netInt := result.Interfaces[interfaceName] 349 for _, netAddress := range netInt.Subnets { 350 size, _ := netAddress.IPNet.Mask.Size() 351 if netAddress.IPNet.IP.To4() != nil { 352 // ipv4 353 if config.IPAddress == "" { 354 config.IPAddress = netAddress.IPNet.IP.String() 355 config.IPPrefixLen = size 356 config.Gateway = netAddress.Gateway.String() 357 } else { 358 config.SecondaryIPAddresses = append(config.SecondaryIPAddresses, define.Address{Addr: netAddress.IPNet.IP.String(), PrefixLength: size}) 359 } 360 } else { 361 // ipv6 362 if config.GlobalIPv6Address == "" { 363 config.GlobalIPv6Address = netAddress.IPNet.IP.String() 364 config.GlobalIPv6PrefixLen = size 365 config.IPv6Gateway = netAddress.Gateway.String() 366 } else { 367 config.SecondaryIPv6Addresses = append(config.SecondaryIPv6Addresses, define.Address{Addr: netAddress.IPNet.IP.String(), PrefixLength: size}) 368 } 369 } 370 } 371 if config.MacAddress == "" { 372 config.MacAddress = netInt.MacAddress.String() 373 } else { 374 config.AdditionalMacAddresses = append(config.AdditionalMacAddresses, netInt.MacAddress.String()) 375 } 376 } 377 return config 378 } 379 380 // NetworkDisconnect removes a container from the network 381 func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error { 382 // only the bridge mode supports cni networks 383 if err := isBridgeNetMode(c.config.NetMode); err != nil { 384 return err 385 } 386 387 c.lock.Lock() 388 defer c.lock.Unlock() 389 390 networks, err := c.networks() 391 if err != nil { 392 return err 393 } 394 395 // check if network exists and if the input is an ID we get the name 396 // CNI and netavark and the libpod db only uses names so it is important that we only use the name 397 netName, err = c.runtime.normalizeNetworkName(netName) 398 if err != nil { 399 return err 400 } 401 402 _, nameExists := networks[netName] 403 if !nameExists && len(networks) > 0 { 404 return fmt.Errorf("container %s is not connected to network %s", nameOrID, netName) 405 } 406 407 if err := c.syncContainer(); err != nil { 408 return err 409 } 410 // get network status before we disconnect 411 networkStatus := c.getNetworkStatus() 412 413 if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil { 414 return err 415 } 416 417 c.newNetworkEvent(events.NetworkDisconnect, netName) 418 if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { 419 return nil 420 } 421 422 if c.state.NetNS == "" { 423 return fmt.Errorf("unable to disconnect %s from %s: %w", nameOrID, netName, define.ErrNoNetwork) 424 } 425 426 opts := types.NetworkOptions{ 427 ContainerID: c.config.ID, 428 ContainerName: getNetworkPodName(c), 429 } 430 opts.PortMappings = c.convertPortMappings() 431 opts.Networks = map[string]types.PerNetworkOptions{ 432 netName: networks[netName], 433 } 434 435 if err := c.runtime.teardownNetworkBackend(c.state.NetNS, opts); err != nil { 436 return err 437 } 438 439 // update network status if container is running 440 oldStatus, statusExist := networkStatus[netName] 441 delete(networkStatus, netName) 442 c.state.NetworkStatus = networkStatus 443 err = c.save() 444 if err != nil { 445 return err 446 } 447 448 // Reload ports when there are still connected networks, maybe we removed the network interface with the child ip. 449 // Reloading without connected networks does not make sense, so we can skip this step. 450 if rootless.IsRootless() && len(networkStatus) > 0 { 451 if err := c.reloadRootlessRLKPortMapping(); err != nil { 452 return err 453 } 454 } 455 456 // Update resolv.conf if required 457 if statusExist { 458 stringIPs := make([]string, 0, len(oldStatus.DNSServerIPs)) 459 for _, ip := range oldStatus.DNSServerIPs { 460 stringIPs = append(stringIPs, ip.String()) 461 } 462 if len(stringIPs) > 0 { 463 logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs) 464 if err := c.removeNameserver(stringIPs); err != nil { 465 return err 466 } 467 } 468 469 // update /etc/hosts file 470 if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { 471 // sync the names with c.getHostsEntries() 472 names := []string{c.Hostname(), c.config.Name} 473 rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...) 474 if len(rm) > 0 { 475 // make sure to lock this file to prevent concurrent writes when 476 // this is used a net dependency container 477 lock, err := lockfile.GetLockFile(file) 478 if err != nil { 479 return fmt.Errorf("failed to lock hosts file: %w", err) 480 } 481 logrus.Debugf("Remove /etc/hosts entries %v", rm) 482 lock.Lock() 483 err = etchosts.Remove(file, rm) 484 lock.Unlock() 485 if err != nil { 486 return err 487 } 488 } 489 } 490 } 491 return nil 492 } 493 494 // ConnectNetwork connects a container to a given network 495 func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNetworkOptions) error { 496 // only the bridge mode supports networks 497 if err := isBridgeNetMode(c.config.NetMode); err != nil { 498 return err 499 } 500 501 c.lock.Lock() 502 defer c.lock.Unlock() 503 504 networks, err := c.networks() 505 if err != nil { 506 return err 507 } 508 509 // check if network exists and if the input is an ID we get the name 510 // CNI and netavark and the libpod db only uses names so it is important that we only use the name 511 netName, err = c.runtime.normalizeNetworkName(netName) 512 if err != nil { 513 return err 514 } 515 516 if err := c.syncContainer(); err != nil { 517 return err 518 } 519 520 // get network status before we connect 521 networkStatus := c.getNetworkStatus() 522 523 netOpts.Aliases = append(netOpts.Aliases, getExtraNetworkAliases(c)...) 524 525 if netOpts.InterfaceName == "" { 526 netOpts.InterfaceName = getFreeInterfaceName(networks) 527 if netOpts.InterfaceName == "" { 528 return errors.New("could not find free network interface name") 529 } 530 } 531 532 if err := c.runtime.state.NetworkConnect(c, netName, netOpts); err != nil { 533 // Docker compat: treat requests to attach already attached networks as a no-op, ignoring opts 534 if errors.Is(err, define.ErrNetworkConnected) && !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { 535 return nil 536 } 537 538 return err 539 } 540 c.newNetworkEvent(events.NetworkConnect, netName) 541 if !c.ensureState(define.ContainerStateRunning, define.ContainerStateCreated) { 542 return nil 543 } 544 if c.state.NetNS == "" { 545 return fmt.Errorf("unable to connect %s to %s: %w", nameOrID, netName, define.ErrNoNetwork) 546 } 547 548 opts := types.NetworkOptions{ 549 ContainerID: c.config.ID, 550 ContainerName: getNetworkPodName(c), 551 } 552 opts.PortMappings = c.convertPortMappings() 553 opts.Networks = map[string]types.PerNetworkOptions{ 554 netName: netOpts, 555 } 556 557 results, err := c.runtime.setUpNetwork(c.state.NetNS, opts) 558 if err != nil { 559 return err 560 } 561 if len(results) != 1 { 562 return errors.New("when adding aliases, results must be of length 1") 563 } 564 565 // we need to get the old host entries before we add the new one to the status 566 // if we do not add do it here we will get the wrong existing entries which will throw of the logic 567 // we could also copy the map but this does not seem worth it 568 // sync the hostNames with c.getHostsEntries() 569 hostNames := []string{c.Hostname(), c.config.Name} 570 oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...) 571 572 // update network status 573 if networkStatus == nil { 574 networkStatus = make(map[string]types.StatusBlock, 1) 575 } 576 networkStatus[netName] = results[netName] 577 c.state.NetworkStatus = networkStatus 578 579 err = c.save() 580 if err != nil { 581 return err 582 } 583 584 // The first network needs a port reload to set the correct child ip for the rootlessport process. 585 // Adding a second network does not require a port reload because the child ip is still valid. 586 if rootless.IsRootless() && len(networks) == 0 { 587 if err := c.reloadRootlessRLKPortMapping(); err != nil { 588 return err 589 } 590 } 591 592 ipv6 := c.checkForIPv6(networkStatus) 593 594 // Update resolv.conf if required 595 stringIPs := make([]string, 0, len(results[netName].DNSServerIPs)) 596 for _, ip := range results[netName].DNSServerIPs { 597 if (ip.To4() == nil) && !ipv6 { 598 continue 599 } 600 stringIPs = append(stringIPs, ip.String()) 601 } 602 if len(stringIPs) > 0 { 603 logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs) 604 if err := c.addNameserver(stringIPs); err != nil { 605 return err 606 } 607 } 608 609 // update /etc/hosts file 610 if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { 611 // make sure to lock this file to prevent concurrent writes when 612 // this is used a net dependency container 613 lock, err := lockfile.GetLockFile(file) 614 if err != nil { 615 return fmt.Errorf("failed to lock hosts file: %w", err) 616 } 617 new := etchosts.GetNetworkHostEntries(results, hostNames...) 618 logrus.Debugf("Add /etc/hosts entries %v", new) 619 // use special AddIfExists API to make sure we only add new entries if an old one exists 620 // see the AddIfExists() comment for more information 621 lock.Lock() 622 err = etchosts.AddIfExists(file, oldHostEntries, new) 623 lock.Unlock() 624 if err != nil { 625 return err 626 } 627 } 628 629 return nil 630 } 631 632 // get a free interface name for a new network 633 // return an empty string if no free name was found 634 func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string { 635 ifNames := make([]string, 0, len(networks)) 636 for _, opts := range networks { 637 ifNames = append(ifNames, opts.InterfaceName) 638 } 639 for i := 0; i < 100000; i++ { 640 ifName := fmt.Sprintf("eth%d", i) 641 if !util.StringInSlice(ifName, ifNames) { 642 return ifName 643 } 644 } 645 return "" 646 } 647 648 func getExtraNetworkAliases(c *Container) []string { 649 // always add the short id as alias for docker compat 650 alias := []string{c.config.ID[:12]} 651 // if an explicit hostname was set add it as well 652 if c.config.Spec.Hostname != "" { 653 alias = append(alias, c.config.Spec.Hostname) 654 } 655 return alias 656 } 657 658 // DisconnectContainerFromNetwork removes a container from its network 659 func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error { 660 ctr, err := r.LookupContainer(nameOrID) 661 if err != nil { 662 return err 663 } 664 return ctr.NetworkDisconnect(nameOrID, netName, force) 665 } 666 667 // ConnectContainerToNetwork connects a container to a network 668 func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, netOpts types.PerNetworkOptions) error { 669 ctr, err := r.LookupContainer(nameOrID) 670 if err != nil { 671 return err 672 } 673 return ctr.NetworkConnect(nameOrID, netName, netOpts) 674 } 675 676 // normalizeNetworkName takes a network name, a partial or a full network ID and returns the network name. 677 // If the network is not found an error is returned. 678 func (r *Runtime) normalizeNetworkName(nameOrID string) (string, error) { 679 net, err := r.network.NetworkInspect(nameOrID) 680 if err != nil { 681 return "", err 682 } 683 return net.Name, nil 684 } 685 686 // ocicniPortsToNetTypesPorts convert the old port format to the new one 687 // while deduplicating ports into ranges 688 func ocicniPortsToNetTypesPorts(ports []types.OCICNIPortMapping) []types.PortMapping { 689 if len(ports) == 0 { 690 return nil 691 } 692 693 newPorts := make([]types.PortMapping, 0, len(ports)) 694 695 // first sort the ports 696 sort.Slice(ports, func(i, j int) bool { 697 return compareOCICNIPorts(ports[i], ports[j]) 698 }) 699 700 // we already check if the slice is empty so we can use the first element 701 currentPort := types.PortMapping{ 702 HostIP: ports[0].HostIP, 703 HostPort: uint16(ports[0].HostPort), 704 ContainerPort: uint16(ports[0].ContainerPort), 705 Protocol: ports[0].Protocol, 706 Range: 1, 707 } 708 709 for i := 1; i < len(ports); i++ { 710 if ports[i].HostIP == currentPort.HostIP && 711 ports[i].Protocol == currentPort.Protocol && 712 ports[i].HostPort-int32(currentPort.Range) == int32(currentPort.HostPort) && 713 ports[i].ContainerPort-int32(currentPort.Range) == int32(currentPort.ContainerPort) { 714 currentPort.Range++ 715 } else { 716 newPorts = append(newPorts, currentPort) 717 currentPort = types.PortMapping{ 718 HostIP: ports[i].HostIP, 719 HostPort: uint16(ports[i].HostPort), 720 ContainerPort: uint16(ports[i].ContainerPort), 721 Protocol: ports[i].Protocol, 722 Range: 1, 723 } 724 } 725 } 726 newPorts = append(newPorts, currentPort) 727 return newPorts 728 } 729 730 // compareOCICNIPorts will sort the ocicni ports by 731 // 1) host ip 732 // 2) protocol 733 // 3) hostPort 734 // 4) container port 735 func compareOCICNIPorts(i, j types.OCICNIPortMapping) bool { 736 if i.HostIP != j.HostIP { 737 return i.HostIP < j.HostIP 738 } 739 740 if i.Protocol != j.Protocol { 741 return i.Protocol < j.Protocol 742 } 743 744 if i.HostPort != j.HostPort { 745 return i.HostPort < j.HostPort 746 } 747 748 return i.ContainerPort < j.ContainerPort 749 }