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"; 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)).ToList(); } /// /// 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)).ToList(); } /// /// 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); Util.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] }); 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; } } }