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

feature: support ipv6 #5902

Merged
merged 9 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading