|
| 1 | +// NOTE(sg): This file is symlinked to `component/espejote-templates` in |
| 2 | +// component-cilium to allow the `espejote-templates/egress-gateway.libsonnet` |
| 3 | +// library to work regardless of whether it's used by Espejote or the |
| 4 | +// component. We export this as a component library since it might be useful |
| 5 | +// for other components on Cilium-enabled clusters. |
| 6 | + |
| 7 | +// Convert an IPv4 address in A.B.C.D format that's already been split into an |
| 8 | +// array to decimal format according to the formula `A*256^3 + B*256^2 + C*256 |
| 9 | +// + D`. The decimal format allows us to make range comparisons and compute |
| 10 | +// offsets into a range. |
| 11 | +// Parameter ip can either be the IP as a string, or already split into an |
| 12 | +// array holding each dotted part. |
| 13 | +local ipval(ip) = |
| 14 | + local iparr = |
| 15 | + if std.type(ip) == 'array' then |
| 16 | + ip |
| 17 | + else |
| 18 | + std.split(ip, '.'); |
| 19 | + local iparr_int = std.map(std.parseInt, iparr); |
| 20 | + |
| 21 | + if std.any(std.map(function(v) v > 255, iparr_int)) then |
| 22 | + error 'Error parsing IPv4 address: %s is not a valid address' % [ |
| 23 | + ip, |
| 24 | + ] |
| 25 | + else |
| 26 | + std.foldl( |
| 27 | + function(v, p) v * 256 + p, |
| 28 | + iparr_int, |
| 29 | + 0 |
| 30 | + ); |
| 31 | + |
| 32 | +// Extract start and end from the provided range, stripping any |
| 33 | +// whitespace. `prefix` is only used for the error message. |
| 34 | +local parse_ip_range(prefix, rangespec) = |
| 35 | + local range_parts = std.map( |
| 36 | + function(s) std.stripChars(s, ' '), |
| 37 | + std.split(rangespec, '-') |
| 38 | + ); |
| 39 | + if std.length(range_parts) != 2 then |
| 40 | + error 'Expected IP range for "%s" in format "192.0.2.32-192.0.2.63", got %s' % [ |
| 41 | + prefix, |
| 42 | + rangespec, |
| 43 | + ] |
| 44 | + else |
| 45 | + { |
| 46 | + start: range_parts[0], |
| 47 | + end: range_parts[1], |
| 48 | + }; |
| 49 | + |
| 50 | +local format_ipval(val) = |
| 51 | + assert |
| 52 | + val >= 0 && val <= ipval('255.255.255.255') |
| 53 | + : '%s not an IPv4 address in decimal' % val; |
| 54 | + |
| 55 | + local iparr = std.reverse(std.foldl( |
| 56 | + function(st, i) |
| 57 | + local arr = st.arr; |
| 58 | + local rem = st.rem; |
| 59 | + { |
| 60 | + arr: arr + [ rem % 256 ], |
| 61 | + rem: rem / 256, |
| 62 | + }, |
| 63 | + [ 0, 0, 0, 0 ], |
| 64 | + { arr: [], rem: val } |
| 65 | + ).arr); |
| 66 | + |
| 67 | + std.join('.', std.map(function(v) '%d' % v, iparr)); |
| 68 | + |
| 69 | +// Parse network in CIDR notation. Leading and trailing whitespace is |
| 70 | +// stripped. `prefix` is only used for the error message. |
| 71 | +// |
| 72 | +// This function correctly parses the full network info from arbitrary IPs in |
| 73 | +// CIDR notation. We return an object that's inspired by the output of the |
| 74 | +// Linux utility `ipcalc`. |
| 75 | +// |
| 76 | +// The return value contains the network address, broadcast address, count of |
| 77 | +// IPs in the CIDR and prefix length. For prefix lengths of less than 32, the |
| 78 | +// return value additionally contains the first and last host (in `min_host` |
| 79 | +// and `max_host`) and netmask. |
| 80 | +local parse_cidr(prefix, cidr) = |
| 81 | + local parts = std.split(std.stripChars(cidr, ' '), '/'); |
| 82 | + if std.length(parts) != 2 then |
| 83 | + error 'Expected value for "%s" to be in CIDR notation, got "%s"' % [ |
| 84 | + prefix, |
| 85 | + cidr, |
| 86 | + ] |
| 87 | + else |
| 88 | + local prefix_length = std.parseInt(parts[1]); |
| 89 | + if prefix_length < 0 || prefix_length > 32 then |
| 90 | + error 'Invalid CIDR %s: prefix must be between 0 and 32' % cidr |
| 91 | + else |
| 92 | + // We compute count, netmask and network address using bitwise operations. |
| 93 | + // Jsonnet uses 64 bit integers for bitwise ops, so we don't have to worry |
| 94 | + // about overflowing when working with 32 bit values (IPv4 addresses). |
| 95 | + // |
| 96 | + // IPv4 CIDR notation works as follows: <addr>/<prefix> defines a network |
| 97 | + // where the first <prefix> bits of the IP are the "network" and the last |
| 98 | + // 32-<prefix> bits are (mostly) freely selectable for addresses within |
| 99 | + // that network. |
| 100 | + // |
| 101 | + // Bitwise glossary: |
| 102 | + // - (1 << n) == 2**n |
| 103 | + // - `&` is bitwise and (setting all bits that are set in either operand) |
| 104 | + // - `~` is bitwise not (flipping all bits of the operand) |
| 105 | + // Jsonnet operator precedence: binary +- bind higher than shifts |
| 106 | + |
| 107 | + // count is the number of available addresses (including the network and |
| 108 | + // broadcast address in the network). It's a value which has the |
| 109 | + // 32-<prefix> low bits set to 1 and all other bits set to 0. |
| 110 | + local count = (1 << 32 - prefix_length) - 1; |
| 111 | + // Netmask has the high <prefix> bits set to one and the 32-<prefix> low |
| 112 | + // bits set to 0. We can use `~count` as the mask to set the low |
| 113 | + // 32-<prefix> bits to 0, since count has only these bits set to 1 and |
| 114 | + // bitwise not flips all bits. |
| 115 | + local netmask = ((1 << 32) - 1) & ~count; |
| 116 | + // The network address is the first address in the network. By converting |
| 117 | + // the specified <addr> to an integer and using the netmask to set the low |
| 118 | + // 32-<prefix> bits to 0 we reliably get the network address regardless of |
| 119 | + // which IP in the network that the user specified for a given prefix. |
| 120 | + local net_addr = ipval(parts[0]) & netmask; |
| 121 | + |
| 122 | + { |
| 123 | + network_address: format_ipval(net_addr), |
| 124 | + broadcast_address: format_ipval(net_addr + count), |
| 125 | + prefix_length: prefix_length, |
| 126 | + count: count, |
| 127 | + } + if prefix_length < 32 then { |
| 128 | + host_min: format_ipval(net_addr + 1), |
| 129 | + host_max: format_ipval(net_addr + count - 1), |
| 130 | + netmask: format_ipval(netmask), |
| 131 | + } else {}; |
| 132 | + |
| 133 | +{ |
| 134 | + ipval: ipval, |
| 135 | + parse_ip_range: parse_ip_range, |
| 136 | + parse_cidr: parse_cidr, |
| 137 | + format_ipval: format_ipval, |
| 138 | +} |
0 commit comments