瀏覽代碼

Allow CIDR notation in a query string query

Close #14773
William Bowling 10 年之前
父節點
當前提交
18c9eba40a

+ 82 - 1
core/src/main/java/org/elasticsearch/index/mapper/ip/IpFieldMapper.java

@@ -28,9 +28,10 @@ import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.BytesRefBuilder;
 import org.apache.lucene.util.NumericUtils;
 import org.elasticsearch.common.Explicit;
-import org.elasticsearch.common.network.InetAddresses;
+import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.Numbers;
 import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.network.InetAddresses;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.unit.Fuzziness;
 import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -46,10 +47,13 @@ import org.elasticsearch.index.mapper.ParseContext;
 import org.elasticsearch.index.mapper.core.LongFieldMapper;
 import org.elasticsearch.index.mapper.core.LongFieldMapper.CustomLongNumericField;
 import org.elasticsearch.index.mapper.core.NumberFieldMapper;
+import org.elasticsearch.index.query.QueryShardContext;
+
 import java.io.IOException;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import static org.elasticsearch.index.mapper.MapperBuilders.ipField;
@@ -61,6 +65,7 @@ import static org.elasticsearch.index.mapper.core.TypeParsers.parseNumberField;
 public class IpFieldMapper extends NumberFieldMapper {
 
     public static final String CONTENT_TYPE = "ip";
+    public static final long MAX_IP = 4294967296l;
 
     public static String longToIp(long longIp) {
         int octet3 = (int) ((longIp >> 24) % 256);
@@ -71,6 +76,7 @@ public class IpFieldMapper extends NumberFieldMapper {
     }
 
     private static final Pattern pattern = Pattern.compile("\\.");
+    private static final Pattern MASK_PATTERN = Pattern.compile("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,3})");
 
     public static long ipToLong(String ip) {
         try {
@@ -91,6 +97,64 @@ public class IpFieldMapper extends NumberFieldMapper {
         }
     }
 
+    /**
+     * Computes the min & max ip addresses (represented as long values -
+     * same way as stored in index) represented by the given CIDR mask
+     * expression. The returned array has the length of 2, where the first entry
+     * represents the {@code min} address and the second the {@code max}. A
+     * {@code -1} value for either the {@code min} or the {@code max},
+     * represents an unbounded end. In other words:
+     *
+     * <p>
+     * {@code min == -1 == "0.0.0.0" }
+     * </p>
+     *
+     * and
+     *
+     * <p>
+     * {@code max == -1 == "255.255.255.255" }
+     * </p>
+     */
+    public static long[] cidrMaskToMinMax(String cidr) {
+        Matcher matcher = MASK_PATTERN.matcher(cidr);
+        if (!matcher.matches()) {
+            return null;
+        }
+        int addr = ((Integer.parseInt(matcher.group(1)) << 24) & 0xFF000000) | ((Integer.parseInt(matcher.group(2)) << 16) & 0xFF0000)
+                | ((Integer.parseInt(matcher.group(3)) << 8) & 0xFF00) | (Integer.parseInt(matcher.group(4)) & 0xFF);
+
+        int mask = (-1) << (32 - Integer.parseInt(matcher.group(5)));
+
+        if (Integer.parseInt(matcher.group(5)) == 0) {
+            mask = 0 << 32;
+        }
+
+        int from = addr & mask;
+        long longFrom = intIpToLongIp(from);
+        if (longFrom == 0) {
+            longFrom = -1;
+        }
+
+        int to = from + (~mask);
+        long longTo = intIpToLongIp(to) + 1; // we have to +1 here as the range
+                                             // is non-inclusive on the "to"
+                                             // side
+
+        if (longTo == MAX_IP) {
+            longTo = -1;
+        }
+
+        return new long[] { longFrom, longTo };
+    }
+
+    private static long intIpToLongIp(int i) {
+        long p1 = ((long) ((i >> 24) & 0xFF)) << 24;
+        int p2 = ((i >> 16) & 0xFF) << 16;
+        int p3 = ((i >> 8) & 0xFF) << 8;
+        int p4 = i & 0xFF;
+        return p1 + p2 + p3 + p4;
+    }
+
     public static class Defaults extends NumberFieldMapper.Defaults {
         public static final String NULL_VALUE = null;
 
@@ -205,6 +269,23 @@ public class IpFieldMapper extends NumberFieldMapper {
             return bytesRef.get();
         }
 
+        @Override
+        public Query termQuery(Object value, @Nullable QueryShardContext context) {
+            if (value != null) {
+                long[] fromTo;
+                if (value instanceof BytesRef) {
+                    fromTo = cidrMaskToMinMax(((BytesRef) value).utf8ToString());
+                } else {
+                    fromTo = cidrMaskToMinMax(value.toString());
+                }
+                if (fromTo != null) {
+                    return rangeQuery(fromTo[0] < 0 ? null : fromTo[0],
+                            fromTo[1] < 0 ? null : fromTo[1], true, false);
+                }
+            }
+            return super.termQuery(value, context);
+        }
+
         @Override
         public Query rangeQuery(Object lowerTerm, Object upperTerm, boolean includeLower, boolean includeUpper) {
             return NumericRangeQuery.newLongRange(names().indexName(), numericPrecisionStep(),

+ 1 - 57
core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IPv4RangeBuilder.java

@@ -22,15 +22,13 @@ package org.elasticsearch.search.aggregations.bucket.range.ipv4;
 import org.elasticsearch.search.aggregations.bucket.range.AbstractRangeBuilder;
 import org.elasticsearch.search.builder.SearchSourceBuilderException;
 
-import java.util.regex.Pattern;
+import static org.elasticsearch.index.mapper.ip.IpFieldMapper.cidrMaskToMinMax;
 
 /**
  * Builder for the {@code IPv4Range} aggregation.
  */
 public class IPv4RangeBuilder extends AbstractRangeBuilder<IPv4RangeBuilder> {
 
-    private static final Pattern MASK_PATTERN = Pattern.compile("[\\.|/]");
-
     /**
      * Sole constructor.
      */
@@ -109,58 +107,4 @@ public class IPv4RangeBuilder extends AbstractRangeBuilder<IPv4RangeBuilder> {
         return addUnboundedFrom(null, from);
     }
 
-    /**
-     * Computes the min &amp; max ip addresses (represented as long values - same way as stored in index) represented by the given CIDR mask
-     * expression. The returned array has the length of 2, where the first entry represents the {@code min} address and the second the {@code max}.
-     * A {@code -1} value for either the {@code min} or the {@code max}, represents an unbounded end. In other words:
-     *
-     * <p>
-     * {@code min == -1 == "0.0.0.0" }
-     * </p>
-     *
-     * and
-     *
-     * <p>
-     * {@code max == -1 == "255.255.255.255" }
-     * </p>
-     */
-    static long[] cidrMaskToMinMax(String cidr) {
-        String[] parts = MASK_PATTERN.split(cidr);
-        if (parts.length != 5) {
-            return null;
-        }
-        int addr = (( Integer.parseInt(parts[0]) << 24 ) & 0xFF000000)
-                | (( Integer.parseInt(parts[1]) << 16 ) & 0xFF0000)
-                | (( Integer.parseInt(parts[2]) << 8 ) & 0xFF00)
-                |  ( Integer.parseInt(parts[3]) & 0xFF);
-
-        int mask = (-1) << (32 - Integer.parseInt(parts[4]));
-
-        if (Integer.parseInt(parts[4]) == 0) {
-            mask = 0 << 32;
-        }
-
-        int from = addr & mask;
-        long longFrom = intIpToLongIp(from);
-        if (longFrom == 0) {
-            longFrom = -1;
-        }
-
-        int to = from + (~mask);
-        long longTo = intIpToLongIp(to) + 1; // we have to +1 here as the range is non-inclusive on the "to" side
-
-        if (longTo == InternalIPv4Range.MAX_IP) {
-            longTo = -1;
-        }
-
-        return new long[] { longFrom, longTo };
-    }
-
-    private static long intIpToLongIp(int i) {
-        long p1 = ((long) ((i >> 24 ) & 0xFF)) << 24;
-        int p2 = ((i >> 16 ) & 0xFF) << 16;
-        int p3 = ((i >>  8 ) & 0xFF) << 8;
-        int p4 = i & 0xFF;
-        return p1 + p2 + p3 + p4;
-    }
 }

+ 2 - 2
core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/InternalIPv4Range.java

@@ -32,13 +32,13 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
+import static org.elasticsearch.index.mapper.ip.IpFieldMapper.MAX_IP;
+
 /**
  *
  */
 public class InternalIPv4Range extends InternalRange<InternalIPv4Range.Bucket, InternalIPv4Range> {
 
-    public static final long MAX_IP = 4294967296l;
-
     public final static Type TYPE = new Type("ip_range", "iprange");
 
     private final static AggregationStreams.Stream STREAM = new AggregationStreams.Stream() {

+ 2 - 1
core/src/main/java/org/elasticsearch/search/aggregations/bucket/range/ipv4/IpRangeParser.java

@@ -19,6 +19,7 @@
 package org.elasticsearch.search.aggregations.bucket.range.ipv4;
 
 import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.index.mapper.ip.IpFieldMapper;
 import org.elasticsearch.search.SearchParseException;
 import org.elasticsearch.search.aggregations.Aggregator;
 import org.elasticsearch.search.aggregations.AggregatorFactory;
@@ -124,7 +125,7 @@ public class IpRangeParser implements Aggregator.Parser {
     }
 
     private static void parseMaskRange(String cidr, RangeAggregator.Range range, String aggregationName, SearchContext ctx) {
-        long[] fromTo = IPv4RangeBuilder.cidrMaskToMinMax(cidr);
+        long[] fromTo = IpFieldMapper.cidrMaskToMinMax(cidr);
         if (fromTo == null) {
             throw new SearchParseException(ctx, "invalid CIDR mask [" + cidr + "] in aggregation [" + aggregationName + "]",
                     null);

+ 53 - 0
core/src/test/java/org/elasticsearch/search/simple/SimpleSearchIT.java

@@ -27,6 +27,7 @@ import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentFactory;
 import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.rest.RestStatus;
 import org.elasticsearch.search.internal.DefaultSearchContext;
 import org.elasticsearch.test.ESIntegTestCase;
 
@@ -41,6 +42,7 @@ import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
+import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertFailures;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
 import static org.hamcrest.Matchers.containsString;
@@ -104,6 +106,57 @@ public class SimpleSearchIT extends ESIntegTestCase {
         assertHitCount(search, 1l);
     }
 
+    public void testIpCIDR() throws Exception {
+        createIndex("test");
+
+        client().admin().indices().preparePutMapping("test").setType("type1")
+                .setSource(XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
+                        .startObject("ip").field("type", "ip").endObject()
+                        .endObject().endObject().endObject())
+                .execute().actionGet();
+        ensureGreen();
+
+        client().prepareIndex("test", "type1", "1").setSource("ip", "192.168.0.1").execute().actionGet();
+        client().prepareIndex("test", "type1", "2").setSource("ip", "192.168.0.2").execute().actionGet();
+        client().prepareIndex("test", "type1", "3").setSource("ip", "192.168.0.3").execute().actionGet();
+        client().prepareIndex("test", "type1", "4").setSource("ip", "192.168.1.4").execute().actionGet();
+        refresh();
+
+        SearchResponse search = client().prepareSearch()
+                .setQuery(boolQuery().must(QueryBuilders.termQuery("ip", "192.168.0.1/32")))
+                .execute().actionGet();
+        assertHitCount(search, 1l);
+
+        search = client().prepareSearch()
+                .setQuery(boolQuery().must(QueryBuilders.termQuery("ip", "192.168.0.1/24")))
+                .execute().actionGet();
+        assertHitCount(search, 3l);
+
+        search = client().prepareSearch()
+                .setQuery(boolQuery().must(QueryBuilders.termQuery("ip", "192.168.0.1/8")))
+                .execute().actionGet();
+        assertHitCount(search, 4l);
+
+        search = client().prepareSearch()
+                .setQuery(boolQuery().must(QueryBuilders.termQuery("ip", "192.168.1.1/24")))
+                .execute().actionGet();
+        assertHitCount(search, 1l);
+
+        search = client().prepareSearch()
+                .setQuery(boolQuery().must(QueryBuilders.termQuery("ip", "0.0.0.0/0")))
+                .execute().actionGet();
+        assertHitCount(search, 4l);
+
+        search = client().prepareSearch()
+                .setQuery(boolQuery().must(QueryBuilders.termQuery("ip", "192.168.1.5/32")))
+                .execute().actionGet();
+        assertHitCount(search, 0l);
+
+        assertFailures(client().prepareSearch().setQuery(boolQuery().must(QueryBuilders.termQuery("ip", "0/0/0/0/0"))),
+                RestStatus.BAD_REQUEST,
+                containsString("not a valid ip address"));
+    }
+
     public void testSimpleId() {
         createIndex("test");