at master 6.0 kB view raw
1{ 2 lib ? import ../., 3}: 4let 5 inherit (builtins) 6 map 7 match 8 genList 9 length 10 concatMap 11 head 12 toString 13 ; 14 15 inherit (lib) lists strings trivial; 16 17 inherit (lib.lists) last; 18 19 /** 20 IPv6 addresses are 128-bit identifiers. The preferred form is 'x:x:x:x:x:x:x:x', 21 where the 'x's are one to four hexadecimal digits of the eight 16-bit pieces of 22 the address. See RFC 4291. 23 */ 24 ipv6Bits = 128; 25 ipv6Pieces = 8; # 'x:x:x:x:x:x:x:x' 26 ipv6PieceBits = 16; # One piece in range from 0 to 0xffff. 27 ipv6PieceMaxValue = 65535; # 2^16 - 1 28in 29let 30 /** 31 Expand an IPv6 address by removing the "::" compression and padding them 32 with the necessary number of zeros. Converts an address from the string to 33 the list of strings which then can be parsed using `_parseExpanded`. 34 Throws an error when the address is malformed. 35 36 # Type: String -> [ String ] 37 38 # Example: 39 40 ```nix 41 expandIpv6 "2001:DB8::ffff" 42 => ["2001" "DB8" "0" "0" "0" "0" "0" "ffff"] 43 ``` 44 */ 45 expandIpv6 = 46 addr: 47 if match "^[0-9A-Fa-f:]+$" addr == null then 48 throw "${addr} contains malformed characters for IPv6 address" 49 else 50 let 51 pieces = strings.splitString ":" addr; 52 piecesNoEmpty = lists.remove "" pieces; 53 piecesNoEmptyLen = length piecesNoEmpty; 54 zeros = genList (_: "0") (ipv6Pieces - piecesNoEmptyLen); 55 hasPrefix = strings.hasPrefix "::" addr; 56 hasSuffix = strings.hasSuffix "::" addr; 57 hasInfix = strings.hasInfix "::" addr; 58 in 59 if addr == "::" then 60 zeros 61 else if 62 let 63 emptyCount = length pieces - piecesNoEmptyLen; 64 emptyExpected = 65 # splitString produces two empty pieces when "::" in the beginning 66 # or in the end, and only one when in the middle of an address. 67 if hasPrefix || hasSuffix then 68 2 69 else if hasInfix then 70 1 71 else 72 0; 73 in 74 emptyCount != emptyExpected 75 || (hasInfix && piecesNoEmptyLen >= ipv6Pieces) # "::" compresses at least one group of zeros. 76 || (!hasInfix && piecesNoEmptyLen != ipv6Pieces) 77 then 78 throw "${addr} is not a valid IPv6 address" 79 # Create a list of 8 elements, filling some of them with zeros depending 80 # on where the "::" was found. 81 else if hasPrefix then 82 zeros ++ piecesNoEmpty 83 else if hasSuffix then 84 piecesNoEmpty ++ zeros 85 else if hasInfix then 86 concatMap (piece: if piece == "" then zeros else [ piece ]) pieces 87 else 88 pieces; 89 90 /** 91 Parses an expanded IPv6 address (see `expandIpv6`), converting each part 92 from a string to an u16 integer. Returns an internal representation of IPv6 93 address (list of integers) that can be easily processed by other helper 94 functions. 95 Throws an error some element is not an u16 integer. 96 97 # Type: [ String ] -> IPv6 98 99 # Example: 100 101 ```nix 102 parseExpandedIpv6 ["2001" "DB8" "0" "0" "0" "0" "0" "ffff"] 103 => [8193 3512 0 0 0 0 0 65535] 104 ``` 105 */ 106 parseExpandedIpv6 = 107 addr: 108 assert lib.assertMsg ( 109 length addr == ipv6Pieces 110 ) "parseExpandedIpv6: expected list of integers with ${ipv6Pieces} elements"; 111 let 112 u16FromHexStr = 113 hex: 114 let 115 parsed = trivial.fromHexString hex; 116 in 117 if 0 <= parsed && parsed <= ipv6PieceMaxValue then 118 parsed 119 else 120 throw "0x${hex} is not a valid u16 integer"; 121 in 122 map (piece: u16FromHexStr piece) addr; 123in 124let 125 /** 126 Parses an IPv6 address from a string to the internal representation (list 127 of integers). 128 129 # Type: String -> IPv6 130 131 # Example: 132 133 ```nix 134 parseIpv6FromString "2001:DB8::ffff" 135 => [8193 3512 0 0 0 0 0 65535] 136 ``` 137 */ 138 parseIpv6FromString = addr: parseExpandedIpv6 (expandIpv6 addr); 139in 140{ 141 /** 142 Internally, an IPv6 address is stored as a list of 16-bit integers with 8 143 elements. Wherever you see `IPv6` in internal functions docs, it means that 144 it is a list of integers produced by one of the internal parsers, such as 145 `parseIpv6FromString` 146 */ 147 _ipv6 = { 148 /** 149 Converts an internal representation of an IPv6 address (i.e, a list 150 of integers) to a string. The returned string is not a canonical 151 representation as defined in RFC 5952, i.e zeros are not compressed. 152 153 # Type: IPv6 -> String 154 155 # Example: 156 157 ```nix 158 parseIpv6FromString [8193 3512 0 0 0 0 0 65535] 159 => "2001:db8:0:0:0:0:0:ffff" 160 ``` 161 */ 162 toStringFromExpandedIp = 163 pieces: strings.concatMapStringsSep ":" (piece: strings.toLower (trivial.toHexString piece)) pieces; 164 165 /** 166 Extract an address and subnet prefix length from a string. The subnet 167 prefix length is optional and defaults to 128. The resulting address and 168 prefix length are validated and converted to an internal representation 169 that can be used by other functions. 170 171 # Type: String -> [ {address :: IPv6, prefixLength :: Int} ] 172 173 # Example: 174 175 ```nix 176 split "2001:DB8::ffff/32" 177 => { 178 address = [8193 3512 0 0 0 0 0 65535]; 179 prefixLength = 32; 180 } 181 ``` 182 */ 183 split = 184 addr: 185 let 186 splitted = strings.splitString "/" addr; 187 splittedLength = length splitted; 188 in 189 if splittedLength == 1 then # [ ip ] 190 { 191 address = parseIpv6FromString addr; 192 prefixLength = ipv6Bits; 193 } 194 else if splittedLength == 2 then # [ ip subnet ] 195 { 196 address = parseIpv6FromString (head splitted); 197 prefixLength = 198 let 199 n = strings.toInt (last splitted); 200 in 201 if 1 <= n && n <= ipv6Bits then 202 n 203 else 204 throw "${addr} IPv6 subnet should be in range [1;${toString ipv6Bits}], got ${toString n}"; 205 } 206 else 207 throw "${addr} is not a valid IPv6 address in CIDR notation"; 208 }; 209}