github.com/datadog/cilium@v1.6.12/daemon/ipam.go (about)

     1  // Copyright 2016-2020 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  	"fmt"
    19  	"net"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/cilium/cilium/api/v1/models"
    24  	ipamapi "github.com/cilium/cilium/api/v1/server/restapi/ipam"
    25  	"github.com/cilium/cilium/pkg/api"
    26  	"github.com/cilium/cilium/pkg/cidr"
    27  	"github.com/cilium/cilium/pkg/datapath"
    28  	"github.com/cilium/cilium/pkg/defaults"
    29  	"github.com/cilium/cilium/pkg/endpointmanager"
    30  	"github.com/cilium/cilium/pkg/ipam"
    31  	"github.com/cilium/cilium/pkg/logging/logfields"
    32  	"github.com/cilium/cilium/pkg/node"
    33  	"github.com/cilium/cilium/pkg/option"
    34  
    35  	"github.com/go-openapi/runtime/middleware"
    36  	"github.com/go-openapi/swag"
    37  )
    38  
    39  type postIPAM struct {
    40  	daemon *Daemon
    41  }
    42  
    43  // NewPostIPAMHandler creates a new postIPAM from the daemon.
    44  func NewPostIPAMHandler(d *Daemon) ipamapi.PostIPAMHandler {
    45  	return &postIPAM{daemon: d}
    46  }
    47  
    48  // Handle incoming requests address allocation requests for the daemon.
    49  func (h *postIPAM) Handle(params ipamapi.PostIPAMParams) middleware.Responder {
    50  	family := strings.ToLower(swag.StringValue(params.Family))
    51  	owner := swag.StringValue(params.Owner)
    52  	var expirationTimeout time.Duration
    53  	if swag.BoolValue(params.Expiration) {
    54  		expirationTimeout = defaults.IPAMExpiration
    55  	}
    56  	ipv4Result, ipv6Result, err := h.daemon.ipam.AllocateNextWithExpiration(family, owner, expirationTimeout)
    57  	if err != nil {
    58  		return api.Error(ipamapi.PostIPAMFailureCode, err)
    59  	}
    60  
    61  	resp := &models.IPAMResponse{
    62  		HostAddressing: node.GetNodeAddressing(),
    63  		Address:        &models.AddressPair{},
    64  	}
    65  
    66  	if ipv4Result != nil {
    67  		resp.Address.IPV4 = ipv4Result.IP.String()
    68  		resp.IPV4 = &models.IPAMAddressResponse{
    69  			Cidrs:          ipv4Result.CIDRs,
    70  			IP:             ipv4Result.IP.String(),
    71  			MasterMac:      ipv4Result.Master,
    72  			Gateway:        ipv4Result.GatewayIP,
    73  			ExpirationUUID: ipv4Result.ExpirationUUID,
    74  		}
    75  	}
    76  
    77  	if ipv6Result != nil {
    78  		resp.Address.IPV6 = ipv6Result.IP.String()
    79  		resp.IPV6 = &models.IPAMAddressResponse{
    80  			Cidrs:          ipv6Result.CIDRs,
    81  			IP:             ipv6Result.IP.String(),
    82  			MasterMac:      ipv6Result.Master,
    83  			Gateway:        ipv6Result.GatewayIP,
    84  			ExpirationUUID: ipv6Result.ExpirationUUID,
    85  		}
    86  	}
    87  
    88  	return ipamapi.NewPostIPAMCreated().WithPayload(resp)
    89  }
    90  
    91  type postIPAMIP struct {
    92  	daemon *Daemon
    93  }
    94  
    95  // NewPostIPAMIPHandler creates a new postIPAM from the daemon.
    96  func NewPostIPAMIPHandler(d *Daemon) ipamapi.PostIPAMIPHandler {
    97  	return &postIPAMIP{
    98  		daemon: d,
    99  	}
   100  }
   101  
   102  // Handle incoming requests address allocation requests for the daemon.
   103  func (h *postIPAMIP) Handle(params ipamapi.PostIPAMIPParams) middleware.Responder {
   104  	owner := swag.StringValue(params.Owner)
   105  	if err := h.daemon.ipam.AllocateIPString(params.IP, owner); err != nil {
   106  		return api.Error(ipamapi.PostIPAMIPFailureCode, err)
   107  	}
   108  
   109  	return ipamapi.NewPostIPAMIPOK()
   110  }
   111  
   112  type deleteIPAMIP struct {
   113  	daemon *Daemon
   114  }
   115  
   116  // NewDeleteIPAMIPHandler handle incoming requests to delete addresses.
   117  func NewDeleteIPAMIPHandler(d *Daemon) ipamapi.DeleteIPAMIPHandler {
   118  	return &deleteIPAMIP{daemon: d}
   119  }
   120  
   121  func (h *deleteIPAMIP) Handle(params ipamapi.DeleteIPAMIPParams) middleware.Responder {
   122  	// Release of an IP that is in use is not allowed
   123  	if ep := endpointmanager.LookupIPv4(params.IP); ep != nil {
   124  		return api.Error(ipamapi.DeleteIPAMIPFailureCode, fmt.Errorf("IP is in use by endpoint %d", ep.ID))
   125  	}
   126  	if ep := endpointmanager.LookupIPv6(params.IP); ep != nil {
   127  		return api.Error(ipamapi.DeleteIPAMIPFailureCode, fmt.Errorf("IP is in use by endpoint %d", ep.ID))
   128  	}
   129  
   130  	if err := h.daemon.ipam.ReleaseIPString(params.IP); err != nil {
   131  		return api.Error(ipamapi.DeleteIPAMIPFailureCode, err)
   132  	}
   133  
   134  	return ipamapi.NewDeleteIPAMIPOK()
   135  }
   136  
   137  // DumpIPAM dumps in the form of a map, the list of
   138  // reserved IPv4 and IPv6 addresses.
   139  func (d *Daemon) DumpIPAM() *models.IPAMStatus {
   140  	allocv4, allocv6, st := d.ipam.Dump()
   141  	status := &models.IPAMStatus{
   142  		Status: st,
   143  	}
   144  
   145  	v4 := []string{}
   146  	for ip := range allocv4 {
   147  		v4 = append(v4, ip)
   148  	}
   149  
   150  	v6 := []string{}
   151  	if allocv4 == nil {
   152  		allocv4 = map[string]string{}
   153  	}
   154  	for ip, owner := range allocv6 {
   155  		v6 = append(v6, ip)
   156  		// merge allocv6 into allocv4
   157  		allocv4[ip] = owner
   158  	}
   159  
   160  	if option.Config.EnableIPv4 {
   161  		status.IPV4 = v4
   162  	}
   163  
   164  	if option.Config.EnableIPv6 {
   165  		status.IPV6 = v6
   166  	}
   167  
   168  	status.Allocations = allocv4
   169  
   170  	return status
   171  }
   172  
   173  func (d *Daemon) allocateDatapathIPs(family datapath.NodeAddressingFamily) (routerIP net.IP, err error) {
   174  	// Blacklist allocation of the external IP
   175  	d.ipam.BlacklistIP(family.PrimaryExternal(), "node-ip")
   176  
   177  	// (Re-)allocate the router IP. If not possible, allocate a fresh IP.
   178  	// In that case, removal and re-creation of the cilium_host is
   179  	// required. It will also cause disruption of networking until all
   180  	// endpoints have been regenerated.
   181  	routerIP = family.Router()
   182  	if routerIP != nil {
   183  		err = d.ipam.AllocateIP(routerIP, "router")
   184  		if err != nil {
   185  			log.Warningf("Router IP could not be re-allocated. Need to re-allocate. This will cause brief network disruption")
   186  
   187  			// The restored router IP is not part of the allocation range.
   188  			// This indicates that the allocation range has changed.
   189  			if !option.Config.IsFlannelMasterDeviceSet() {
   190  				deleteHostDevice()
   191  			}
   192  
   193  			// force re-allocation of the router IP
   194  			routerIP = nil
   195  		}
   196  	}
   197  
   198  	if routerIP == nil {
   199  		var result *ipam.AllocationResult
   200  		result, err = d.ipam.AllocateNextFamily(ipam.DeriveFamily(family.PrimaryExternal()), "router")
   201  		if err != nil {
   202  			err = fmt.Errorf("Unable to allocate IPv4 router IP: %s", err)
   203  			return
   204  		}
   205  		routerIP = result.IP
   206  	}
   207  
   208  	return
   209  }
   210  
   211  func (d *Daemon) allocateHealthIPs() error {
   212  	bootstrapStats.healthCheck.Start()
   213  	if option.Config.EnableHealthChecking && option.Config.EnableEndpointHealthChecking {
   214  		if option.Config.EnableIPv4 {
   215  			result, err := d.ipam.AllocateNextFamily(ipam.IPv4, "health")
   216  			if err != nil {
   217  				return fmt.Errorf("unable to allocate health IPs: %s,see https://cilium.link/ipam-range-full", err)
   218  			}
   219  
   220  			d.nodeDiscovery.LocalNode.IPv4HealthIP = result.IP
   221  			log.Debugf("IPv4 health endpoint address: %s", result.IP)
   222  		}
   223  
   224  		if option.Config.EnableIPv6 {
   225  			result, err := d.ipam.AllocateNextFamily(ipam.IPv6, "health")
   226  			if err != nil {
   227  				if d.nodeDiscovery.LocalNode.IPv4HealthIP != nil {
   228  					d.ipam.ReleaseIP(d.nodeDiscovery.LocalNode.IPv4HealthIP)
   229  				}
   230  				return fmt.Errorf("unable to allocate health IPs: %s,see https://cilium.link/ipam-range-full", err)
   231  			}
   232  
   233  			d.nodeDiscovery.LocalNode.IPv6HealthIP = result.IP
   234  			log.Debugf("IPv6 health endpoint address: %s", result.IP)
   235  		}
   236  	}
   237  	bootstrapStats.healthCheck.End(true)
   238  	return nil
   239  }
   240  
   241  func (d *Daemon) allocateIPs() error {
   242  	bootstrapStats.ipam.Start()
   243  	if option.Config.EnableIPv4 {
   244  		routerIP, err := d.allocateDatapathIPs(d.datapath.LocalNodeAddressing().IPv4())
   245  		if err != nil {
   246  			return err
   247  		}
   248  		if routerIP != nil {
   249  			node.SetInternalIPv4(routerIP)
   250  		}
   251  	}
   252  
   253  	if option.Config.EnableIPv6 {
   254  		routerIP, err := d.allocateDatapathIPs(d.datapath.LocalNodeAddressing().IPv6())
   255  		if err != nil {
   256  			return err
   257  		}
   258  		if routerIP != nil {
   259  			node.SetIPv6Router(routerIP)
   260  		}
   261  	}
   262  
   263  	log.Info("Addressing information:")
   264  	log.Infof("  Cluster-Name: %s", option.Config.ClusterName)
   265  	log.Infof("  Cluster-ID: %d", option.Config.ClusterID)
   266  	log.Infof("  Local node-name: %s", node.GetName())
   267  	log.Infof("  Node-IPv6: %s", node.GetIPv6())
   268  
   269  	if option.Config.EnableIPv6 {
   270  		log.Infof("  IPv6 node prefix: %s", node.GetIPv6NodeRange())
   271  		log.Infof("  IPv6 allocation prefix: %s", node.GetIPv6AllocRange())
   272  		log.Infof("  IPv6 router address: %s", node.GetIPv6Router())
   273  
   274  		if addrs, err := d.datapath.LocalNodeAddressing().IPv6().LocalAddresses(); err != nil {
   275  			log.WithError(err).Fatal("Unable to list local IPv6 addresses")
   276  		} else {
   277  			log.Info("  Local IPv6 addresses:")
   278  			for _, ip := range addrs {
   279  				log.Infof("  - %s", ip)
   280  			}
   281  		}
   282  	}
   283  
   284  	log.Infof("  External-Node IPv4: %s", node.GetExternalIPv4())
   285  	log.Infof("  Internal-Node IPv4: %s", node.GetInternalIPv4())
   286  
   287  	if option.Config.EnableIPv4 {
   288  		log.Infof("  Cluster IPv4 prefix: %s", node.GetIPv4ClusterRange())
   289  		log.Infof("  IPv4 allocation prefix: %s", node.GetIPv4AllocRange())
   290  
   291  		if c := option.Config.IPv4NativeRoutingCIDR(); c != nil {
   292  			log.Infof("  IPv4 native routing prefix: %s", c.String())
   293  		}
   294  
   295  		// Allocate IPv4 service loopback IP
   296  		loopbackIPv4 := net.ParseIP(option.Config.LoopbackIPv4)
   297  		if loopbackIPv4 == nil {
   298  			return fmt.Errorf("Invalid IPv4 loopback address %s", option.Config.LoopbackIPv4)
   299  		}
   300  		node.SetIPv4Loopback(loopbackIPv4)
   301  		log.Infof("  Loopback IPv4: %s", node.GetIPv4Loopback().String())
   302  
   303  		if addrs, err := d.datapath.LocalNodeAddressing().IPv4().LocalAddresses(); err != nil {
   304  			log.WithError(err).Fatal("Unable to list local IPv4 addresses")
   305  		} else {
   306  			log.Info("  Local IPv4 addresses:")
   307  			for _, ip := range addrs {
   308  				log.Infof("  - %s", ip)
   309  			}
   310  		}
   311  	}
   312  
   313  	bootstrapStats.ipam.End(true)
   314  	return d.allocateHealthIPs()
   315  }
   316  
   317  func (d *Daemon) bootstrapIPAM() {
   318  	// If the device has been specified, the IPv4AllocPrefix and the
   319  	// IPv6AllocPrefix were already allocated before the k8s.Init().
   320  	//
   321  	// If the device hasn't been specified, k8s.Init() allocated the
   322  	// IPv4AllocPrefix and the IPv6AllocPrefix from k8s node annotations.
   323  	//
   324  	// If k8s.Init() failed to retrieve the IPv4AllocPrefix we can try to derive
   325  	// it from an existing node_config.h file or from previous cilium_host
   326  	// interfaces.
   327  	//
   328  	// Then, we will calculate the IPv4 or IPv6 alloc prefix based on the IPv6
   329  	// or IPv4 alloc prefix, respectively, retrieved by k8s node annotations.
   330  	bootstrapStats.ipam.Start()
   331  	log.Info("Initializing node addressing")
   332  
   333  	node.SetIPv4ClusterCidrMaskSize(option.Config.IPv4ClusterCIDRMaskSize)
   334  
   335  	if option.Config.IPv4Range != AutoCIDR {
   336  		allocCIDR, err := cidr.ParseCIDR(option.Config.IPv4Range)
   337  		if err != nil {
   338  			log.WithError(err).WithField(logfields.V4Prefix, option.Config.IPv4Range).Fatal("Invalid IPv4 allocation prefix")
   339  		}
   340  		node.SetIPv4AllocRange(allocCIDR)
   341  	}
   342  
   343  	if option.Config.IPv6Range != AutoCIDR {
   344  		_, net, err := net.ParseCIDR(option.Config.IPv6Range)
   345  		if err != nil {
   346  			log.WithError(err).WithField(logfields.V6Prefix, option.Config.IPv6Range).Fatal("Invalid IPv6 allocation prefix")
   347  		}
   348  
   349  		if err := node.SetIPv6NodeRange(net); err != nil {
   350  			log.WithError(err).WithField(logfields.V6Prefix, net).Fatal("Invalid per node IPv6 allocation prefix")
   351  		}
   352  	}
   353  
   354  	if err := node.AutoComplete(); err != nil {
   355  		log.WithError(err).Fatal("Cannot autocomplete node addresses")
   356  	}
   357  
   358  	// Set up ipam conf after init() because we might be running d.conf.KVStoreIPv4Registration
   359  	d.ipam = ipam.NewIPAM(d.datapath.LocalNodeAddressing(), ipam.Configuration{
   360  		EnableIPv4: option.Config.EnableIPv4,
   361  		EnableIPv6: option.Config.EnableIPv6,
   362  	}, d)
   363  	bootstrapStats.ipam.End(true)
   364  }