Skip to content

Commit 33ff952

Browse files
matt-calderNikhilCollooru
authored andcommitted
initial commit for ip_prefix_subnets
reviewer feedback
1 parent 79a5802 commit 33ff952

File tree

3 files changed

+108
-0
lines changed

3 files changed

+108
-0
lines changed

presto-docs/src/main/sphinx/functions/ip.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,11 @@ IP Functions
7070
SELECT is_private_ip(IPADDRESS '157.240.200.99'); -- false
7171
SELECT is_private_ip(IPADDRESS '2a03:2880:f031:12:face:b00c:0:2'); -- false
7272

73+
.. function:: ip_prefix_subnets(ip_prefix, prefix_length) -> array(ip_prefix)
74+
75+
Returns the subnets of ``ip_prefix`` of size ``prefix_length``. ``prefix_length`` must be valid ([0, 32] for IPv4
76+
and [0, 128] for IPv6) or the query will fail and raise an error. An empty array is returned if ``prefix_length``
77+
is shorter (that is, less specific) than ``ip_prefix``. ::
78+
79+
SELECT IP_PREFIX_SUBNETS(IPPREFIX '192.168.1.0/24', 25); -- [{192.168.1.0/25}, {192.168.1.128/25}]
80+
SELECT IP_PREFIX_SUBNETS(IPPREFIX '2a03:2880:c000::/34', 36); -- [{2a03:2880:c000::/36}, {2a03:2880:d000::/36}, {2a03:2880:e000::/36}, {2a03:2880:f000::/36}]

presto-main/src/main/java/com/facebook/presto/operator/scalar/IpPrefixFunctions.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ public final class IpPrefixFunctions
5252
{
5353
private static final BigInteger TWO = BigInteger.valueOf(2);
5454

55+
private static final Block EMPTY_BLOCK = IPPREFIX.createBlockBuilder(null, 0).build();
56+
5557
/**
5658
* Our definitions for what IANA considers not "globally reachable" are taken from the docs at
5759
* https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml and
@@ -290,6 +292,71 @@ public static boolean isPrivateIpAddress(@SqlType(StandardTypes.IPADDRESS) Slice
290292
return false;
291293
}
292294

295+
@Description("Split the input prefix into subnets the size of the new prefix length.")
296+
@ScalarFunction("ip_prefix_subnets")
297+
@SqlType("array(IPPREFIX)")
298+
public static Block ipPrefixSubnets(@SqlType(StandardTypes.IPPREFIX) Slice prefix, @SqlType(StandardTypes.BIGINT) long newPrefixLength)
299+
{
300+
boolean inputIsIpV4 = isIpv4(prefix);
301+
302+
if (newPrefixLength < 0 || (inputIsIpV4 && newPrefixLength > 32) || (!inputIsIpV4 && newPrefixLength > 128)) {
303+
throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Invalid prefix length for IPv" + (inputIsIpV4 ? "4" : "6") + ": " + newPrefixLength);
304+
}
305+
306+
int inputPrefixLength = getPrefixLength(prefix);
307+
// An IP prefix is a 'network', or group of contiguous IP addresses. The common format for describing IP prefixes is
308+
// uses 2 parts separated by a '/': (1) the IP address part and the (2) prefix length part (also called subnet size or CIDR).
309+
// For example, in 9.255.255.0/24, 9.255.255.0 is the IP address part and 24 is the prefix length.
310+
// The prefix length describes how many IP addresses the prefix contains in terms of the leading number of bits required. A higher number of bits
311+
// means smaller number of IP addresses. Subnets inherently mean smaller groups of IP addresses.
312+
// We can only disaggregate a prefix if the prefix length is the same length or longer (more-specific) than the length of the input prefix.
313+
// E.g., if the input prefix is 9.255.255.0/24, the prefix length can be /24, /25, /26, etc... but not 23 or larger value than 24.
314+
315+
int newPrefixCount = 0; // if inputPrefixLength > newPrefixLength, there are no new prefixes and we will return an empty array.
316+
if (inputPrefixLength <= newPrefixLength) {
317+
// Next, count how many new prefixes we will generate. In general, every difference in prefix length doubles the number new prefixes.
318+
// For example if we start with 9.255.255.0/24, and want to split into /25s, we would have 2 new prefixes. If we wanted to split into /26s,
319+
// we would have 4 new prefixes, and /27 would have 8 prefixes etc....
320+
newPrefixCount = 1 << (newPrefixLength - inputPrefixLength); // 2^N
321+
}
322+
323+
if (newPrefixCount == 0) {
324+
return EMPTY_BLOCK;
325+
}
326+
327+
BlockBuilder blockBuilder = IPPREFIX.createBlockBuilder(null, newPrefixCount);
328+
329+
if (newPrefixCount == 1) {
330+
IPPREFIX.writeSlice(blockBuilder, prefix); // just return the original prefix in an array
331+
return blockBuilder.build(); // returns empty or single entry
332+
}
333+
334+
int ipVersionMaxBits = inputIsIpV4 ? 32 : 128;
335+
BigInteger newPrefixIpCount = TWO.pow(ipVersionMaxBits - (int) newPrefixLength);
336+
337+
Slice startingIpAddressAsSlice = ipSubnetMin(prefix);
338+
BigInteger currentIpAddress = toBigInteger(startingIpAddressAsSlice);
339+
340+
try {
341+
for (int i = 0; i < newPrefixCount; i++) {
342+
InetAddress asInetAddress = bigIntegerToIpAddress(currentIpAddress);
343+
Slice ipPrefixAsSlice = castFromVarcharToIpPrefix(utf8Slice(InetAddresses.toAddrString(asInetAddress) + "/" + newPrefixLength));
344+
IPPREFIX.writeSlice(blockBuilder, ipPrefixAsSlice);
345+
currentIpAddress = currentIpAddress.add(newPrefixIpCount); // increment to start of next new prefix
346+
}
347+
}
348+
catch (UnknownHostException ex) {
349+
throw new PrestoException(GENERIC_INTERNAL_ERROR, "Unable to convert " + currentIpAddress + " to IP prefix", ex);
350+
}
351+
352+
return blockBuilder.build();
353+
}
354+
355+
private static int getPrefixLength(Slice ipPrefix)
356+
{
357+
return ipPrefix.getByte(IPPREFIX.getFixedSize() - 1) & 0xFF;
358+
}
359+
293360
private static List<Slice> generateMinIpPrefixes(BigInteger firstIpAddress, BigInteger lastIpAddress, int ipVersionMaxBits)
294361
{
295362
List<Slice> ipPrefixSlices = new ArrayList<>();

presto-main/src/test/java/com/facebook/presto/operator/scalar/TestIpPrefixFunctions.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,4 +287,37 @@ public void testIsPrivateIpNull()
287287
{
288288
assertFunction("IS_PRIVATE_IP(NULL)", BOOLEAN, null);
289289
}
290+
291+
@Test
292+
public void testIpPrefixSubnets()
293+
{
294+
assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.1.0/24', 25)", new ArrayType(IPPREFIX), ImmutableList.of("192.168.1.0/25", "192.168.1.128/25"));
295+
assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.0.0/24', 26)", new ArrayType(IPPREFIX), ImmutableList.of("192.168.0.0/26", "192.168.0.64/26", "192.168.0.128/26", "192.168.0.192/26"));
296+
assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '2A03:2880:C000::/34', 37)",
297+
new ArrayType(IPPREFIX),
298+
ImmutableList.of("2a03:2880:c000::/37", "2a03:2880:c800::/37", "2a03:2880:d000::/37", "2a03:2880:d800::/37", "2a03:2880:e000::/37", "2a03:2880:e800::/37", "2a03:2880:f000::/37", "2a03:2880:f800::/37"));
299+
}
300+
301+
@Test
302+
public void testIpPrefixSubnetsReturnSelf()
303+
{
304+
assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.1.0/24', 24)", new ArrayType(IPPREFIX), ImmutableList.of("192.168.1.0/24"));
305+
assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '2804:431:b000::/38', 38)", new ArrayType(IPPREFIX), ImmutableList.of("2804:431:b000::/38"));
306+
}
307+
308+
@Test
309+
public void testIpPrefixSubnetsNewPrefixLengthLongerReturnsEmpty()
310+
{
311+
assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.0.0/24', 23)", new ArrayType(IPPREFIX), ImmutableList.of());
312+
assertFunction("IP_PREFIX_SUBNETS(IPPREFIX '64:ff9b::17/64', 48)", new ArrayType(IPPREFIX), ImmutableList.of());
313+
}
314+
315+
@Test
316+
public void testIpPrefixSubnetsInvalidPrefixLengths()
317+
{
318+
assertInvalidFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.0.0/24', -1)", "Invalid prefix length for IPv4: -1");
319+
assertInvalidFunction("IP_PREFIX_SUBNETS(IPPREFIX '192.168.0.0/24', 33)", "Invalid prefix length for IPv4: 33");
320+
assertInvalidFunction("IP_PREFIX_SUBNETS(IPPREFIX '64:ff9b::17/64', -1)", "Invalid prefix length for IPv6: -1");
321+
assertInvalidFunction("IP_PREFIX_SUBNETS(IPPREFIX '64:ff9b::17/64', 129)", "Invalid prefix length for IPv6: 129");
322+
}
290323
}

0 commit comments

Comments
 (0)