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 }