using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net; using Amazon.Runtime; using ThirdParty.Json.LitJson; namespace Amazon.Util { /// /// This class can be used to discover the public address ranges for AWS. The /// information is retrieved from the publicly accessible /// https://ip-ranges.amazonaws.com/ip-ranges.json file. /// /// /// The information in this file is generated from our internal system-of-record and /// is authoritative. You can expect it to change several times per week and should /// poll accordingly. /// public class AWSPublicIpAddressRanges { // The known-in-use service keys; the actual keys can be extended // over time. public const string AmazonServiceKey = "AMAZON"; public const string EC2ServiceKey = "EC2"; public const string CloudFrontServiceKey = "CLOUDFRONT"; public const string Route53ServiceKey = "ROUTE53"; public const string Route53HealthChecksServiceKey = "ROUTE53_HEALTHCHECKS"; /// /// Region identifier string for ROUTE53 and CLOUDFRONT ranges /// public const string GlobalRegionIdentifier = "GLOBAL"; #region Json parse keys const string createDateKey = "createDate"; const string ipv4PrefixesKey = "prefixes"; const string ipv4PrefixKey = "ip_prefix"; const string ipv6PrefixesKey = "ipv6_prefixes"; const string ipv6PrefixKey = "ipv6_prefix"; const string regionKey = "region"; const string serviceKey = "service"; private const string networkBorderGroupKey = "network_border_group"; const string createDateFormatString = "yyyy-MM-dd-HH-mm-ss"; #endregion /// /// Collection of service keys found in the data file. /// public IEnumerable ServiceKeys { get { var keyHash = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var range in AllAddressRanges) { keyHash.Add(range.Service); } return keyHash; } } /// /// The publication date and time of the file that was downloaded and parsed. /// public DateTime CreateDate { get; private set; } /// /// Collection of all public IP ranges. /// public IEnumerable AllAddressRanges { get; private set; } /// /// Filtered collection of public IP ranges for the given service key /// public IEnumerable AddressRangesByServiceKey(string serviceKey) { if (!AllAddressRanges.Any()) throw new InvalidOperationException("No address range data has been loaded and parsed."); return AllAddressRanges.Where(ar => ar.Service.Equals(serviceKey, StringComparison.OrdinalIgnoreCase)); } /// /// Filtered collection of public IP ranges for the given region (us-east-1 etc) or GLOBAL. /// public IEnumerable AddressRangesByRegion(string regionIdentifier) { if (!AllAddressRanges.Any()) throw new InvalidOperationException("No address range data has been loaded and parsed."); return AllAddressRanges.Where(ar => ar.Region.Equals(regionIdentifier, StringComparison.OrdinalIgnoreCase)); } /// /// Filtered collection of public IP ranges for the given network border group. /// public IEnumerable AddressRangesByNetworkBorderGroup(string networkBorderGroup) { if (!AllAddressRanges.Any()) throw new InvalidOperationException("No address range data has been loaded and parsed."); return AllAddressRanges.Where(ar => ar.NetworkBorderGroup.Equals(networkBorderGroup, StringComparison.OrdinalIgnoreCase)); } /// /// Downloads the most recent copy of the endpoint address file and /// parses it to a collection of address range objects. /// public static AWSPublicIpAddressRanges Load() { return Load(null); } /// /// Downloads the most recent copy of the endpoint address file and /// parses it to a collection of address range objects. /// public static AWSPublicIpAddressRanges Load(IWebProxy proxy) { const int maxDownloadRetries = 3; var retries = 0; while (retries < maxDownloadRetries) { try { var content = AWSSDKUtils.DownloadStringContent(IpAddressRangeEndpoint, proxy); AWSPublicIpAddressRanges instance = Parse(content); return instance; } catch (Exception e) { retries++; if (retries == maxDownloadRetries) throw new AmazonServiceException( string.Format(CultureInfo.InvariantCulture, "Error downloading public IP address ranges file from {0}.", IpAddressRangeEndpoint), e); } var delay = (int) (Math.Pow(4, retries) * 100); delay = Math.Min(delay, 30 * 1000); AWSSDKUtils.Sleep(delay); } return null; } private static AWSPublicIpAddressRanges Parse(string fileContent) { try { var instance = new AWSPublicIpAddressRanges(); var json = JsonMapper.ToObject(new JsonReader(fileContent)); DateTime? creationDateTime = null; try { var createdAt = (string) json[createDateKey]; creationDateTime = DateTime.ParseExact(createdAt, createDateFormatString, null); } catch (FormatException) { } catch (ArgumentNullException) { } #pragma warning disable CS0612 // Type or member is obsolete instance.CreateDate = creationDateTime.GetValueOrDefault(AWSSDKUtils.CorrectedUtcNow); #pragma warning restore CS0612 // Type or member is obsolete // ipv4 and v6 addresses occupy different keys in the data file and can't easily be merged // so process each subset separately var parsedRanges = new List(); var ipv4Ranges = json[ipv4PrefixesKey]; var ipv6Ranges = json[ipv6PrefixesKey]; if (!ipv4Ranges.IsArray || !ipv6Ranges.IsArray) throw new InvalidDataException("Expected array content for ip_prefixes and/or ipv6_prefixes keys."); parsedRanges.AddRange(ParseRange(ipv4Ranges, AWSPublicIpAddressRange.AddressFormat.Ipv4)); parsedRanges.AddRange(ParseRange(ipv6Ranges, AWSPublicIpAddressRange.AddressFormat.Ipv6)); instance.AllAddressRanges = parsedRanges; return instance; } catch (Exception e) { throw new InvalidDataException("IP address ranges content in unexpected/invalid format.", e); } } private static IEnumerable ParseRange(JsonData ranges, AWSPublicIpAddressRange.AddressFormat addressFormat) { var prefixKey = addressFormat == AWSPublicIpAddressRange.AddressFormat.Ipv4 ? ipv4PrefixKey : ipv6PrefixKey; var parsedRanges = new List(); parsedRanges.AddRange(from JsonData range in ranges select new AWSPublicIpAddressRange { IpAddressFormat = addressFormat, IpPrefix = (string) range[prefixKey], Region = (string) range[regionKey], Service = (string) range[serviceKey], NetworkBorderGroup = (string) range[networkBorderGroupKey] }); return parsedRanges; } private AWSPublicIpAddressRanges() { } private static readonly Uri IpAddressRangeEndpoint = new Uri("https://ip-ranges.amazonaws.com/ip-ranges.json"); } /// /// Represents the IP address range for a single region and service key. /// public class AWSPublicIpAddressRange { /// /// The public IPv4 or Ipv6 address range, in CIDR notation. /// public string IpPrefix { get; internal set; } public enum AddressFormat { Ipv4, Ipv6 } /// /// Indicates ipv4 or v6 format of the address /// public AddressFormat IpAddressFormat { get; internal set; } /// /// The AWS region or GLOBAL for edge locations. Note that the CLOUDFRONT and ROUTE53 /// ranges are GLOBAL. /// public string Region { get; internal set; } /// /// The subset of IP address ranges. Specify AMAZON to get all IP address ranges /// (for example, the ranges in the EC2 subset are also in the AMAZON subset). Note /// that some IP address ranges are only in the AMAZON subset. /// /// /// Valid values for the service key include "AMAZON", "EC2", "ROUTE53", /// "ROUTE53_HEALTHCHECKS", and "CLOUDFRONT." If you need to know all of /// the ranges and don't care about the service, use the "AMAZON" entries. /// The other entries are subsets of this one. Also, some of the services, /// such as S3, are represented in "AMAZON" and do not have an entry that /// is specific to the service. We plan to add additional values over time; /// code accordingly! /// public string Service { get; internal set; } /// /// The network border group the IP address range belongs to. /// /// /// AWS Local Zones allow you to seamlessly connect to the full range of services in the AWS Region such /// as Amazon Simple Storage Service and Amazon DynamoDB through the same APIs and tool sets. You can extend /// your VPC Region by creating a new subnet that has a Local Zone assignment. When you create a subnet in /// a Local Zone, the VPC is also extended to that Local Zone. /// /// A network border group is a unique set of Availability Zones or Local Zones from where AWS advertises /// public IP addresses. /// /// When you create a VPC that has IPv6 addresses, you can choose to assign a set of Amazon-provided public /// IP addresses to the VPC and also set a network border group for the addresses that limits the addresses /// to the group. When you set a network border group, the IP addresses cannot move between network /// border groups. The us-west-2 network border group contains the four US West (Oregon) Availability Zones. /// The us-west-2-lax-1 network border group contains the Los Angeles Local Zones. /// public string NetworkBorderGroup { get; internal set; } } }