github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/network/subnet.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package network
     5  
     6  import (
     7  	"math/big"
     8  	"net"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/juju/collections/set"
    13  	"github.com/juju/errors"
    14  
    15  	"github.com/juju/juju/core/life"
    16  )
    17  
    18  // FanCIDRs describes the subnets relevant to a fan network.
    19  type FanCIDRs struct {
    20  	// FanLocalUnderlay is the CIDR of the local underlying fan network.
    21  	// It allows easy identification of the device the fan is running on.
    22  	FanLocalUnderlay string
    23  
    24  	// FanOverlay is the CIDR of the complete fan setup.
    25  	FanOverlay string
    26  }
    27  
    28  func newFanCIDRs(overlay, underlay string) *FanCIDRs {
    29  	return &FanCIDRs{
    30  		FanLocalUnderlay: underlay,
    31  		FanOverlay:       overlay,
    32  	}
    33  }
    34  
    35  // SubnetInfo is a source-agnostic representation of a subnet.
    36  // It may originate from state, or from a provider.
    37  type SubnetInfo struct {
    38  	// ID is the unique ID of the subnet.
    39  	ID Id
    40  
    41  	// CIDR of the network, in 123.45.67.89/24 format.
    42  	CIDR string
    43  
    44  	// Memoized value for the parsed network for the above CIDR.
    45  	parsedCIDRNetwork *net.IPNet
    46  
    47  	// ProviderId is a provider-specific subnet ID.
    48  	ProviderId Id
    49  
    50  	// ProviderSpaceId holds the provider ID of the space associated
    51  	// with this subnet. Can be empty if not supported.
    52  	ProviderSpaceId Id
    53  
    54  	// ProviderNetworkId holds the provider ID of the network
    55  	// containing this subnet, for example VPC id for EC2.
    56  	ProviderNetworkId Id
    57  
    58  	// VLANTag needs to be between 1 and 4094 for VLANs and 0 for
    59  	// normal networks. It's defined by IEEE 802.1Q standard, and used
    60  	// to define a VLAN network. For more information, see:
    61  	// http://en.wikipedia.org/wiki/IEEE_802.1Q.
    62  	VLANTag int
    63  
    64  	// AvailabilityZones describes which availability zones this
    65  	// subnet is in. It can be empty if the provider does not support
    66  	// availability zones.
    67  	AvailabilityZones []string
    68  
    69  	// SpaceID is the id of the space the subnet is associated with.
    70  	// Default value should be AlphaSpaceId. It can be empty if
    71  	// the subnet is returned from an networkingEnviron. SpaceID is
    72  	// preferred over SpaceName in state and non networkingEnviron use.
    73  	SpaceID string
    74  
    75  	// SpaceName is the name of the space the subnet is associated with.
    76  	// An empty string indicates it is part of the AlphaSpaceName OR
    77  	// if the SpaceID is set. Should primarily be used in an networkingEnviron.
    78  	SpaceName string
    79  
    80  	// FanInfo describes the fan networking setup for the subnet.
    81  	// It may be empty if this is not a fan subnet,
    82  	// or if this subnet information comes from a provider.
    83  	FanInfo *FanCIDRs
    84  
    85  	// IsPublic describes whether a subnet is public or not.
    86  	IsPublic bool
    87  
    88  	// Life represents the current life-cycle status of the subnets.
    89  	Life life.Value
    90  }
    91  
    92  // SetFan sets the fan networking information for the subnet.
    93  func (s *SubnetInfo) SetFan(underlay, overlay string) {
    94  	s.FanInfo = newFanCIDRs(overlay, underlay)
    95  }
    96  
    97  // FanLocalUnderlay returns the fan underlay CIDR if known.
    98  func (s *SubnetInfo) FanLocalUnderlay() string {
    99  	if s.FanInfo == nil {
   100  		return ""
   101  	}
   102  	return s.FanInfo.FanLocalUnderlay
   103  }
   104  
   105  // FanOverlay returns the fan overlay CIDR if known.
   106  func (s *SubnetInfo) FanOverlay() string {
   107  	if s.FanInfo == nil {
   108  		return ""
   109  	}
   110  	return s.FanInfo.FanOverlay
   111  }
   112  
   113  // Validate validates the subnet, checking the CIDR, and VLANTag, if present.
   114  func (s *SubnetInfo) Validate() error {
   115  	if s.CIDR == "" {
   116  		return errors.Errorf("missing CIDR")
   117  	} else if _, err := s.ParsedCIDRNetwork(); err != nil {
   118  		return errors.Trace(err)
   119  	}
   120  
   121  	if s.VLANTag < 0 || s.VLANTag > 4094 {
   122  		return errors.Errorf("invalid VLAN tag %d: must be between 0 and 4094", s.VLANTag)
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  // ParsedCIDRNetwork returns the network represented by the CIDR field.
   129  func (s *SubnetInfo) ParsedCIDRNetwork() (*net.IPNet, error) {
   130  	// Memoize the CIDR the first time this method is called or if the
   131  	// CIDR field has changed.
   132  	if s.parsedCIDRNetwork == nil || s.parsedCIDRNetwork.String() != s.CIDR {
   133  		_, ipNet, err := net.ParseCIDR(s.CIDR)
   134  		if err != nil {
   135  			return nil, err
   136  		}
   137  
   138  		s.parsedCIDRNetwork = ipNet
   139  	}
   140  	return s.parsedCIDRNetwork, nil
   141  }
   142  
   143  // SubnetInfos is a collection of subnets.
   144  type SubnetInfos []SubnetInfo
   145  
   146  // SpaceIDs returns the set of space IDs that these subnets are in.
   147  func (s SubnetInfos) SpaceIDs() set.Strings {
   148  	spaceIDs := set.NewStrings()
   149  	for _, sub := range s {
   150  		spaceIDs.Add(sub.SpaceID)
   151  	}
   152  	return spaceIDs
   153  }
   154  
   155  // GetByUnderlayCIDR returns any subnets in this collection that are fan
   156  // overlays for the input CIDR.
   157  // An error is returned if the input is not a valid CIDR.
   158  // TODO (manadart 2020-04-15): Consider storing subnet IDs in FanInfo,
   159  // so we can ensure uniqueness in multi-network deployments.
   160  func (s SubnetInfos) GetByUnderlayCIDR(cidr string) (SubnetInfos, error) {
   161  	if !IsValidCIDR(cidr) {
   162  		return nil, errors.NotValidf("CIDR %q", cidr)
   163  	}
   164  
   165  	var overlays SubnetInfos
   166  	for _, sub := range s {
   167  		if sub.FanLocalUnderlay() == cidr {
   168  			overlays = append(overlays, sub)
   169  		}
   170  	}
   171  	return overlays, nil
   172  }
   173  
   174  // ContainsID returns true if the collection contains a
   175  // space with the given ID.
   176  func (s SubnetInfos) ContainsID(id Id) bool {
   177  	return s.GetByID(id) != nil
   178  }
   179  
   180  // GetByID returns a reference to the subnet with the input ID if one is found.
   181  func (s SubnetInfos) GetByID(id Id) *SubnetInfo {
   182  	for _, sub := range s {
   183  		if sub.ID == id {
   184  			return &sub
   185  		}
   186  	}
   187  	return nil
   188  }
   189  
   190  // GetByCIDR returns all subnets in the collection
   191  // with a CIDR matching the input.
   192  func (s SubnetInfos) GetByCIDR(cidr string) (SubnetInfos, error) {
   193  	if !IsValidCIDR(cidr) {
   194  		return nil, errors.NotValidf("CIDR %q", cidr)
   195  	}
   196  
   197  	var matching SubnetInfos
   198  	for _, sub := range s {
   199  		if sub.CIDR == cidr {
   200  			matching = append(matching, sub)
   201  		}
   202  	}
   203  
   204  	if len(matching) != 0 {
   205  		return matching, nil
   206  	}
   207  
   208  	// Some providers (e.g. equinix) carve subnets into smaller CIDRs and
   209  	// assign addresses from the carved subnets to the machines. If we were
   210  	// not able to find a direct CIDR match fallback to a CIDR is sub-CIDR
   211  	// of check.
   212  	firstIP, lastIP, err := IPRangeForCIDR(cidr)
   213  	if err != nil {
   214  		return nil, errors.Annotatef(err, "unable to extract first and last IP addresses from CIDR %q", cidr)
   215  	}
   216  
   217  	for _, sub := range s {
   218  		subNet, err := sub.ParsedCIDRNetwork()
   219  		if err != nil { // this should not happen; but let's be paranoid.
   220  			logger.Warningf("unable to parse CIDR %q for subnet %q", sub.CIDR, sub.ID)
   221  			continue
   222  		}
   223  
   224  		if subNet.Contains(firstIP) && subNet.Contains(lastIP) {
   225  			matching = append(matching, sub)
   226  		}
   227  	}
   228  
   229  	return matching, nil
   230  }
   231  
   232  // GetByAddress returns subnets that based on IP range,
   233  // include the input IP address.
   234  func (s SubnetInfos) GetByAddress(addr string) (SubnetInfos, error) {
   235  	ip := net.ParseIP(addr)
   236  	if ip == nil {
   237  		return nil, errors.NotValidf("%q as IP address", addr)
   238  	}
   239  
   240  	var subs SubnetInfos
   241  	for _, sub := range s {
   242  		ipNet, err := sub.ParsedCIDRNetwork()
   243  		if err != nil {
   244  			return nil, errors.Trace(err)
   245  		}
   246  		if ipNet.Contains(ip) {
   247  			subs = append(subs, sub)
   248  		}
   249  	}
   250  	return subs, nil
   251  }
   252  
   253  // GetBySpaceID returns all subnets with the input space ID,
   254  // including those inferred by being overlays of subnets in the space.
   255  func (s SubnetInfos) GetBySpaceID(spaceID string) (SubnetInfos, error) {
   256  	var subsInSpace SubnetInfos
   257  	for _, sub := range s {
   258  		if sub.SpaceID == spaceID {
   259  			subsInSpace = append(subsInSpace, sub)
   260  		}
   261  	}
   262  
   263  	var spaceOverlays SubnetInfos
   264  	for _, sub := range subsInSpace {
   265  		// If we picked up an overlay because the space was already set,
   266  		// don't try to find subnets for which it is an underlay.
   267  		if sub.FanInfo != nil {
   268  			continue
   269  		}
   270  
   271  		// TODO (manadart 2020-05-13): See comment for GetByUnderlayCIDR.
   272  		// This will only be correct for unique CIDRs.
   273  		overlays, err := s.GetByUnderlayCIDR(sub.CIDR)
   274  		if err != nil {
   275  			return nil, errors.Trace(err)
   276  		}
   277  
   278  		// Don't include overlays that already have a space ID.
   279  		// They will have been retrieved as subsInSpace.
   280  		for _, overlay := range overlays {
   281  			if overlay.SpaceID == "" {
   282  				overlay.SpaceID = spaceID
   283  				spaceOverlays = append(spaceOverlays, overlay)
   284  			}
   285  		}
   286  	}
   287  
   288  	return append(subsInSpace, spaceOverlays...), nil
   289  }
   290  
   291  // AllSubnetInfos implements SubnetLookup
   292  // by returning all of the subnets.
   293  func (s SubnetInfos) AllSubnetInfos() (SubnetInfos, error) {
   294  	return s, nil
   295  }
   296  
   297  // EqualTo returns true if this slice of SubnetInfo is equal to the input.
   298  func (s SubnetInfos) EqualTo(other SubnetInfos) bool {
   299  	if len(s) != len(other) {
   300  		return false
   301  	}
   302  
   303  	SortSubnetInfos(s)
   304  	SortSubnetInfos(other)
   305  	for i := 0; i < len(s); i++ {
   306  		if s[i].ID != other[i].ID {
   307  			return false
   308  		}
   309  	}
   310  
   311  	return true
   312  }
   313  
   314  func (s SubnetInfos) Len() int      { return len(s) }
   315  func (s SubnetInfos) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   316  func (s SubnetInfos) Less(i, j int) bool {
   317  	return s[i].ID < s[j].ID
   318  }
   319  
   320  // SortSubnetInfos sorts subnets by ID.
   321  func SortSubnetInfos(s SubnetInfos) {
   322  	sort.Sort(s)
   323  }
   324  
   325  // IsValidCIDR returns whether cidr is a valid subnet CIDR.
   326  func IsValidCIDR(cidr string) bool {
   327  	_, ipNet, err := net.ParseCIDR(cidr)
   328  	if err == nil && ipNet.String() == cidr {
   329  		return true
   330  	}
   331  	return false
   332  }
   333  
   334  // FindSubnetIDsForAvailabilityZone returns a series of subnet IDs from a series
   335  // of zones, if zones match the zoneName.
   336  //
   337  // Returns an error if no matching subnets match the zoneName.
   338  func FindSubnetIDsForAvailabilityZone(zoneName string, subnetsToZones map[Id][]string) ([]Id, error) {
   339  	matchingSubnetIDs := set.NewStrings()
   340  	for subnetID, zones := range subnetsToZones {
   341  		zonesSet := set.NewStrings(zones...)
   342  		if zonesSet.Size() == 0 && zoneName == "" || zonesSet.Contains(zoneName) {
   343  			matchingSubnetIDs.Add(string(subnetID))
   344  		}
   345  	}
   346  
   347  	if matchingSubnetIDs.IsEmpty() {
   348  		return nil, errors.NotFoundf("subnets in AZ %q", zoneName)
   349  	}
   350  
   351  	sorted := make([]Id, matchingSubnetIDs.Size())
   352  	for k, v := range matchingSubnetIDs.SortedValues() {
   353  		sorted[k] = Id(v)
   354  	}
   355  
   356  	return sorted, nil
   357  }
   358  
   359  // InFan describes a network fan type.
   360  const InFan = "INFAN"
   361  
   362  // FilterInFanNetwork filters out any fan networks.
   363  func FilterInFanNetwork(networks []Id) []Id {
   364  	var result []Id
   365  	for _, network := range networks {
   366  		if !IsInFanNetwork(network) {
   367  			result = append(result, network)
   368  		}
   369  	}
   370  	return result
   371  }
   372  
   373  func IsInFanNetwork(network Id) bool {
   374  	return strings.Contains(network.String(), InFan)
   375  }
   376  
   377  // IPRangeForCIDR returns the first and last addresses that correspond to the
   378  // provided CIDR. The first address will always be the network address. The
   379  // returned range also includes the broadcast address. For example, a CIDR of
   380  // 10.0.0.0/24 yields: [10.0.0.0, 10.0.0.255].
   381  func IPRangeForCIDR(cidr string) (net.IP, net.IP, error) {
   382  	_, ipNet, err := net.ParseCIDR(cidr)
   383  	if err != nil {
   384  		return net.IP{}, net.IP{}, errors.Trace(err)
   385  	}
   386  	ones, numBits := ipNet.Mask.Size()
   387  
   388  	// Special case: CIDR specifies a single address (i.e. a /32 or /128
   389  	// for IPV4 and IPV6 CIDRs accordingly).
   390  	if ones == numBits {
   391  		firstIP := ipNet.IP
   392  		lastIP := make(net.IP, len(firstIP))
   393  		copy(lastIP, firstIP)
   394  		return firstIP, lastIP, nil
   395  	}
   396  
   397  	// Calculate number of hosts in network (2^hostBits - 1) or the
   398  	// equivalent (1 << hostBits) - 1.
   399  	hostCount := big.NewInt(1)
   400  	hostCount = hostCount.Lsh(hostCount, uint(numBits-ones))
   401  	hostCount = hostCount.Sub(hostCount, big.NewInt(1))
   402  
   403  	// Calculate last IP in range.
   404  	lastIPNum := big.NewInt(0).SetBytes([]byte(ipNet.IP))
   405  	lastIPNum = lastIPNum.Add(lastIPNum, hostCount)
   406  
   407  	// Convert last IP into bytes. Since BigInt strips off leading zeroes
   408  	// we need to prepend them again before casting back to net.IP.
   409  	lastIPBytes := lastIPNum.Bytes()
   410  	lastIPBytes = append(make([]byte, len(ipNet.IP)-len(lastIPBytes)), lastIPBytes...)
   411  
   412  	return ipNet.IP, net.IP(lastIPBytes), nil
   413  }