Skip to content

Commit

Permalink
feature: support ipv6 (#5902)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ifdevil authored Oct 9, 2023
1 parent bb617e2 commit df32ebe
Show file tree
Hide file tree
Showing 13 changed files with 349 additions and 25 deletions.
2 changes: 2 additions & 0 deletions changes/en-us/2.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The version is updated as follows:
- [[#4033](https://github.com/seata/seata/pull/4033)] add SQLServer support for Server DB storage mode
- [[#5717](https://github.com/seata/seata/pull/5717)] compatible with file.conf and registry.conf configurations in version 1.4.2 and below
- [[#5842](https://github.com/seata/seata/pull/5842)] adding metainfo to docker image
- [[#5902](https://github.com/seata/seata/pull/5902)] support IPv6
- [[#5907](https://github.com/seata/seata/pull/5907)] support polardb-x 2.0 in AT mode

### bugfix:
Expand Down Expand Up @@ -181,6 +182,7 @@ Thanks to these contributors for their code commits. Please report an unintended
- [robynron](https://github.com/robynron)
- [XQDD](https://github.com/XQDD)
- [Weelerer](https://github.com/Weelerer)
- [Ifdevil](https://github.com/Ifdevil)


Also, we receive many valuable issues, questions and advices from our community. Thanks for you all.
Expand Down
2 changes: 2 additions & 0 deletions changes/zh-cn/2.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单
- [[#4033](https://github.com/seata/seata/pull/4033)] 增加ServerDB存储模式的SQLServer支持
- [[#5717](https://github.com/seata/seata/pull/5717)] 兼容1.4.2及以下版本的file.conf/registry.conf配置
- [[#5842](https://github.com/seata/seata/pull/5842)] 构建docker 镜像时添加相关git信息,方便定位代码关系
- [[#5902](https://github.com/seata/seata/pull/5902)] 支持IPv6网络环境
- [[#5907](https://github.com/seata/seata/pull/5907)] 增加AT模式的PolarDB-X 2.0数据库支持

### bugfix:
Expand Down Expand Up @@ -181,6 +182,7 @@ Seata 是一款开源的分布式事务解决方案,提供高性能和简单
- [robynron](https://github.com/robynron)
- [XQDD](https://github.com/XQDD)
- [Weelerer](https://github.com/Weelerer)
- [Ifdevil](https://github.com/Ifdevil)

同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。

Expand Down
152 changes: 152 additions & 0 deletions common/src/main/java/io/seata/common/util/NetAddressValidatorUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright 1999-2019 Seata.io Group.
*
* Licensed 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 io.seata.common.util;

import java.util.regex.Pattern;

/**
* ipv4 ipv6 check util.
*
* @author Ifdevil
*/
public class NetAddressValidatorUtil {

private static final String PERCENT = "%";

private static final String DOUBLE_COLON = "::";

private static final String DOUBLE_COLON_FFFF = "::ffff:";

private static final String FE80 = "fe80:";

private static final int ZERO = 0;

private static final int SEVEN = 7;

private static final int FIVE = 5;

private static final Pattern IPV4_PATTERN = Pattern
.compile("^" + "(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)" + "(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}" + "$");

private static final Pattern IPV6_STD_PATTERN = Pattern
.compile("^" + "(?:[0-9a-fA-F]{1,4}:){7}" + "[0-9a-fA-F]{1,4}" + "$");

private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern
.compile("^" + "(" + "(?:[0-9A-Fa-f]{1,4}" + "(?::[0-9A-Fa-f]{1,4})*)?" + ")" + "::"

+ "(" + "(?:[0-9A-Fa-f]{1,4}" + "(?::[0-9A-Fa-f]{1,4})*)?" + ")" + "$");

private static final Pattern IPV6_MIXED_COMPRESSED_REGEX = Pattern.compile(
"^" + "(" + "(?:[0-9A-Fa-f]{1,4}" + "(?::[0-9A-Fa-f]{1,4})*)?" + ")" + "::" + "(" + "(?:[0-9A-Fa-f]{1,4}:"
+ "(?:[0-9A-Fa-f]{1,4}:)*)?" + ")" + "$");

private static final Pattern IPV6_MIXED_UNCOMPRESSED_REGEX = Pattern
.compile("^" + "(?:[0-9a-fA-F]{1,4}:){6}" + "$");


public static boolean isIPv4Address(final String input) {
return IPV4_PATTERN.matcher(input).matches();
}

public static boolean isIPv6Address(final String input) {
return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input) || isLinkLocalIPv6WithZoneIndex(input)
|| isIPv6IPv4MappedAddress(input) || isIPv6MixedAddress(input);
}

/**
* Check if the given address is a valid IPv6 address in the standard format
* The format is 'xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx'. Eight blocks of hexadecimal digits
* are required.
*
* @param input ip-address to check
* @return true if <code>input</code> is in correct IPv6 notation.
*/
public static boolean isIPv6StdAddress(final String input) {
return IPV6_STD_PATTERN.matcher(input).matches();
}

/**
* Check if the given address is a valid IPv6 address in the hex-compressed notation
* The format is 'xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx'. If all digits in a block are '0'
* the block can be left empty.
*
* @param input ip-address to check
* @return true if <code>input</code> is in correct IPv6 (hex-compressed) notation.
*/
public static boolean isIPv6HexCompressedAddress(final String input) {
return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches();
}

/**
* Check if the given address is a valid IPv6 address in the mixed-standard or mixed-compressed notation.
* IPV6 Mixed mode consists of two parts, the first 96 bits (up to 6 blocks of 4 hex digits) are IPv6
* the IPV6 part can be either compressed or uncompressed
* the second block is a full IPv4 address
* e.g. '0:0:0:0:0:0:172.12.55.18'
*
* @param input ip-address to check
* @return true if <code>input</code> is in correct IPv6 (mixed-standard or mixed-compressed) notation.
*/
public static boolean isIPv6MixedAddress(final String input) {
int splitIndex = input.lastIndexOf(':');
if (splitIndex == -1) {
return false;
}
//the last part is a ipv4 address
boolean ipv4PartValid = isIPv4Address(input.substring(splitIndex + 1));
String ipV6Part = input.substring(ZERO, splitIndex + 1);
if (DOUBLE_COLON.equals(ipV6Part)) {
return ipv4PartValid;
}
boolean ipV6UncompressedDetected = IPV6_MIXED_UNCOMPRESSED_REGEX.matcher(ipV6Part).matches();
boolean ipV6CompressedDetected = IPV6_MIXED_COMPRESSED_REGEX.matcher(ipV6Part).matches();
return ipv4PartValid && (ipV6UncompressedDetected || ipV6CompressedDetected);
}

/**
* Check if <code>input</code> is an IPv4 address mapped into a IPv6 address. These are
* starting with "::ffff:" followed by the IPv4 address in a dot-seperated notation.
* The format is '::ffff:d.d.d.d'
*
* @param input ip-address to check
* @return true if <code>input</code> is in correct IPv6 notation containing an IPv4 address
*/
public static boolean isIPv6IPv4MappedAddress(final String input) {
if (input.length() > SEVEN && input.substring(ZERO, SEVEN).equalsIgnoreCase(DOUBLE_COLON_FFFF)) {
String lowerPart = input.substring(SEVEN);
return isIPv4Address(lowerPart);
}
return false;
}

/**
* Check if <code>input</code> is a link local IPv6 address starting with "fe80:" and containing
* a zone index with "%xxx". The zone index will not be checked.
*
* @param input ip-address to check
* @return true if address part of <code>input</code> is in correct IPv6 notation.
*/
public static boolean isLinkLocalIPv6WithZoneIndex(String input) {
if (input.length() > FIVE && input.substring(ZERO, FIVE).equalsIgnoreCase(FE80)) {
int lastIndex = input.lastIndexOf(PERCENT);
if (lastIndex > ZERO && lastIndex < (input.length() - 1)) {
String ipPart = input.substring(ZERO, lastIndex);
return isIPv6StdAddress(ipPart) || isIPv6HexCompressedAddress(ipPart);
}
}
return false;
}
}
117 changes: 102 additions & 15 deletions common/src/main/java/io/seata/common/util/NetUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,22 @@
*/
package io.seata.common.util;

import io.seata.common.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.regex.Pattern;
import java.util.LinkedHashSet;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The type Net util.
Expand All @@ -34,13 +39,25 @@
*/
public class NetUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(NetUtil.class);
private static final String LOCALHOST = "127.0.0.1";

public static final boolean PREFER_IPV6_ADDRESSES = Boolean.parseBoolean(
System.getProperty("java.net.preferIPv6Addresses"));

private static final String LOCALHOST = "127.0.0.1";
private static final String ANY_HOST = "0.0.0.0";

public static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";
public static final String LOCALHOST_SHORT_IPV6 = "::1";
public static final String ANY_HOST_IPV6 = "0:0:0:0:0:0:0:0";
public static final String ANY_HOST_SHORT_IPV6 = "::";

private static volatile InetAddress LOCAL_ADDRESS = null;

private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
private static final Set<String> FORBIDDEN_HOSTS = Collections.unmodifiableSet(
new LinkedHashSet<>(Arrays.asList(
LOCALHOST, ANY_HOST,
LOCALHOST_IPV6, LOCALHOST_SHORT_IPV6,
ANY_HOST_IPV6,ANY_HOST_SHORT_IPV6)));

/**
* To string address string.
Expand Down Expand Up @@ -83,19 +100,40 @@ public static String toStringAddress(InetSocketAddress address) {
* @return the inet socket address
*/
public static InetSocketAddress toInetSocketAddress(String address) {
int i = address.indexOf(':');
String[] ipPortStr = splitIPPortStr(address);
String host;
int port;
if (i > -1) {
host = address.substring(0, i);
port = Integer.parseInt(address.substring(i + 1));
if (null != ipPortStr) {
host = ipPortStr[0];
port = Integer.parseInt(ipPortStr[1]);
} else {
host = address;
port = 0;
}
return new InetSocketAddress(host, port);
}

public static String[] splitIPPortStr(String address) {
if (StringUtils.isBlank(address)) {
throw new IllegalArgumentException("ip and port string cannot be empty!");
}
if (address.charAt(0) == '[') {
address = removeBrackets(address);
}
String[] serverAddArr = null;
int i = address.lastIndexOf(Constants.IP_PORT_SPLIT_CHAR);
if (i > -1) {
serverAddArr = new String[2];
String hostAddress = address.substring(0,i);
if (hostAddress.contains("%")) {
hostAddress = hostAddress.substring(0, hostAddress.indexOf("%"));
}
serverAddArr[0] = hostAddress;
serverAddArr[1] = address.substring(i + 1);
}
return serverAddArr;
}

/**
* To long long.
*
Expand All @@ -121,7 +159,23 @@ public static long toLong(String address) {
*/
public static String getLocalIp(String... preferredNetworks) {
InetAddress address = getLocalAddress(preferredNetworks);
return address == null ? LOCALHOST : address.getHostAddress();
if (null != address) {
String hostAddress = address.getHostAddress();
if (address instanceof Inet6Address) {
if (hostAddress.contains("%")) {
hostAddress = hostAddress.substring(0, hostAddress.indexOf("%"));
}
}
return hostAddress;
}
return localIP();
}

public static String localIP() {
if (PREFER_IPV6_ADDRESSES) {
return LOCALHOST_IPV6;
}
return LOCALHOST;
}

/**
Expand Down Expand Up @@ -224,7 +278,21 @@ private static boolean isValidAddress(InetAddress address) {
if (address == null || address.isLoopbackAddress()) {
return false;
}
return isValidIp(address.getHostAddress(), false);
String hostAddress = address.getHostAddress();
if (address instanceof Inet6Address) {
if (!PREFER_IPV6_ADDRESSES) {
return false;
}
if (address.isAnyLocalAddress() // filter ::/128
|| address.isLinkLocalAddress() //filter fe80::/10
|| address.isSiteLocalAddress()// filter fec0::/10
|| isUniqueLocalAddress(address)) //filter fd00::/8
{
return false;
}
return isValidIPv6(hostAddress);
}
return !FORBIDDEN_HOSTS.contains(hostAddress) && isValidIPv4(hostAddress);
}

/**
Expand All @@ -240,11 +308,10 @@ public static boolean isValidIp(String ip, boolean validLocalAndAny) {
}
ip = convertIpIfNecessary(ip);
if (validLocalAndAny) {
return IP_PATTERN.matcher(ip).matches();
return isValidIPv4(ip) || isValidIPv6(ip);
} else {
return !ANY_HOST.equals(ip) && !LOCALHOST.equals(ip) && IP_PATTERN.matcher(ip).matches();
return !FORBIDDEN_HOSTS.contains(ip) && (isValidIPv4(ip) || isValidIPv6(ip));
}

}

/**
Expand All @@ -254,7 +321,7 @@ public static boolean isValidIp(String ip, boolean validLocalAndAny) {
* @return java.lang.String
*/
private static String convertIpIfNecessary(String ip) {
if (IP_PATTERN.matcher(ip).matches()) {
if (isValidIPv4(ip) || isValidIPv6(ip)) {
return ip;
} else {
try {
Expand All @@ -264,4 +331,24 @@ private static String convertIpIfNecessary(String ip) {
}
}
}

public static boolean isValidIPv4(String ip) {
return NetAddressValidatorUtil.isIPv4Address(ip);
}

public static boolean isValidIPv6(String ip) {
return NetAddressValidatorUtil.isIPv6Address(ip);
}

private static boolean isUniqueLocalAddress(InetAddress address) {
byte[] ip = address.getAddress();
return (ip[0] & 0xff) == 0xfd;
}

private static String removeBrackets(String str) {
if (StringUtils.isBlank(str)) {
return "";
}
return str.replaceAll("[\\[\\]]", "");
}
}
Loading

0 comments on commit df32ebe

Please sign in to comment.