github.com/letsencrypt/boulder@v0.20251208.0/iana/ip.go (about) 1 package iana 2 3 import ( 4 "bytes" 5 "encoding/csv" 6 "errors" 7 "fmt" 8 "io" 9 "net/netip" 10 "regexp" 11 "slices" 12 "strings" 13 14 _ "embed" 15 ) 16 17 type reservedPrefix struct { 18 // addressFamily is "IPv4" or "IPv6". 19 addressFamily string 20 // The other fields are defined in: 21 // https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml 22 // https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml 23 addressBlock netip.Prefix 24 name string 25 rfc string 26 // The BRs' requirement that we not issue for Reserved IP Addresses only 27 // cares about presence in one of these registries, not any of the other 28 // metadata fields tracked by the registries. Therefore, we ignore the 29 // Allocation Date, Termination Date, Source, Destination, Forwardable, 30 // Globally Reachable, and Reserved By Protocol columns. 31 } 32 33 var ( 34 reservedPrefixes []reservedPrefix 35 36 // https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml 37 //go:embed data/iana-ipv4-special-registry-1.csv 38 ipv4Registry []byte 39 // https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml 40 //go:embed data/iana-ipv6-special-registry-1.csv 41 ipv6Registry []byte 42 ) 43 44 // init parses and loads the embedded IANA special-purpose address registry CSV 45 // files for all address families, panicking if any one fails. 46 func init() { 47 ipv4Prefixes, err := parseReservedPrefixFile(ipv4Registry, "IPv4") 48 if err != nil { 49 panic(err) 50 } 51 52 ipv6Prefixes, err := parseReservedPrefixFile(ipv6Registry, "IPv6") 53 if err != nil { 54 panic(err) 55 } 56 57 reservedPrefixes = slices.Concat(ipv4Prefixes, ipv6Prefixes) 58 59 // Sort the list of reserved prefixes in descending order of prefix size, so 60 // that checks will match the most-specific reserved prefix first. 61 slices.SortFunc(reservedPrefixes, func(a, b reservedPrefix) int { 62 if a.addressBlock.Bits() == b.addressBlock.Bits() { 63 return 0 64 } 65 if a.addressBlock.Bits() > b.addressBlock.Bits() { 66 return -1 67 } 68 return 1 69 }) 70 } 71 72 // Define regexps we'll use to clean up poorly formatted registry entries. 73 var ( 74 // 2+ sequential whitespace characters. The csv package takes care of 75 // newlines automatically. 76 ianaWhitespacesRE = regexp.MustCompile(`\s{2,}`) 77 // Footnotes at the end, like `[2]`. 78 ianaFootnotesRE = regexp.MustCompile(`\[\d+\]$`) 79 ) 80 81 // parseReservedPrefixFile parses and returns the IANA special-purpose address 82 // registry CSV data for a single address family, or returns an error if parsing 83 // fails. 84 func parseReservedPrefixFile(registryData []byte, addressFamily string) ([]reservedPrefix, error) { 85 if addressFamily != "IPv4" && addressFamily != "IPv6" { 86 return nil, fmt.Errorf("failed to parse reserved address registry: invalid address family %q", addressFamily) 87 } 88 if registryData == nil { 89 return nil, fmt.Errorf("failed to parse reserved %s address registry: empty", addressFamily) 90 } 91 92 reader := csv.NewReader(bytes.NewReader(registryData)) 93 94 // Parse the header row. 95 record, err := reader.Read() 96 if err != nil { 97 return nil, fmt.Errorf("failed to parse reserved %s address registry header: %w", addressFamily, err) 98 } 99 if record[0] != "Address Block" || record[1] != "Name" || record[2] != "RFC" { 100 return nil, fmt.Errorf("failed to parse reserved %s address registry header: must begin with \"Address Block\", \"Name\" and \"RFC\"", addressFamily) 101 } 102 103 // Parse the records. 104 var prefixes []reservedPrefix 105 for { 106 row, err := reader.Read() 107 if errors.Is(err, io.EOF) { 108 // Finished parsing the file. 109 if len(prefixes) < 1 { 110 return nil, fmt.Errorf("failed to parse reserved %s address registry: no rows after header", addressFamily) 111 } 112 break 113 } else if err != nil { 114 return nil, err 115 } else if len(row) < 3 { 116 return nil, fmt.Errorf("failed to parse reserved %s address registry: incomplete row", addressFamily) 117 } 118 119 // Remove any footnotes, then handle each comma-separated prefix. 120 for prefixStr := range strings.SplitSeq(ianaFootnotesRE.ReplaceAllLiteralString(row[0], ""), ",") { 121 prefix, err := netip.ParsePrefix(strings.TrimSpace(prefixStr)) 122 if err != nil { 123 return nil, fmt.Errorf("failed to parse reserved %s address registry: couldn't parse entry %q as an IP address prefix: %s", addressFamily, prefixStr, err) 124 } 125 126 prefixes = append(prefixes, reservedPrefix{ 127 addressFamily: addressFamily, 128 addressBlock: prefix, 129 name: row[1], 130 // Replace any whitespace sequences with a single space. 131 rfc: ianaWhitespacesRE.ReplaceAllLiteralString(row[2], " "), 132 }) 133 } 134 } 135 136 return prefixes, nil 137 } 138 139 // IsReservedAddr returns an error if an IP address is part of a reserved range. 140 func IsReservedAddr(ip netip.Addr) error { 141 // Strip zone from IPv6 addresses before checking 142 ip = ip.WithZone("") 143 for _, rpx := range reservedPrefixes { 144 if rpx.addressBlock.Contains(ip) { 145 return fmt.Errorf("IP address is in a reserved address block: %s: %s", rpx.rfc, rpx.name) 146 } 147 } 148 149 return nil 150 } 151 152 // IsReservedPrefix returns an error if an IP address prefix overlaps with a 153 // reserved range. 154 func IsReservedPrefix(prefix netip.Prefix) error { 155 for _, rpx := range reservedPrefixes { 156 if rpx.addressBlock.Overlaps(prefix) { 157 return fmt.Errorf("IP address is in a reserved address block: %s: %s", rpx.rfc, rpx.name) 158 } 159 } 160 161 return nil 162 }