Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue #3289. enhance tagRoute: support ip expression match #3578

Merged
merged 12 commits into from
Mar 7, 2019
5 changes: 5 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,8 @@ This product contains a modified portion of 'Netty', an event-driven asynchronou
* io.netty.util.Timeout
* io.netty.util.HashedWheelTimer

For the org.apache.dubbo.common.utils.CIDRUtils :

This product contains a modified portion of 'edazdarevic.commons.net.CIDRUtils',
under a "MIT License" license, see https://github.com/edazdarevic/CIDRUtils/blob/master/CIDRUtils.java

Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private synchronized void init(String ruleKey) {
String routerKey = ruleKey + RULE_SUFFIX;
configuration.addListener(routerKey, this);
String rule = configuration.getConfig(routerKey);
if (rule != null) {
if (StringUtils.isNotEmpty(rule)) {
this.process(new ConfigChangeEvent(routerKey, rule));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.configcenter.ConfigChangeEvent;
import org.apache.dubbo.configcenter.ConfigChangeType;
Expand All @@ -34,6 +35,7 @@
import org.apache.dubbo.rpc.cluster.router.tag.model.TagRouterRule;
import org.apache.dubbo.rpc.cluster.router.tag.model.TagRuleParser;

import java.net.UnknownHostException;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -143,10 +145,10 @@ public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation

/**
* If there's no dynamic tag rule being set, use static tag in URL.
*
* <p>
* A typical scenario is a Consumer using version 2.7.x calls Providers using version 2.6.x or lower,
* the Consumer should always respect the tag in provider URL regardless of whether a dynamic tag rule has been set to it or not.
*
* <p>
* TODO, to guarantee consistent behavior of interoperability between 2.6- and 2.7+, this method should has the same logic with the TagRouter in 2.6.x.
*
* @param invokers
Expand Down Expand Up @@ -199,11 +201,26 @@ private <T> List<Invoker<T>> filterInvoker(List<Invoker<T>> invokers, Predicate<
}

private boolean addressMatches(URL url, List<String> addresses) {
return addresses != null && addresses.contains(url.getAddress());
return addresses != null && checkAddressMatch(addresses, url.getHost(), url.getPort());
}

private boolean addressNotMatches(URL url, List<String> addresses) {
return addresses == null || !addresses.contains(url.getAddress());
return addresses == null || !checkAddressMatch(addresses, url.getHost(), url.getPort());
}

private boolean checkAddressMatch(List<String> addresses, String host, int port) {
for (String address : addresses) {
try {
if (NetUtils.matchIpExpression(address, host, port)) {
return true;
}
} catch (UnknownHostException e) {
logger.error("The format of ip address is invalid in tag route. Address :" + address, e);
} catch (Exception e) {
logger.error("The format of ip address is invalid in tag route. Address :" + address, e);
}
}
return false;
}

public void setApplication(String app) {
Expand Down Expand Up @@ -235,7 +252,7 @@ public <T> void notify(List<Invoker<T>> invokers) {
configuration.addListener(key, this);
application = providerApplication;
String rawRule = configuration.getConfig(key);
if (rawRule != null) {
if (StringUtils.isNotEmpty(rawRule)) {
this.process(new ConfigChangeEvent(key, rawRule));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* The MIT License
*
* Copyright (c) 2013 Edin Dazdarevic (edin.dazdarevic@gmail.com)
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
**/
package org.apache.dubbo.common.utils;

import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
* A class that enables to get an IP range from CIDR specification. It supports
* both IPv4 and IPv6.
* <p>
* From https://github.com/edazdarevic/CIDRUtils/blob/master/CIDRUtils.java
*/
public class CIDRUtils {
private final String cidr;

private InetAddress inetAddress;
private InetAddress startAddress;
private InetAddress endAddress;
private final int prefixLength;


public CIDRUtils(String cidr) throws UnknownHostException {

this.cidr = cidr;

/* split CIDR to address and prefix part */
if (this.cidr.contains("/")) {
int index = this.cidr.indexOf("/");
String addressPart = this.cidr.substring(0, index);
String networkPart = this.cidr.substring(index + 1);

inetAddress = InetAddress.getByName(addressPart);
prefixLength = Integer.parseInt(networkPart);

calculate();
} else {
throw new IllegalArgumentException("not an valid CIDR format!");
}
}


private void calculate() throws UnknownHostException {

ByteBuffer maskBuffer;
int targetSize;
if (inetAddress.getAddress().length == 4) {
maskBuffer =
ByteBuffer
.allocate(4)
.putInt(-1);
targetSize = 4;
} else {
maskBuffer = ByteBuffer.allocate(16)
.putLong(-1L)
.putLong(-1L);
targetSize = 16;
}

BigInteger mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(prefixLength);

ByteBuffer buffer = ByteBuffer.wrap(inetAddress.getAddress());
BigInteger ipVal = new BigInteger(1, buffer.array());

BigInteger startIp = ipVal.and(mask);
BigInteger endIp = startIp.add(mask.not());

byte[] startIpArr = toBytes(startIp.toByteArray(), targetSize);
byte[] endIpArr = toBytes(endIp.toByteArray(), targetSize);

this.startAddress = InetAddress.getByAddress(startIpArr);
this.endAddress = InetAddress.getByAddress(endIpArr);

}

private byte[] toBytes(byte[] array, int targetSize) {
int counter = 0;
List<Byte> newArr = new ArrayList<Byte>();
while (counter < targetSize && (array.length - 1 - counter >= 0)) {
newArr.add(0, array[array.length - 1 - counter]);
counter++;
}

int size = newArr.size();
for (int i = 0; i < (targetSize - size); i++) {

newArr.add(0, (byte) 0);
}

byte[] ret = new byte[newArr.size()];
for (int i = 0; i < newArr.size(); i++) {
ret[i] = newArr.get(i);
}
return ret;
}

public String getNetworkAddress() {

return this.startAddress.getHostAddress();
}

public String getBroadcastAddress() {
return this.endAddress.getHostAddress();
}

public boolean isInRange(String ipAddress) throws UnknownHostException {
InetAddress address = InetAddress.getByName(ipAddress);
BigInteger start = new BigInteger(1, this.startAddress.getAddress());
BigInteger end = new BigInteger(1, this.endAddress.getAddress());
BigInteger target = new BigInteger(1, address.getAddress());

int st = start.compareTo(target);
int te = target.compareTo(end);

return (st == -1 || st == 0) && (te == -1 || te == 0);
}
}
137 changes: 135 additions & 2 deletions dubbo-common/src/main/java/org/apache/dubbo/common/utils/NetUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public class NetUtils {
private static final Map<String, String> hostNameCache = new LRUCache<>(1000);
private static volatile InetAddress LOCAL_ADDRESS = null;

private static final String SPLIT_IPV4_CHARECTER = "\\.";
private static final String SPLIT_IPV6_CHARECTER = ":";

public static int getRandomPort() {
return RND_PORT_START + ThreadLocalRandom.current().nextInt(RND_PORT_RANGE);
}
Expand Down Expand Up @@ -340,13 +343,13 @@ public static String toURL(String protocol, String host, int port, String path)
return sb.toString();
}

public static void joinMulticastGroup (MulticastSocket multicastSocket, InetAddress multicastAddress) throws IOException {
public static void joinMulticastGroup(MulticastSocket multicastSocket, InetAddress multicastAddress) throws IOException {
setInterface(multicastSocket, multicastAddress instanceof Inet6Address);
multicastSocket.setLoopbackMode(false);
multicastSocket.joinGroup(multicastAddress);
}

public static void setInterface (MulticastSocket multicastSocket, boolean preferIpv6) throws IOException{
public static void setInterface(MulticastSocket multicastSocket, boolean preferIpv6) throws IOException {
boolean interfaceSet = false;
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
Expand All @@ -370,4 +373,134 @@ public static void setInterface (MulticastSocket multicastSocket, boolean prefer
}
}

public static boolean matchIpExpression(String pattern, String host, int port) throws UnknownHostException {

// if the pattern is subnet format, it will not be allowed to config port param in pattern.
if (pattern.contains("/")) {
CIDRUtils utils = new CIDRUtils(pattern);
return utils.isInRange(host);
}


return matchIpRange(pattern, host, port);
}

/**
* @param pattern
* @param host
* @param port
* @return
* @throws UnknownHostException
*/
public static boolean matchIpRange(String pattern, String host, int port) throws UnknownHostException {
if (pattern == null || host == null) {
throw new IllegalArgumentException("Illegal Argument pattern or hostName. Pattern:" + pattern + ", Host:" + host);
}
pattern = pattern.trim();
if (pattern.equals("*.*.*.*") || pattern.equals("*")) {
return true;
}

InetAddress inetAddress = InetAddress.getByName(host);
boolean isIpv4 = isValidV4Address(inetAddress) ? true : false;
String[] hostAndPort = getPatternHostAndPort(pattern, isIpv4);
if (hostAndPort[1] != null && !hostAndPort[1].equals(String.valueOf(port))) {
return false;
}
pattern = hostAndPort[0];

String splitCharacter = SPLIT_IPV4_CHARECTER;
if (!isIpv4) {
splitCharacter = SPLIT_IPV6_CHARECTER;
}
String[] mask = pattern.split(splitCharacter);
//check format of pattern
checkHostPattern(pattern, mask, isIpv4);

host = inetAddress.getHostAddress();

String[] ip_address = host.split(splitCharacter);
if (pattern.equals(host)) {
return true;
}
// short name condition
if (!ipPatternContainExpression(pattern)) {
InetAddress patternAddress = InetAddress.getByName(pattern);
if (patternAddress.getHostAddress().equals(host)) {
return true;
} else {
return false;
}
}
for (int i = 0; i < mask.length; i++) {
if (mask[i].equals("*") || mask[i].equals(ip_address[i])) {
continue;
} else if (mask[i].contains("-")) {
String[] rangeNumStrs = mask[i].split("-");
if (rangeNumStrs.length != 2) {
throw new IllegalArgumentException("There is wrong format of ip Address: " + mask[i]);
}
Integer min = getNumOfIpSegment(rangeNumStrs[0], isIpv4);
Integer max = getNumOfIpSegment(rangeNumStrs[1], isIpv4);
Integer ip = getNumOfIpSegment(ip_address[i], isIpv4);
if (ip < min || ip > max) {
return false;
}
} else if ("0".equals(ip_address[i]) && ("0".equals(mask[i]) || "00".equals(mask[i]) || "000".equals(mask[i]) || "0000".equals(mask[i]))) {
continue;
} else if (!mask[i].equals(ip_address[i])) {
return false;
}
}
return true;
}

private static boolean ipPatternContainExpression(String pattern) {
return pattern.contains("*") || pattern.contains("-");
}

private static void checkHostPattern(String pattern, String[] mask, boolean isIpv4) {
if (!isIpv4) {
if (mask.length != 8 && ipPatternContainExpression(pattern)) {
throw new IllegalArgumentException("If you config ip expression that contains '*' or '-', please fill qulified ip pattern like 234e:0:4567:0:0:0:3d:*. ");
}
if (mask.length != 8 && !pattern.contains("::")) {
throw new IllegalArgumentException("The host is ipv6, but the pattern is not ipv6 pattern : " + pattern);
}
} else {
if (mask.length != 4) {
throw new IllegalArgumentException("The host is ipv4, but the pattern is not ipv4 pattern : " + pattern);
}
}
}

private static String[] getPatternHostAndPort(String pattern, boolean isIpv4) {
String[] result = new String[2];
if (pattern.startsWith("[") && pattern.contains("]:")) {
int end = pattern.indexOf("]:");
result[0] = pattern.substring(1, end);
result[1] = pattern.substring(end + 2);
return result;
} else if (pattern.startsWith("[") && pattern.endsWith("]")) {
result[0] = pattern.substring(1, pattern.length() - 1);
result[1] = null;
return result;
} else if (isIpv4 && pattern.contains(":")) {
int end = pattern.indexOf(":");
result[0] = pattern.substring(0, end);
result[1] = pattern.substring(end + 1);
return result;
} else {
result[0] = pattern;
return result;
}
}

private static Integer getNumOfIpSegment(String ipSegment, boolean isIpv4) {
if (isIpv4) {
return Integer.parseInt(ipSegment);
}
return Integer.parseInt(ipSegment, 16);
}

}
Loading