github.com/AbhinandanKurakure/podman/v3@v3.4.10/libpod/network/cni/run.go (about) 1 // +build linux 2 3 package cni 4 5 import ( 6 "context" 7 "net" 8 "os" 9 "strings" 10 11 "github.com/containernetworking/cni/libcni" 12 cnitypes "github.com/containernetworking/cni/pkg/types" 13 "github.com/containernetworking/cni/pkg/types/current" 14 "github.com/containernetworking/plugins/pkg/ns" 15 "github.com/containers/podman/v3/libpod/define" 16 "github.com/containers/podman/v3/libpod/network/types" 17 "github.com/hashicorp/go-multierror" 18 "github.com/pkg/errors" 19 "github.com/sirupsen/logrus" 20 "github.com/vishvananda/netlink" 21 ) 22 23 // Setup will setup the container network namespace. It returns 24 // a map of StatusBlocks, the key is the network name. 25 func (n *cniNetwork) Setup(namespacePath string, options types.SetupOptions) (map[string]types.StatusBlock, error) { 26 n.lock.Lock() 27 defer n.lock.Unlock() 28 err := n.loadNetworks() 29 if err != nil { 30 return nil, err 31 } 32 33 if namespacePath == "" { 34 return nil, errors.New("namespacePath is empty") 35 } 36 if options.ContainerID == "" { 37 return nil, errors.New("ContainerID is empty") 38 } 39 if len(options.Networks) == 0 { 40 return nil, errors.New("must specify at least one network") 41 } 42 for name, netOpts := range options.Networks { 43 network := n.networks[name] 44 if network == nil { 45 return nil, errors.Wrapf(define.ErrNoSuchNetwork, "network %s", name) 46 } 47 err := validatePerNetworkOpts(network, netOpts) 48 if err != nil { 49 return nil, err 50 } 51 } 52 53 // set the loopback adapter up in the container netns 54 err = ns.WithNetNSPath(namespacePath, func(_ ns.NetNS) error { 55 link, err := netlink.LinkByName("lo") 56 if err == nil { 57 err = netlink.LinkSetUp(link) 58 } 59 return err 60 }) 61 if err != nil { 62 return nil, errors.Wrapf(err, "failed to set the loopback adapter up") 63 } 64 65 var retErr error 66 teardownOpts := options 67 teardownOpts.Networks = map[string]types.PerNetworkOptions{} 68 // make sure to teardown the already connected networks on error 69 defer func() { 70 if retErr != nil { 71 if len(teardownOpts.Networks) > 0 { 72 err := n.teardown(namespacePath, types.TeardownOptions(teardownOpts)) 73 if err != nil { 74 logrus.Warn(err) 75 } 76 } 77 } 78 }() 79 80 ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings) 81 if err != nil { 82 return nil, err 83 } 84 85 results := make(map[string]types.StatusBlock, len(options.Networks)) 86 for name, netOpts := range options.Networks { 87 network := n.networks[name] 88 rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, netOpts) 89 90 // If we have more than one static ip we need parse the ips via runtime config, 91 // make sure to add the ips capability to the first plugin otherwise it doesn't get the ips 92 if len(netOpts.StaticIPs) > 0 && !network.cniNet.Plugins[0].Network.Capabilities["ips"] { 93 caps := make(map[string]interface{}) 94 caps["capabilities"] = map[string]bool{"ips": true} 95 network.cniNet.Plugins[0], retErr = libcni.InjectConf(network.cniNet.Plugins[0], caps) 96 if retErr != nil { 97 return nil, retErr 98 } 99 } 100 101 var res cnitypes.Result 102 res, retErr = n.cniConf.AddNetworkList(context.Background(), network.cniNet, rt) 103 // Add this network to teardown opts since it is now connected. 104 // Also add this if an errors was returned since we want to call teardown on this regardless. 105 teardownOpts.Networks[name] = netOpts 106 if retErr != nil { 107 return nil, retErr 108 } 109 110 var cnires *current.Result 111 cnires, retErr = current.GetResult(res) 112 if retErr != nil { 113 return nil, retErr 114 } 115 logrus.Debugf("cni result for container %s network %s: %v", options.ContainerID, name, cnires) 116 var status types.StatusBlock 117 status, retErr = cniResultToStatus(cnires) 118 if retErr != nil { 119 return nil, retErr 120 } 121 results[name] = status 122 } 123 return results, nil 124 } 125 126 // cniResultToStatus convert the cni result to status block 127 func cniResultToStatus(cniResult *current.Result) (types.StatusBlock, error) { 128 result := types.StatusBlock{} 129 nameservers := make([]net.IP, 0, len(cniResult.DNS.Nameservers)) 130 for _, nameserver := range cniResult.DNS.Nameservers { 131 ip := net.ParseIP(nameserver) 132 if ip == nil { 133 return result, errors.Errorf("failed to parse cni nameserver ip %s", nameserver) 134 } 135 nameservers = append(nameservers, ip) 136 } 137 result.DNSServerIPs = nameservers 138 result.DNSSearchDomains = cniResult.DNS.Search 139 140 interfaces := make(map[string]types.NetInterface) 141 for _, ip := range cniResult.IPs { 142 if ip.Interface == nil { 143 // we do no expect ips without an interface 144 continue 145 } 146 if len(cniResult.Interfaces) <= *ip.Interface { 147 return result, errors.Errorf("invalid cni result, interface index %d out of range", *ip.Interface) 148 } 149 cniInt := cniResult.Interfaces[*ip.Interface] 150 netInt, ok := interfaces[cniInt.Name] 151 if ok { 152 netInt.Networks = append(netInt.Networks, types.NetAddress{ 153 Subnet: types.IPNet{IPNet: ip.Address}, 154 Gateway: ip.Gateway, 155 }) 156 interfaces[cniInt.Name] = netInt 157 } else { 158 mac, err := net.ParseMAC(cniInt.Mac) 159 if err != nil { 160 return result, err 161 } 162 interfaces[cniInt.Name] = types.NetInterface{ 163 MacAddress: mac, 164 Networks: []types.NetAddress{{ 165 Subnet: types.IPNet{IPNet: ip.Address}, 166 Gateway: ip.Gateway, 167 }}, 168 } 169 } 170 } 171 result.Interfaces = interfaces 172 return result, nil 173 } 174 175 // validatePerNetworkOpts checks that all given static ips are in a subnet on this network 176 func validatePerNetworkOpts(network *network, netOpts types.PerNetworkOptions) error { 177 if netOpts.InterfaceName == "" { 178 return errors.Errorf("interface name on network %s is empty", network.libpodNet.Name) 179 } 180 outer: 181 for _, ip := range netOpts.StaticIPs { 182 for _, s := range network.libpodNet.Subnets { 183 if s.Subnet.Contains(ip) { 184 continue outer 185 } 186 } 187 return errors.Errorf("requested static ip %s not in any subnet on network %s", ip.String(), network.libpodNet.Name) 188 } 189 if len(netOpts.Aliases) > 0 && !network.libpodNet.DNSEnabled { 190 return errors.New("cannot set aliases on a network without dns enabled") 191 } 192 return nil 193 } 194 195 func getRuntimeConfig(netns, conName, conID, networkName string, ports []cniPortMapEntry, opts types.PerNetworkOptions) *libcni.RuntimeConf { 196 rt := &libcni.RuntimeConf{ 197 ContainerID: conID, 198 NetNS: netns, 199 IfName: opts.InterfaceName, 200 Args: [][2]string{ 201 {"IgnoreUnknown", "1"}, 202 // Do not set the K8S env vars, see https://github.com/containers/podman/issues/12083. 203 // Only K8S_POD_NAME is used by dnsname to get the container name. 204 {"K8S_POD_NAME", conName}, 205 }, 206 CapabilityArgs: map[string]interface{}{}, 207 } 208 209 // Propagate environment CNI_ARGS 210 for _, kvpairs := range strings.Split(os.Getenv("CNI_ARGS"), ";") { 211 if keyval := strings.SplitN(kvpairs, "=", 2); len(keyval) == 2 { 212 rt.Args = append(rt.Args, [2]string{keyval[0], keyval[1]}) 213 } 214 } 215 216 // Add mac address to cni args 217 if len(opts.StaticMAC) > 0 { 218 rt.Args = append(rt.Args, [2]string{"MAC", opts.StaticMAC.String()}) 219 } 220 221 if len(opts.StaticIPs) == 1 { 222 // Add a single IP to the args field. CNI plugins < 1.0.0 223 // do not support multiple ips via capability args. 224 rt.Args = append(rt.Args, [2]string{"IP", opts.StaticIPs[0].String()}) 225 } else if len(opts.StaticIPs) > 1 { 226 // Set the static ips in the capability args 227 // to support more than one static ip per network. 228 rt.CapabilityArgs["ips"] = opts.StaticIPs 229 } 230 231 // Set network aliases for the dnsname plugin. 232 if len(opts.Aliases) > 0 { 233 rt.CapabilityArgs["aliases"] = map[string][]string{ 234 networkName: opts.Aliases, 235 } 236 } 237 238 // Set PortMappings in Capabilities 239 if len(ports) > 0 { 240 rt.CapabilityArgs["portMappings"] = ports 241 } 242 243 return rt 244 } 245 246 // Teardown will teardown the container network namespace. 247 func (n *cniNetwork) Teardown(namespacePath string, options types.TeardownOptions) error { 248 n.lock.Lock() 249 defer n.lock.Unlock() 250 err := n.loadNetworks() 251 if err != nil { 252 return err 253 } 254 return n.teardown(namespacePath, options) 255 } 256 257 func (n *cniNetwork) teardown(namespacePath string, options types.TeardownOptions) error { 258 // Note: An empty namespacePath is allowed because some plugins 259 // still need teardown, for example ipam should remove used ip allocations. 260 261 ports, err := convertSpecgenPortsToCNIPorts(options.PortMappings) 262 if err != nil { 263 return err 264 } 265 266 var multiErr *multierror.Error 267 for name, netOpts := range options.Networks { 268 rt := getRuntimeConfig(namespacePath, options.ContainerName, options.ContainerID, name, ports, netOpts) 269 270 cniConfList, newRt, err := getCachedNetworkConfig(n.cniConf, name, rt) 271 if err == nil { 272 rt = newRt 273 } else { 274 logrus.Warnf("failed to load cached network config: %v, falling back to loading network %s from disk", err, name) 275 network := n.networks[name] 276 if network == nil { 277 multiErr = multierror.Append(multiErr, errors.Wrapf(define.ErrNoSuchNetwork, "network %s", name)) 278 continue 279 } 280 cniConfList = network.cniNet 281 } 282 283 err = n.cniConf.DelNetworkList(context.Background(), cniConfList, rt) 284 if err != nil { 285 multiErr = multierror.Append(multiErr, err) 286 } 287 } 288 return multiErr.ErrorOrNil() 289 } 290 291 func getCachedNetworkConfig(cniConf *libcni.CNIConfig, name string, rt *libcni.RuntimeConf) (*libcni.NetworkConfigList, *libcni.RuntimeConf, error) { 292 cniConfList := &libcni.NetworkConfigList{ 293 Name: name, 294 } 295 confBytes, rt, err := cniConf.GetNetworkListCachedConfig(cniConfList, rt) 296 if err != nil { 297 return nil, nil, err 298 } else if confBytes == nil { 299 return nil, nil, errors.Errorf("network %s not found in CNI cache", name) 300 } 301 302 cniConfList, err = libcni.ConfListFromBytes(confBytes) 303 if err != nil { 304 return nil, nil, err 305 } 306 return cniConfList, rt, nil 307 }