github.com/looshlee/cilium@v1.6.12/plugins/cilium-cni/cilium-cni.go (about) 1 // Copyright 2016-2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "context" 19 "fmt" 20 "net" 21 "os" 22 "path/filepath" 23 "runtime" 24 "sort" 25 "syscall" 26 27 "github.com/cilium/cilium/api/v1/models" 28 "github.com/cilium/cilium/common/addressing" 29 "github.com/cilium/cilium/pkg/client" 30 "github.com/cilium/cilium/pkg/datapath/linux/route" 31 "github.com/cilium/cilium/pkg/defaults" 32 "github.com/cilium/cilium/pkg/endpoint/connector" 33 endpointid "github.com/cilium/cilium/pkg/endpoint/id" 34 "github.com/cilium/cilium/pkg/labels" 35 "github.com/cilium/cilium/pkg/logging" 36 "github.com/cilium/cilium/pkg/logging/logfields" 37 "github.com/cilium/cilium/pkg/netns" 38 "github.com/cilium/cilium/pkg/option" 39 "github.com/cilium/cilium/pkg/uuid" 40 "github.com/cilium/cilium/pkg/version" 41 chainingapi "github.com/cilium/cilium/plugins/cilium-cni/chaining/api" 42 _ "github.com/cilium/cilium/plugins/cilium-cni/chaining/awscni" 43 _ "github.com/cilium/cilium/plugins/cilium-cni/chaining/azure" 44 _ "github.com/cilium/cilium/plugins/cilium-cni/chaining/flannel" 45 _ "github.com/cilium/cilium/plugins/cilium-cni/chaining/generic-veth" 46 _ "github.com/cilium/cilium/plugins/cilium-cni/chaining/portmap" 47 "github.com/cilium/cilium/plugins/cilium-cni/types" 48 49 "github.com/containernetworking/cni/pkg/skel" 50 cniTypes "github.com/containernetworking/cni/pkg/types" 51 cniTypesVer "github.com/containernetworking/cni/pkg/types/current" 52 cniVersion "github.com/containernetworking/cni/pkg/version" 53 "github.com/containernetworking/plugins/pkg/ns" 54 "github.com/sirupsen/logrus" 55 "github.com/vishvananda/netlink" 56 57 "golang.org/x/sys/unix" 58 ) 59 60 var ( 61 log = logging.DefaultLogger.WithField(logfields.LogSubsys, "cilium-cni") 62 ) 63 64 func init() { 65 logging.SetLogLevel(logrus.DebugLevel) 66 runtime.LockOSThread() 67 } 68 69 type CmdState struct { 70 Endpoint *models.EndpointChangeRequest 71 IP6 addressing.CiliumIPv6 72 IP6routes []route.Route 73 IP4 addressing.CiliumIPv4 74 IP4routes []route.Route 75 Client *client.Client 76 HostAddr *models.NodeAddressing 77 } 78 79 func main() { 80 skel.PluginMain(cmdAdd, 81 nil, 82 cmdDel, 83 cniVersion.PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1"), 84 "Cilium CNI plugin "+version.Version) 85 } 86 87 func ipv6IsEnabled(ipam *models.IPAMResponse) bool { 88 if ipam == nil || ipam.Address.IPV6 == "" { 89 return false 90 } 91 92 if ipam.HostAddressing != nil && ipam.HostAddressing.IPV6 != nil { 93 return ipam.HostAddressing.IPV6.Enabled 94 } 95 96 return true 97 } 98 99 func ipv4IsEnabled(ipam *models.IPAMResponse) bool { 100 if ipam == nil || ipam.Address.IPV4 == "" { 101 return false 102 } 103 104 if ipam.HostAddressing != nil && ipam.HostAddressing.IPV4 != nil { 105 return ipam.HostAddressing.IPV4.Enabled 106 } 107 108 return true 109 } 110 111 func releaseIP(client *client.Client, ip string) { 112 if ip != "" { 113 if err := client.IPAMReleaseIP(ip); err != nil { 114 log.WithError(err).WithField(logfields.IPAddr, ip).Warn("Unable to release IP") 115 } 116 } 117 } 118 119 func releaseIPs(client *client.Client, addr *models.AddressPair) { 120 releaseIP(client, addr.IPV6) 121 releaseIP(client, addr.IPV4) 122 } 123 124 func addIPConfigToLink(ip addressing.CiliumIP, routes []route.Route, link netlink.Link, ifName string) error { 125 log.WithFields(logrus.Fields{ 126 logfields.IPAddr: ip, 127 "netLink": logfields.Repr(link), 128 logfields.Interface: ifName, 129 }).Debug("Configuring link") 130 131 addr := &netlink.Addr{IPNet: ip.EndpointPrefix()} 132 if ip.IsIPv6() { 133 addr.Flags = syscall.IFA_F_NODAD 134 } 135 if err := netlink.AddrAdd(link, addr); err != nil { 136 return fmt.Errorf("failed to add addr to %q: %v", ifName, err) 137 } 138 139 // ipvlan needs to be UP before we add routes, and can only be UPed after 140 // we added an IP address. 141 if err := netlink.LinkSetUp(link); err != nil { 142 return fmt.Errorf("failed to set %q UP: %v", ifName, err) 143 } 144 145 // Sort provided routes to make sure we apply any more specific 146 // routes first which may be used as nexthops in wider routes 147 sort.Sort(route.ByMask(routes)) 148 149 for _, r := range routes { 150 log.WithField("route", logfields.Repr(r)).Debug("Adding route") 151 rt := &netlink.Route{ 152 LinkIndex: link.Attrs().Index, 153 Scope: netlink.SCOPE_UNIVERSE, 154 Dst: &r.Prefix, 155 MTU: r.MTU, 156 } 157 158 if r.Nexthop == nil { 159 rt.Scope = netlink.SCOPE_LINK 160 } else { 161 rt.Gw = *r.Nexthop 162 } 163 164 if err := netlink.RouteAdd(rt); err != nil { 165 if !os.IsExist(err) { 166 return fmt.Errorf("failed to add route '%s via %v dev %v': %v", 167 r.Prefix.String(), r.Nexthop, ifName, err) 168 } 169 } 170 } 171 172 return nil 173 } 174 175 func configureIface(ipam *models.IPAMResponse, ifName string, state *CmdState) (string, error) { 176 l, err := netlink.LinkByName(ifName) 177 if err != nil { 178 return "", fmt.Errorf("failed to lookup %q: %v", ifName, err) 179 } 180 181 if err := netlink.LinkSetUp(l); err != nil { 182 return "", fmt.Errorf("failed to set %q UP: %v", ifName, err) 183 } 184 185 if ipv4IsEnabled(ipam) { 186 if err := addIPConfigToLink(state.IP4, state.IP4routes, l, ifName); err != nil { 187 return "", fmt.Errorf("error configuring IPv4: %s", err.Error()) 188 } 189 } 190 191 if ipv6IsEnabled(ipam) { 192 if err := addIPConfigToLink(state.IP6, state.IP6routes, l, ifName); err != nil { 193 return "", fmt.Errorf("error configuring IPv6: %s", err.Error()) 194 } 195 } 196 197 if err := netlink.LinkSetUp(l); err != nil { 198 return "", fmt.Errorf("failed to set %q UP: %v", ifName, err) 199 } 200 201 if l.Attrs() != nil { 202 return l.Attrs().HardwareAddr.String(), nil 203 } 204 205 return "", nil 206 } 207 208 func newCNIRoute(r route.Route) *cniTypes.Route { 209 rt := &cniTypes.Route{ 210 Dst: r.Prefix, 211 } 212 if r.Nexthop != nil { 213 rt.GW = *r.Nexthop 214 } 215 216 return rt 217 } 218 219 func prepareIP(ipAddr string, isIPv6 bool, state *CmdState, mtu int) (*cniTypesVer.IPConfig, []*cniTypes.Route, error) { 220 var ( 221 routes []route.Route 222 err error 223 gw string 224 ipVersion string 225 ip addressing.CiliumIP 226 ) 227 228 if isIPv6 { 229 if state.IP6, err = addressing.NewCiliumIPv6(ipAddr); err != nil { 230 return nil, nil, err 231 } 232 if state.IP6routes, err = connector.IPv6Routes(state.HostAddr, mtu); err != nil { 233 return nil, nil, err 234 } 235 routes = state.IP6routes 236 ip = state.IP6 237 gw = connector.IPv6Gateway(state.HostAddr) 238 ipVersion = "6" 239 } else { 240 if state.IP4, err = addressing.NewCiliumIPv4(ipAddr); err != nil { 241 return nil, nil, err 242 } 243 if state.IP4routes, err = connector.IPv4Routes(state.HostAddr, mtu); err != nil { 244 return nil, nil, err 245 } 246 routes = state.IP4routes 247 ip = state.IP4 248 gw = connector.IPv4Gateway(state.HostAddr) 249 ipVersion = "4" 250 } 251 252 rt := []*cniTypes.Route{} 253 for _, r := range routes { 254 rt = append(rt, newCNIRoute(r)) 255 } 256 257 gwIP := net.ParseIP(gw) 258 if gwIP == nil { 259 return nil, nil, fmt.Errorf("Invalid gateway address: %s", gw) 260 } 261 262 return &cniTypesVer.IPConfig{ 263 Address: *ip.EndpointPrefix(), 264 Gateway: gwIP, 265 Version: ipVersion, 266 }, rt, nil 267 } 268 269 func cmdAdd(args *skel.CmdArgs) (err error) { 270 var ( 271 ipConfig *cniTypesVer.IPConfig 272 routes []*cniTypes.Route 273 ipam *models.IPAMResponse 274 n *types.NetConf 275 c *client.Client 276 netNs ns.NetNS 277 ) 278 279 logger := log.WithField("eventUUID", uuid.NewUUID()) 280 logger.Debugf("Processing CNI ADD request %#v", args) 281 282 n, err = types.LoadNetConf(args.StdinData) 283 if err != nil { 284 err = fmt.Errorf("unable to parse CNI configuration \"%s\": %s", args.StdinData, err) 285 return 286 } 287 logger.Debugf("CNI NetConf: %#v", n) 288 if n.PrevResult != nil { 289 logger.Debugf("CNI Previous result: %#v", n.PrevResult) 290 } 291 292 cniArgs := types.ArgsSpec{} 293 if err = cniTypes.LoadArgs(args.Args, &cniArgs); err != nil { 294 err = fmt.Errorf("unable to extract CNI arguments: %s", err) 295 return 296 } 297 logger.Debugf("CNI Args: %#v", cniArgs) 298 299 c, err = client.NewDefaultClientWithTimeout(defaults.ClientConnectTimeout) 300 if err != nil { 301 err = fmt.Errorf("unable to connect to Cilium daemon: %s", client.Hint(err)) 302 return 303 } 304 305 if len(n.NetConf.RawPrevResult) != 0 && n.Name != chainingapi.DefaultConfigName { 306 if chainAction := chainingapi.Lookup(n.Name); chainAction != nil { 307 var ( 308 res *cniTypesVer.Result 309 ctx = chainingapi.PluginContext{ 310 Logger: logger, 311 Args: args, 312 CniArgs: cniArgs, 313 NetConf: n, 314 Client: c, 315 } 316 ) 317 318 if chainAction.ImplementsAdd() { 319 res, err = chainAction.Add(context.TODO(), ctx) 320 if err != nil { 321 return 322 } 323 logger.Debugf("Returning result %#v", res) 324 err = cniTypes.PrintResult(res, n.CNIVersion) 325 return 326 } 327 } else { 328 logger.Warnf("Unknown CNI chaining configuration name '%s'", n.Name) 329 } 330 } 331 332 netNs, err = ns.GetNS(args.Netns) 333 if err != nil { 334 err = fmt.Errorf("failed to open netns %q: %s", args.Netns, err) 335 } 336 defer netNs.Close() 337 338 if err = netns.RemoveIfFromNetNSIfExists(netNs, args.IfName); err != nil { 339 err = fmt.Errorf("failed removing interface %q from namespace %q: %s", 340 args.IfName, args.Netns, err) 341 return 342 } 343 344 addLabels := models.Labels{} 345 346 for _, label := range n.Args.Mesos.NetworkInfo.Labels.Labels { 347 addLabels = append(addLabels, fmt.Sprintf("%s:%s=%s", labels.LabelSourceMesos, label.Key, label.Value)) 348 } 349 350 configResult, err := c.ConfigGet() 351 if err != nil { 352 err = fmt.Errorf("unable to retrieve configuration from cilium-agent: %s", err) 353 return 354 } 355 356 if configResult == nil || configResult.Status == nil { 357 err = fmt.Errorf("did not receive configuration from cilium-agent") 358 return 359 } 360 361 conf := *configResult.Status 362 363 ep := &models.EndpointChangeRequest{ 364 ContainerID: args.ContainerID, 365 Labels: addLabels, 366 State: models.EndpointStateWaitingForIdentity, 367 Addressing: &models.AddressPair{}, 368 K8sPodName: string(cniArgs.K8S_POD_NAME), 369 K8sNamespace: string(cniArgs.K8S_POD_NAMESPACE), 370 } 371 372 switch conf.DatapathMode { 373 case option.DatapathModeVeth: 374 var ( 375 veth *netlink.Veth 376 peer *netlink.Link 377 tmpIfName string 378 ) 379 veth, peer, tmpIfName, err = connector.SetupVeth(ep.ContainerID, int(conf.DeviceMTU), ep) 380 if err != nil { 381 err = fmt.Errorf("unable to set up veth on host side: %s", err) 382 return err 383 } 384 defer func() { 385 if err != nil { 386 if err2 := netlink.LinkDel(veth); err2 != nil { 387 logger.WithError(err2).WithField(logfields.Veth, veth.Name).Warn("failed to clean up and delete veth") 388 } 389 } 390 }() 391 392 if err = netlink.LinkSetNsFd(*peer, int(netNs.Fd())); err != nil { 393 err = fmt.Errorf("unable to move veth pair '%v' to netns: %s", peer, err) 394 return 395 } 396 397 _, _, err = connector.SetupVethRemoteNs(netNs, tmpIfName, args.IfName) 398 if err != nil { 399 err = fmt.Errorf("unable to set up veth on container side: %s", err) 400 return 401 } 402 case option.DatapathModeIpvlan: 403 ipvlanConf := *conf.IpvlanConfiguration 404 index := int(ipvlanConf.MasterDeviceIndex) 405 406 var mapFD int 407 mapFD, err = connector.CreateAndSetupIpvlanSlave( 408 ep.ContainerID, args.IfName, netNs, 409 int(conf.DeviceMTU), index, ipvlanConf.OperationMode, ep, 410 ) 411 if err != nil { 412 err = fmt.Errorf("unable to setup ipvlan datapath: %s", err) 413 return 414 } 415 defer unix.Close(mapFD) 416 } 417 418 podName := string(cniArgs.K8S_POD_NAMESPACE) + "/" + string(cniArgs.K8S_POD_NAME) 419 ipam, err = c.IPAMAllocate("", podName, true) 420 if err != nil { 421 err = fmt.Errorf("unable to allocate IP via local cilium agent: %s", err) 422 return 423 } 424 425 if ipam.Address == nil { 426 err = fmt.Errorf("Invalid IPAM response, missing addressing") 427 return 428 } 429 430 // release addresses on failure 431 defer func() { 432 if err != nil { 433 releaseIP(c, ipam.Address.IPV4) 434 releaseIP(c, ipam.Address.IPV6) 435 } 436 }() 437 438 if err = connector.SufficientAddressing(ipam.HostAddressing); err != nil { 439 err = fmt.Errorf("IP allocation addressing in insufficient: %s", err) 440 return 441 } 442 443 state := CmdState{ 444 Endpoint: ep, 445 Client: c, 446 HostAddr: ipam.HostAddressing, 447 } 448 449 res := &cniTypesVer.Result{} 450 451 if !ipv6IsEnabled(ipam) && !ipv4IsEnabled(ipam) { 452 err = fmt.Errorf("IPAM did not provide IPv4 or IPv6 address") 453 return 454 } 455 456 if ipv6IsEnabled(ipam) { 457 ep.Addressing.IPV6 = ipam.Address.IPV6 458 ep.Addressing.IPV6ExpirationUUID = ipam.IPV6.ExpirationUUID 459 460 ipConfig, routes, err = prepareIP(ep.Addressing.IPV6, true, &state, int(conf.RouteMTU)) 461 if err != nil { 462 err = fmt.Errorf("unable to prepare IP addressing for '%s': %s", ep.Addressing.IPV6, err) 463 return 464 } 465 res.IPs = append(res.IPs, ipConfig) 466 res.Routes = append(res.Routes, routes...) 467 } 468 469 if ipv4IsEnabled(ipam) { 470 ep.Addressing.IPV4 = ipam.Address.IPV4 471 ep.Addressing.IPV4ExpirationUUID = ipam.IPV4.ExpirationUUID 472 473 ipConfig, routes, err = prepareIP(ep.Addressing.IPV4, false, &state, int(conf.RouteMTU)) 474 if err != nil { 475 err = fmt.Errorf("unable to prepare IP addressing for '%s': %s", ep.Addressing.IPV4, err) 476 return 477 } 478 res.IPs = append(res.IPs, ipConfig) 479 res.Routes = append(res.Routes, routes...) 480 481 if conf.IPAMMode == option.IPAMENI { 482 err = eniAdd(ipConfig, ipam.IPV4, conf) 483 if err != nil { 484 err = fmt.Errorf("unable to setup ENI datapath: %s", err) 485 return 486 } 487 } 488 } 489 490 var macAddrStr string 491 if err = netNs.Do(func(_ ns.NetNS) error { 492 allInterfacesPath := filepath.Join("/proc", "sys", "net", "ipv6", "conf", "all", "disable_ipv6") 493 err = connector.WriteSysConfig(allInterfacesPath, "0\n") 494 if err != nil { 495 logger.WithError(err).Warn("unable to enable ipv6 on all interfaces") 496 } 497 macAddrStr, err = configureIface(ipam, args.IfName, &state) 498 return err 499 }); err != nil { 500 err = fmt.Errorf("unable to configure interfaces in container namespace: %s", err) 501 return 502 } 503 504 res.Interfaces = append(res.Interfaces, &cniTypesVer.Interface{ 505 Name: args.IfName, 506 Mac: macAddrStr, 507 Sandbox: "/proc/" + args.Netns + "/ns/net", 508 }) 509 510 // Specify that endpoint must be regenerated synchronously. See GH-4409. 511 ep.SyncBuildEndpoint = true 512 if err = c.EndpointCreate(ep); err != nil { 513 logger.WithError(err).WithFields(logrus.Fields{ 514 logfields.ContainerID: ep.ContainerID}).Warn("Unable to create endpoint") 515 err = fmt.Errorf("Unable to create endpoint: %s", err) 516 return 517 } 518 519 logger.WithFields(logrus.Fields{ 520 logfields.ContainerID: ep.ContainerID}).Debug("Endpoint successfully created") 521 return cniTypes.PrintResult(res, n.CNIVersion) 522 } 523 524 // cmdDel is invoked on CNI DEL 525 // 526 // Note: ENI specific attributes do not need to be released as the ENIs and ENI 527 // IPs can be reused and are not released until the node terminates. 528 func cmdDel(args *skel.CmdArgs) error { 529 // Note about when to return errors: kubelet will retry the deletion 530 // for a long time. Therefore, only return an error for errors which 531 // are guaranteed to be recoverable. 532 533 logger := log.WithField("eventUUID", uuid.NewUUID()) 534 logger.Debugf("Processing CNI DEL request %#v", args) 535 536 n, err := types.LoadNetConf(args.StdinData) 537 if err != nil { 538 return err 539 } 540 logger.Debugf("CNI NetConf: %#v", n) 541 542 cniArgs := types.ArgsSpec{} 543 if err = cniTypes.LoadArgs(args.Args, &cniArgs); err != nil { 544 return fmt.Errorf("unable to extract CNI arguments: %s", err) 545 } 546 logger.Debugf("CNI Args: %#v", cniArgs) 547 548 c, err := client.NewDefaultClientWithTimeout(defaults.ClientConnectTimeout) 549 if err != nil { 550 // this error can be recovered from 551 return fmt.Errorf("unable to connect to Cilium daemon: %s", client.Hint(err)) 552 } 553 554 if n.Name != chainingapi.DefaultConfigName { 555 if chainAction := chainingapi.Lookup(n.Name); chainAction != nil { 556 var ( 557 ctx = chainingapi.PluginContext{ 558 Logger: logger, 559 Args: args, 560 CniArgs: cniArgs, 561 NetConf: n, 562 Client: c, 563 } 564 ) 565 566 if chainAction.ImplementsDelete() { 567 return chainAction.Delete(context.TODO(), ctx) 568 } 569 } else { 570 logger.Warnf("Unknown CNI chaining configuration name '%s'", n.Name) 571 } 572 } 573 574 id := endpointid.NewID(endpointid.ContainerIdPrefix, args.ContainerID) 575 if err := c.EndpointDelete(id); err != nil { 576 // EndpointDelete returns an error in the following scenarios: 577 // DeleteEndpointIDInvalid: Invalid delete parameters, no need to retry 578 // DeleteEndpointIDNotFound: No need to retry 579 // DeleteEndpointIDErrors: Errors encountered while deleting, 580 // the endpoint is always deleted though, no 581 // need to retry 582 log.WithError(err).Warning("Errors encountered while deleting endpoint") 583 } 584 585 netNs, err := ns.GetNS(args.Netns) 586 if err != nil { 587 log.WithError(err).Warningf("Unable to enter namespace %q, will not delete interface", args.Netns) 588 // We are not returning an error as this is very unlikely to be recoverable 589 return nil 590 } 591 defer netNs.Close() 592 593 err = netns.RemoveIfFromNetNSIfExists(netNs, args.IfName) 594 if err != nil { 595 log.WithError(err).Warningf("Unable to delete interface %s in namespace %q, will not delete interface", args.IfName, args.Netns) 596 // We are not returning an error as this is very unlikely to be recoverable 597 } 598 599 return nil 600 }