Browse Source

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
Adrien Grand 8 years ago
parent
commit
d692ccf261

+ 30 - 0
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<InetAddress, Integer> 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 + "]");
+        }
+    }
 }

+ 3 - 8
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<InetAddress, Integer> cidr = InetAddresses.parseCidr(term);
+                    return InetAddressPoint.newPrefixQuery(name(), cidr.v1(), cidr.v2());
                 }
                 InetAddress address = InetAddresses.forString(term);
                 return InetAddressPoint.newExactQuery(name(), address);

+ 6 - 14
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<InetAddress, Integer> 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;

+ 32 - 0
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<InetAddress, Integer> 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());
+    }
 }