From d692ccf26172af1ae4c08bfa67cba6620b6fb408 Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Mon, 28 Aug 2017 10:04:05 +0200 Subject: [PATCH] Reject IPv6-mapped IPv4 addresses when using the CIDR notation. (#26254) It introduces ambiguity as to whether the prefix length should be interpreted as a v4 prefix length or a v6 prefix length. See https://issues.apache.org/jira/browse/LUCENE-7920. Closes #26078 --- .../common/network/InetAddresses.java | 30 +++++++++++++++++ .../index/mapper/IpFieldMapper.java | 11 ++----- .../range/IpRangeAggregationBuilder.java | 20 ++++-------- .../common/network/InetAddressesTests.java | 32 +++++++++++++++++++ 4 files changed, 71 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/network/InetAddresses.java b/core/src/main/java/org/elasticsearch/common/network/InetAddresses.java index 4d3d140ae63..2e68d8358f0 100644 --- a/core/src/main/java/org/elasticsearch/common/network/InetAddresses.java +++ b/core/src/main/java/org/elasticsearch/common/network/InetAddresses.java @@ -16,6 +16,8 @@ package org.elasticsearch.common.network; +import org.elasticsearch.common.collect.Tuple; + import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -354,4 +356,32 @@ public class InetAddresses { throw new AssertionError(e); } } + + /** + * Parse an IP address and its prefix length using the CIDR notation. + * @throws IllegalArgumentException if the string is not formatted as {@code ip_address/prefix_length} + * @throws IllegalArgumentException if the IP address is an IPv6-mapped ipv4 address + * @throws IllegalArgumentException if the prefix length is not in 0-32 for IPv4 addresses and 0-128 for IPv6 addresses + * @throws NumberFormatException if the prefix length is not an integer + */ + public static Tuple parseCidr(String maskedAddress) { + String[] fields = maskedAddress.split("/"); + if (fields.length == 2) { + final String addressString = fields[0]; + final InetAddress address = forString(addressString); + if (addressString.contains(":") && address.getAddress().length == 4) { + throw new IllegalArgumentException("CIDR notation is not allowed with IPv6-mapped IPv4 address [" + addressString + + " as it introduces ambiguity as to whether the prefix length should be interpreted as a v4 prefix length or a" + + " v6 prefix length"); + } + final int prefixLength = Integer.parseInt(fields[1]); + if (prefixLength < 0 || prefixLength > 8 * address.getAddress().length) { + throw new IllegalArgumentException("Illegal prefix length [" + prefixLength + "] in [" + maskedAddress + + "]. Must be 0-32 for IPv4 ranges, 0-128 for IPv6 ranges"); + } + return new Tuple<>(address, prefixLength); + } else { + throw new IllegalArgumentException("Expected [ip/prefix] but was [" + maskedAddress + "]"); + } + } } diff --git a/core/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java b/core/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java index 7b45e5f05b1..d192fa48730 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/IpFieldMapper.java @@ -31,6 +31,7 @@ import org.apache.lucene.util.ArrayUtil; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.Explicit; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.network.InetAddresses; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -163,14 +164,8 @@ public class IpFieldMapper extends FieldMapper { } String term = value.toString(); if (term.contains("/")) { - String[] fields = term.split("/"); - if (fields.length == 2) { - InetAddress address = InetAddresses.forString(fields[0]); - int prefixLength = Integer.parseInt(fields[1]); - return InetAddressPoint.newPrefixQuery(name(), address, prefixLength); - } else { - throw new IllegalArgumentException("Expected [ip/prefix] but was [" + term + "]"); - } + final Tuple cidr = InetAddresses.parseCidr(term); + return InetAddressPoint.newPrefixQuery(name(), cidr.v1(), cidr.v2()); } InetAddress address = InetAddresses.forString(term); return InetAddressPoint.newExactQuery(name(), address); diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/IpRangeAggregationBuilder.java b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/IpRangeAggregationBuilder.java index 6e58d045349..05b05c2fc01 100644 --- a/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/IpRangeAggregationBuilder.java +++ b/core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/IpRangeAggregationBuilder.java @@ -22,6 +22,7 @@ import org.apache.lucene.document.InetAddressPoint; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParsingException; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.network.InetAddresses; @@ -127,21 +128,12 @@ public final class IpRangeAggregationBuilder } Range(String key, String mask) { - String[] splits = mask.split("/"); - if (splits.length != 2) { - throw new IllegalArgumentException("Expected [ip/prefix_length] but got [" + mask - + "], which contains zero or more than one [/]"); - } - InetAddress value = InetAddresses.forString(splits[0]); - int prefixLength = Integer.parseInt(splits[1]); - // copied from InetAddressPoint.newPrefixQuery - if (prefixLength < 0 || prefixLength > 8 * value.getAddress().length) { - throw new IllegalArgumentException("illegal prefixLength [" + prefixLength - + "] in [" + mask + "]. Must be 0-32 for IPv4 ranges, 0-128 for IPv6 ranges"); - } + final Tuple cidr = InetAddresses.parseCidr(mask); + final InetAddress address = cidr.v1(); + final int prefixLength = cidr.v2(); // create the lower value by zeroing out the host portion, upper value by filling it with all ones. - byte lower[] = value.getAddress(); - byte upper[] = value.getAddress(); + byte lower[] = address.getAddress(); + byte upper[] = address.getAddress(); for (int i = prefixLength; i < 8 * lower.length; i++) { int m = 1 << (7 - (i & 7)); lower[i >> 3] &= ~m; diff --git a/core/src/test/java/org/elasticsearch/common/network/InetAddressesTests.java b/core/src/test/java/org/elasticsearch/common/network/InetAddressesTests.java index 2aa284dd843..f323494b987 100644 --- a/core/src/test/java/org/elasticsearch/common/network/InetAddressesTests.java +++ b/core/src/test/java/org/elasticsearch/common/network/InetAddressesTests.java @@ -16,7 +16,9 @@ package org.elasticsearch.common.network; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.test.ESTestCase; +import org.hamcrest.Matchers; import java.net.InetAddress; import java.net.UnknownHostException; @@ -214,4 +216,34 @@ public class InetAddressesTests extends ESTestCase { InetAddress ip = InetAddresses.forString(ipStr); assertEquals("[3ffe::1]", InetAddresses.toUriString(ip)); } + + public void testParseCidr() { + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> InetAddresses.parseCidr("")); + assertThat(e.getMessage(), Matchers.containsString("Expected [ip/prefix] but was []")); + + e = expectThrows(IllegalArgumentException.class, () -> InetAddresses.parseCidr("192.168.1.42/33")); + assertThat(e.getMessage(), Matchers.containsString("Illegal prefix length")); + + e = expectThrows(IllegalArgumentException.class, () -> InetAddresses.parseCidr("::1/129")); + assertThat(e.getMessage(), Matchers.containsString("Illegal prefix length")); + + e = expectThrows(IllegalArgumentException.class, () -> InetAddresses.parseCidr("::ffff:0:0/96")); + assertThat(e.getMessage(), Matchers.containsString("CIDR notation is not allowed with IPv6-mapped IPv4 address")); + + Tuple cidr = InetAddresses.parseCidr("192.168.0.0/24"); + assertEquals(InetAddresses.forString("192.168.0.0"), cidr.v1()); + assertEquals(Integer.valueOf(24), cidr.v2()); + + cidr = InetAddresses.parseCidr("::fffe:0:0/95"); + assertEquals(InetAddresses.forString("::fffe:0:0"), cidr.v1()); + assertEquals(Integer.valueOf(95), cidr.v2()); + + cidr = InetAddresses.parseCidr("192.168.0.0/32"); + assertEquals(InetAddresses.forString("192.168.0.0"), cidr.v1()); + assertEquals(Integer.valueOf(32), cidr.v2()); + + cidr = InetAddresses.parseCidr("::fffe:0:0/128"); + assertEquals(InetAddresses.forString("::fffe:0:0"), cidr.v1()); + assertEquals(Integer.valueOf(128), cidr.v2()); + } }