Skip to content

Commit

Permalink
Merge pull request elastic#14874 from jasontedor/ipv4-parsing
Browse files Browse the repository at this point in the history
Do not be lenient when parsing CIDRs
  • Loading branch information
jasontedor committed Nov 24, 2015
2 parents 321606d + b31f285 commit 7c104fd
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 84 deletions.
116 changes: 116 additions & 0 deletions core/src/main/java/org/elasticsearch/common/network/Cidrs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.common.network;

import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;

public final class Cidrs {
private Cidrs() {
}

/**
* Parses an IPv4 address block in CIDR notation into a pair of
* longs representing the bottom and top of the address block
*
* @param cidr an address block in CIDR notation a.b.c.d/n
* @return array representing the address block
* @throws IllegalArgumentException if the cidr can not be parsed
*/
public static long[] cidrMaskToMinMax(String cidr) {
Objects.requireNonNull(cidr, "cidr");
String[] fields = cidr.split("/");
if (fields.length != 2) {
throw new IllegalArgumentException(
String.format(Locale.ROOT, "invalid IPv4/CIDR; expected [a.b.c.d, e] but was [%s] after splitting on \"/\" in [%s]", Arrays.toString(fields), cidr)
);
}
// do not try to parse IPv4-mapped IPv6 address
if (fields[0].contains(":")) {
throw new IllegalArgumentException(
String.format(Locale.ROOT, "invalid IPv4/CIDR; expected [a.b.c.d, e] where a, b, c, d are decimal octets but was [%s] after splitting on \"/\" in [%s]", Arrays.toString(fields), cidr)
);
}
byte[] addressBytes;
try {
addressBytes = InetAddresses.forString(fields[0]).getAddress();
} catch (Throwable t) {
throw new IllegalArgumentException(
String.format(Locale.ROOT, "invalid IPv4/CIDR; unable to parse [%s] as an IP address literal", fields[0]), t
);
}
long accumulator =
((addressBytes[0] & 0xFFL) << 24) +
((addressBytes[1] & 0xFFL) << 16) +
((addressBytes[2] & 0xFFL) << 8) +
((addressBytes[3] & 0xFFL));
int networkMask;
try {
networkMask = Integer.parseInt(fields[1]);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
String.format(Locale.ROOT, "invalid IPv4/CIDR; invalid network mask [%s] in [%s]", fields[1], cidr),
e
);
}
if (networkMask < 0 || networkMask > 32) {
throw new IllegalArgumentException(
String.format(Locale.ROOT, "invalid IPv4/CIDR; invalid network mask [%s], out of range in [%s]", fields[1], cidr)
);
}

long blockSize = 1L << (32 - networkMask);
// validation
if ((accumulator & (blockSize - 1)) != 0) {
throw new IllegalArgumentException(
String.format(
Locale.ROOT,
"invalid IPv4/CIDR; invalid address/network mask combination in [%s]; perhaps [%s] was intended?",
cidr,
octetsToCIDR(longToOctets(accumulator - (accumulator & (blockSize - 1))), networkMask)
)
);
}
return new long[] { accumulator, accumulator + blockSize };
}

static int[] longToOctets(long value) {
assert value >= 0 && value <= (1L << 32) : value;
int[] octets = new int[4];
octets[0] = (int)((value >> 24) & 0xFF);
octets[1] = (int)((value >> 16) & 0xFF);
octets[2] = (int)((value >> 8) & 0xFF);
octets[3] = (int)(value & 0xFF);
return octets;
}

static String octetsToString(int[] octets) {
assert octets != null;
assert octets.length == 4;
return String.format(Locale.ROOT, "%d.%d.%d.%d", octets[0], octets[1], octets[2], octets[3]);
}

static String octetsToCIDR(int[] octets, int networkMask) {
assert octets != null;
assert octets.length == 4;
return octetsToString(octets) + "/" + networkMask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Numbers;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.network.Cidrs;
import org.elasticsearch.common.network.InetAddresses;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.Fuzziness;
Expand All @@ -48,6 +49,7 @@
import org.elasticsearch.index.mapper.core.LongFieldMapper.CustomLongNumericField;
import org.elasticsearch.index.mapper.core.NumberFieldMapper;
import org.elasticsearch.index.query.QueryShardContext;
import org.elasticsearch.search.aggregations.bucket.range.ipv4.InternalIPv4Range;

import java.io.IOException;
import java.util.Iterator;
Expand Down Expand Up @@ -76,7 +78,6 @@ public static String longToIp(long longIp) {
}

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 {
Expand All @@ -97,64 +98,6 @@ public static long ipToLong(String ip) {
}
}

/**
* 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>
*/
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;

Expand Down Expand Up @@ -274,13 +217,13 @@ public Query termQuery(Object value, @Nullable QueryShardContext context) {
if (value != null) {
long[] fromTo;
if (value instanceof BytesRef) {
fromTo = cidrMaskToMinMax(((BytesRef) value).utf8ToString());
fromTo = Cidrs.cidrMaskToMinMax(((BytesRef) value).utf8ToString());
} else {
fromTo = cidrMaskToMinMax(value.toString());
fromTo = Cidrs.cidrMaskToMinMax(value.toString());
}
if (fromTo != null) {
return rangeQuery(fromTo[0] < 0 ? null : fromTo[0],
fromTo[1] < 0 ? null : fromTo[1], true, false);
return rangeQuery(fromTo[0] == 0 ? null : fromTo[0],
fromTo[1] == InternalIPv4Range.MAX_IP ? null : fromTo[1], true, false);
}
}
return super.termQuery(value, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@

package org.elasticsearch.search.aggregations.bucket.range.ipv4;

import org.elasticsearch.common.network.Cidrs;
import org.elasticsearch.search.aggregations.bucket.range.AbstractRangeBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilderException;

import static org.elasticsearch.index.mapper.ip.IpFieldMapper.cidrMaskToMinMax;

/**
* Builder for the {@code IPv4Range} aggregation.
*/
Expand Down Expand Up @@ -59,11 +58,13 @@ public IPv4RangeBuilder addMaskRange(String mask) {
* Add a range based on a CIDR mask.
*/
public IPv4RangeBuilder addMaskRange(String key, String mask) {
long[] fromTo = cidrMaskToMinMax(mask);
if (fromTo == null) {
throw new SearchSourceBuilderException("invalid CIDR mask [" + mask + "] in ip_range aggregation [" + getName() + "]");
long[] fromTo;
try {
fromTo = Cidrs.cidrMaskToMinMax(mask);
} catch (IllegalArgumentException e) {
throw new SearchSourceBuilderException("invalid CIDR mask [" + mask + "] in ip_range aggregation [" + getName() + "]", e);
}
ranges.add(new Range(key, fromTo[0] < 0 ? null : fromTo[0], fromTo[1] < 0 ? null : fromTo[1]));
ranges.add(new Range(key, fromTo[0] == 0 ? null : fromTo[0], fromTo[1] == InternalIPv4Range.MAX_IP ? null : fromTo[1]));
return this;
}

Expand Down Expand Up @@ -106,5 +107,4 @@ public IPv4RangeBuilder addUnboundedFrom(String key, String from) {
public IPv4RangeBuilder addUnboundedFrom(String from) {
return addUnboundedFrom(null, from);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
*
*/
public class InternalIPv4Range extends InternalRange<InternalIPv4Range.Bucket, InternalIPv4Range> {
public static final long MAX_IP = 1L << 32;

public final static Type TYPE = new Type("ip_range", "iprange");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.elasticsearch.search.aggregations.bucket.range.ipv4;

import org.elasticsearch.common.network.Cidrs;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.mapper.ip.IpFieldMapper;
import org.elasticsearch.search.SearchParseException;
Expand Down Expand Up @@ -125,13 +126,15 @@ public AggregatorFactory parse(String aggregationName, XContentParser parser, Se
}

private static void parseMaskRange(String cidr, RangeAggregator.Range range, String aggregationName, SearchContext ctx) {
long[] fromTo = IpFieldMapper.cidrMaskToMinMax(cidr);
if (fromTo == null) {
long[] fromTo;
try {
fromTo = Cidrs.cidrMaskToMinMax(cidr);
} catch (IllegalArgumentException e) {
throw new SearchParseException(ctx, "invalid CIDR mask [" + cidr + "] in aggregation [" + aggregationName + "]",
null);
null, e);
}
range.from = fromTo[0] < 0 ? Double.NEGATIVE_INFINITY : fromTo[0];
range.to = fromTo[1] < 0 ? Double.POSITIVE_INFINITY : fromTo[1];
range.from = fromTo[0] == 0 ? Double.NEGATIVE_INFINITY : fromTo[0];
range.to = fromTo[1] == InternalIPv4Range.MAX_IP ? Double.POSITIVE_INFINITY : fromTo[1];
if (range.key == null) {
range.key = cidr;
}
Expand Down
Loading

0 comments on commit 7c104fd

Please sign in to comment.