8224159: JDWP IPv6 scope support
Reviewed-by: sspitsyn, cjplummer
This commit is contained in:
parent
a3ee39cb81
commit
3f93ec68ee
@ -38,7 +38,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBDT_SOCKET, \
|
||||
$(call SET_SHARED_LIBRARY_ORIGIN), \
|
||||
LIBS_linux := -lpthread, \
|
||||
LIBS_solaris := -lnsl -lsocket, \
|
||||
LIBS_windows := $(JDKLIB_LIBS) ws2_32.lib, \
|
||||
LIBS_windows := $(JDKLIB_LIBS) ws2_32.lib iphlpapi.lib, \
|
||||
))
|
||||
|
||||
$(BUILD_LIBDT_SOCKET): $(call FindLib, java.base, java)
|
||||
|
@ -35,9 +35,11 @@
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <net/if.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -266,17 +268,102 @@ static unsigned short getPort(struct sockaddr *sa)
|
||||
: (((struct sockaddr_in6*)sa)->sin6_port));
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses scope id.
|
||||
* Scope id is ulong on Windows, uint32 on unix, so returns long which can be cast to uint32.
|
||||
* On error sets last error and returns -1.
|
||||
*/
|
||||
static long parseScopeId(const char *str) {
|
||||
// try to handle scope as interface name
|
||||
unsigned long scopeId = if_nametoindex(str);
|
||||
if (scopeId == 0) {
|
||||
// try to parse integer value
|
||||
char *end;
|
||||
scopeId = strtoul(str, &end, 10);
|
||||
if (*end != '\0') {
|
||||
setLastError(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "failed to parse scope");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
// ensure parsed value is in uint32 range
|
||||
if (scopeId > 0xFFFFFFFF) {
|
||||
setLastError(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "scope is out of range");
|
||||
return -1;
|
||||
}
|
||||
return (long)scopeId;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrapper for dbgsysGetAddrInfo (getaddrinfo).
|
||||
* Handles enclosing square brackets and scopes.
|
||||
*/
|
||||
static jdwpTransportError
|
||||
getAddrInfo(const char *hostname, size_t hostnameLen,
|
||||
const char *service,
|
||||
const struct addrinfo *hints,
|
||||
struct addrinfo **result)
|
||||
{
|
||||
int err = 0;
|
||||
char *buffer = NULL;
|
||||
long scopeId = 0;
|
||||
|
||||
if (hostname != NULL) {
|
||||
char *scope = NULL;
|
||||
// skip surrounding
|
||||
if (hostnameLen > 2 && hostname[0] == '[' && hostname[hostnameLen - 1] == ']') {
|
||||
hostname++;
|
||||
hostnameLen -= 2;
|
||||
}
|
||||
buffer = (*callback->alloc)((int)hostnameLen + 1);
|
||||
if (buffer == NULL) {
|
||||
RETURN_ERROR(JDWPTRANSPORT_ERROR_OUT_OF_MEMORY, "out of memory");
|
||||
}
|
||||
memcpy(buffer, hostname, hostnameLen);
|
||||
buffer[hostnameLen] = '\0';
|
||||
|
||||
scope = strchr(buffer, '%');
|
||||
if (scope != NULL) {
|
||||
// drop scope from the address
|
||||
*scope = '\0';
|
||||
// and parse the value
|
||||
scopeId = parseScopeId(scope + 1);
|
||||
if (scopeId < 0) {
|
||||
(*callback->free)(buffer);
|
||||
return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = dbgsysGetAddrInfo(buffer, service, hints, result);
|
||||
|
||||
if (buffer != NULL) {
|
||||
(*callback->free)(buffer);
|
||||
}
|
||||
if (err != 0) {
|
||||
setLastError(err, "getaddrinfo: failed to parse address");
|
||||
return JDWPTRANSPORT_ERROR_IO_ERROR;
|
||||
}
|
||||
|
||||
if (scopeId > 0) {
|
||||
if ((*result)->ai_family != AF_INET6) {
|
||||
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "IPv4 address cannot contain scope");
|
||||
}
|
||||
|
||||
((struct sockaddr_in6 *)((*result)->ai_addr))->sin6_scope_id = (uint32_t)scopeId;
|
||||
}
|
||||
|
||||
return JDWPTRANSPORT_ERROR_NONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Result must be released with dbgsysFreeAddrInfo.
|
||||
*/
|
||||
static jdwpTransportError
|
||||
parseAddress(const char *address, struct addrinfo **result) {
|
||||
const char *colon;
|
||||
size_t hostLen;
|
||||
char *host = NULL;
|
||||
size_t hostnameLen;
|
||||
const char *port;
|
||||
struct addrinfo hints;
|
||||
int res;
|
||||
|
||||
*result = NULL;
|
||||
|
||||
@ -295,39 +382,21 @@ parseAddress(const char *address, struct addrinfo **result) {
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = AI_NUMERICSERV; // port must be a number
|
||||
|
||||
hostLen = (colon == NULL ? 0 : colon - address);
|
||||
if (hostLen == 0) {
|
||||
hostnameLen = (colon == NULL ? 0 : colon - address);
|
||||
if (hostnameLen == 0) {
|
||||
/* no hostname - use localhost address (pass NULL to getaddrinfo) */
|
||||
} else if (*address == '*' && hostLen == 1) {
|
||||
address = NULL;
|
||||
} else if (*address == '*' && hostnameLen == 1) {
|
||||
/* *:port - listen on all interfaces
|
||||
* use IPv6 socket (to accept IPv6 and mapped IPv4),
|
||||
* pass hostname == NULL to getaddrinfo.
|
||||
*/
|
||||
hints.ai_family = allowOnlyIPv4 ? AF_INET : AF_INET6;
|
||||
hints.ai_flags |= AI_PASSIVE | (allowOnlyIPv4 ? 0 : AI_V4MAPPED | AI_ALL);
|
||||
} else {
|
||||
if (address[0] == '[' && colon[-1] == ']') {
|
||||
address++;
|
||||
hostLen -= 2;
|
||||
}
|
||||
host = (*callback->alloc)((int)hostLen + 1);
|
||||
if (host == NULL) {
|
||||
RETURN_ERROR(JDWPTRANSPORT_ERROR_OUT_OF_MEMORY, "out of memory");
|
||||
}
|
||||
strncpy(host, address, hostLen);
|
||||
host[hostLen] = '\0';
|
||||
address = NULL;
|
||||
}
|
||||
|
||||
res = dbgsysGetAddrInfo(host, port, &hints, result);
|
||||
if (host != NULL) {
|
||||
(*callback->free)(host);
|
||||
}
|
||||
if (res != 0) {
|
||||
setLastError(res, "getaddrinfo: unknown host");
|
||||
return JDWPTRANSPORT_ERROR_IO_ERROR;
|
||||
}
|
||||
|
||||
return JDWPTRANSPORT_ERROR_NONE;
|
||||
return getAddrInfo(address, hostnameLen, port, &hints, result);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -352,7 +421,7 @@ static jdwpTransportError
|
||||
parseAllowedAddr(const char *buffer, struct in6_addr *result, int *isIPv4) {
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *addrInfo = NULL;
|
||||
int err;
|
||||
jdwpTransportError err;
|
||||
|
||||
/*
|
||||
* To parse both IPv4 and IPv6 need to specify AF_UNSPEC family
|
||||
@ -364,11 +433,10 @@ parseAllowedAddr(const char *buffer, struct in6_addr *result, int *isIPv4) {
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = AI_NUMERICHOST; // only numeric addresses, no resolution
|
||||
|
||||
err = dbgsysGetAddrInfo(buffer, NULL, &hints, &addrInfo);
|
||||
err = getAddrInfo(buffer, strlen(buffer), NULL, &hints, &addrInfo);
|
||||
|
||||
if (err != 0) {
|
||||
setLastError(err, "getaddrinfo: failed to parse address");
|
||||
return JDWPTRANSPORT_ERROR_IO_ERROR;
|
||||
if (err != JDWPTRANSPORT_ERROR_NONE) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (addrInfo->ai_family == AF_INET6) {
|
||||
@ -844,6 +912,7 @@ static jdwpTransportError connectToAddr(struct addrinfo *ai, jlong timeout, int
|
||||
}
|
||||
|
||||
err = dbgsysConnect(socketFD, ai->ai_addr, (socklen_t)ai->ai_addrlen);
|
||||
|
||||
if (err == DBG_EINPROGRESS && timeout > 0) {
|
||||
err = dbgsysFinishConnect(socketFD, (long)timeout);
|
||||
|
||||
|
@ -32,6 +32,7 @@ import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
@ -41,7 +42,6 @@ import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/*
|
||||
* @test
|
||||
@ -54,24 +54,36 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
public class JdwpAttachTest {
|
||||
|
||||
private static final boolean IsWindows = System.getProperty("os.name").toLowerCase().contains("windows");
|
||||
|
||||
// Set to true to perform testing of attach from wrong address (expected to fail).
|
||||
// It's off by default as it caused significant test time increase\
|
||||
// (tests <number_of_addresses> * <number_of_addresses> cases, each case fails by timeout).
|
||||
private static boolean testFailedAttach = false;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
List<InetAddress> addresses = getAddresses();
|
||||
|
||||
boolean ipv4EnclosedTested = false;
|
||||
boolean ipv6EnclosedTested = false;
|
||||
for (InetAddress addr: addresses) {
|
||||
// also test that addresses enclosed in square brackets are supported
|
||||
attachTest(addr.getHostAddress(), addr.getHostAddress());
|
||||
if (testFailedAttach) {
|
||||
for (InetAddress connectAddr : addresses) {
|
||||
attachTest(addr.getHostAddress(), connectAddr.getHostAddress(), addr.equals(connectAddr));
|
||||
}
|
||||
} else {
|
||||
attachTest(addr.getHostAddress(), addr.getHostAddress(), true);
|
||||
}
|
||||
// listening on "*" should accept connections from all addresses
|
||||
attachTest("*", addr.getHostAddress());
|
||||
attachTest("*", addr.getHostAddress(), true);
|
||||
|
||||
// test that addresses enclosed in square brackets are supported.
|
||||
// also test that addresses enclosed in square brackets are supported.
|
||||
if (addr instanceof Inet4Address && !ipv4EnclosedTested) {
|
||||
attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]");
|
||||
attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]", true);
|
||||
ipv4EnclosedTested = true;
|
||||
}
|
||||
if (addr instanceof Inet6Address && !ipv6EnclosedTested) {
|
||||
attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]");
|
||||
attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]", true);
|
||||
ipv6EnclosedTested = true;
|
||||
}
|
||||
}
|
||||
@ -80,13 +92,14 @@ public class JdwpAttachTest {
|
||||
// we should be able to attach to both IPv4 and IPv6 addresses (127.0.0.1 & ::1)
|
||||
InetAddress localAddresses[] = InetAddress.getAllByName("localhost");
|
||||
for (int i = 0; i < localAddresses.length; i++) {
|
||||
attachTest(localAddresses[i].getHostAddress(), "");
|
||||
attachTest(localAddresses[i].getHostAddress(), "", true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void attachTest(String listenAddress, String connectAddresses)
|
||||
private static void attachTest(String listenAddress, String connectAddress, boolean expectedResult)
|
||||
throws Exception {
|
||||
log("Starting listening at " + listenAddress);
|
||||
log("\nTest: listen on '" + listenAddress + "', attach to '" + connectAddress + "'");
|
||||
log(" Starting listening at " + listenAddress);
|
||||
ListeningConnector connector = getListenConnector();
|
||||
Map<String, Connector.Argument> args = connector.defaultArguments();
|
||||
setConnectorArg(args, "localAddress", listenAddress);
|
||||
@ -100,31 +113,48 @@ public class JdwpAttachTest {
|
||||
throw new RuntimeException("values from connector.startListening (" + actualPort
|
||||
+ " is not equal to values from arguments (" + port + ")");
|
||||
}
|
||||
log("Listening port: " + port);
|
||||
log(" Listening port: " + port);
|
||||
|
||||
log("Attaching from " + connectAddresses);
|
||||
log(" Attaching from " + connectAddress);
|
||||
try {
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
executor.submit((Callable<Exception>)() -> {
|
||||
VirtualMachine vm = connector.accept(args);
|
||||
log("ACCEPTED.");
|
||||
vm.dispose();
|
||||
return null;
|
||||
});
|
||||
executor.shutdown();
|
||||
|
||||
LingeredApp debuggee = LingeredApp.startApp(
|
||||
Arrays.asList("-agentlib:jdwp=transport=dt_socket"
|
||||
+",address=" + connectAddresses + ":" + port
|
||||
+ ",server=n,suspend=n"));
|
||||
debuggee.stopApp();
|
||||
|
||||
executor.awaitTermination(20, TimeUnit.SECONDS);
|
||||
try {
|
||||
LingeredApp debuggee = LingeredApp.startApp(
|
||||
Arrays.asList("-agentlib:jdwp=transport=dt_socket"
|
||||
+ ",address=" + connectAddress + ":" + port
|
||||
+ ",server=n,suspend=n"
|
||||
// if failure is expected set small timeout (default is 20 sec)
|
||||
+ (!expectedResult ? ",timeout=1000" : "")));
|
||||
debuggee.stopApp();
|
||||
if (expectedResult) {
|
||||
log("OK: attached as expected");
|
||||
} else {
|
||||
throw new RuntimeException("ERROR: LingeredApp.startApp was able to attach");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (expectedResult) {
|
||||
throw new RuntimeException("ERROR: LingeredApp.startApp was able to attach");
|
||||
} else {
|
||||
log("OK: failed to attach as expected");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
connector.stopListening(args);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addAddr(List<InetAddress> list, InetAddress addr) {
|
||||
log(" - (" + addr.getClass().getSimpleName() + ") " + addr.getHostAddress());
|
||||
list.add(addr);
|
||||
}
|
||||
|
||||
private static List<InetAddress> getAddresses() {
|
||||
List<InetAddress> result = new LinkedList<>();
|
||||
try {
|
||||
@ -136,17 +166,37 @@ public class JdwpAttachTest {
|
||||
Enumeration<InetAddress> addresses = iface.getInetAddresses();
|
||||
while (addresses.hasMoreElements()) {
|
||||
InetAddress addr = addresses.nextElement();
|
||||
// Java reports link local addresses with named scope,
|
||||
// but Windows sockets routines support only numeric scope id.
|
||||
// skip such addresses.
|
||||
// Java reports link local addresses with symbolic scope,
|
||||
// but on Windows java.net.NetworkInterface generates its own scope names
|
||||
// which are incompatible with native Windows routines.
|
||||
// So on Windows test only addresses with numeric scope.
|
||||
// On other platforms test both symbolic and numeric scopes.
|
||||
if (addr instanceof Inet6Address) {
|
||||
Inet6Address addr6 = (Inet6Address)addr;
|
||||
if (addr6.getScopedInterface() != null) {
|
||||
continue;
|
||||
NetworkInterface scopeIface = addr6.getScopedInterface();
|
||||
if (scopeIface != null && scopeIface.getName() != null) {
|
||||
// On some test machines VPN creates link local addresses
|
||||
// which we cannot connect to.
|
||||
// Skip them.
|
||||
if (scopeIface.isPointToPoint()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// the same address with numeric scope
|
||||
addAddr(result, Inet6Address.getByAddress(null, addr6.getAddress(), addr6.getScopeId()));
|
||||
} catch (UnknownHostException e) {
|
||||
// cannot happen!
|
||||
throw new RuntimeException("Unexpected", e);
|
||||
}
|
||||
|
||||
if (IsWindows) {
|
||||
// don't add addresses with symbolic scope
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
log(" - (" + addr.getClass().getSimpleName() + ") " + addr.getHostAddress());
|
||||
result.add(addr);
|
||||
addAddr(result, addr);
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
@ -184,8 +234,11 @@ public class JdwpAttachTest {
|
||||
arg.setValue(value);
|
||||
}
|
||||
|
||||
private static long startTime = System.currentTimeMillis();
|
||||
|
||||
private static void log(Object o) {
|
||||
System.out.println(String.valueOf(o));
|
||||
long time = System.currentTimeMillis() - startTime;
|
||||
System.out.println(String.format("[%7.3f] %s", (time / 1000f), String.valueOf(o)));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
@ -51,6 +52,8 @@ import java.util.Map;
|
||||
*/
|
||||
public class JdwpListenTest {
|
||||
|
||||
private static final boolean IsWindows = System.getProperty("os.name").toLowerCase().contains("windows");
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
List<InetAddress> addresses = getAddresses();
|
||||
|
||||
@ -60,6 +63,8 @@ public class JdwpListenTest {
|
||||
for (InetAddress attach: addresses) {
|
||||
// can connect only from the same address
|
||||
// IPv6 cannot connect to IPv4 (::1 to 127.0.0.1) and vice versa.
|
||||
// Note: for IPv6 addresses equals() does not compare scopes
|
||||
// (so addresses with symbolic and numeric scopes are equals).
|
||||
listenTest(listen.getHostAddress(), attach.getHostAddress(), attach.equals(listen));
|
||||
}
|
||||
// test that addresses enclosed in square brackets are supported.
|
||||
@ -80,6 +85,8 @@ public class JdwpListenTest {
|
||||
|
||||
private static void listenTest(String listenAddress, String connectAddress, boolean expectedResult)
|
||||
throws IOException {
|
||||
log("\nTest: listen at " + listenAddress + ", attaching from " + connectAddress
|
||||
+ ", expected: " + (expectedResult ? "SUCCESS" : "FAILURE"));
|
||||
log("Starting listening debuggee at " + listenAddress);
|
||||
try (Debuggee debuggee = Debuggee.launcher("HelloWorld").setAddress(listenAddress + ":0").launch()) {
|
||||
log("Debuggee is listening on " + listenAddress + ":" + debuggee.getAddress());
|
||||
@ -98,6 +105,11 @@ public class JdwpListenTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addAddr(List<InetAddress> list, InetAddress addr) {
|
||||
log(" - (" + addr.getClass().getSimpleName() + ") " + addr.getHostAddress());
|
||||
list.add(addr);
|
||||
}
|
||||
|
||||
private static List<InetAddress> getAddresses() {
|
||||
List<InetAddress> result = new LinkedList<>();
|
||||
try {
|
||||
@ -109,17 +121,37 @@ public class JdwpListenTest {
|
||||
Enumeration<InetAddress> addresses = iface.getInetAddresses();
|
||||
while (addresses.hasMoreElements()) {
|
||||
InetAddress addr = addresses.nextElement();
|
||||
// Java reports link local addresses with named scope,
|
||||
// but Windows sockets routines support only numeric scope id.
|
||||
// skip such addresses.
|
||||
// Java reports link local addresses with symbolic scope,
|
||||
// but on Windows java.net.NetworkInterface generates its own scope names
|
||||
// which are incompatible with native Windows routines.
|
||||
// So on Windows test only addresses with numeric scope.
|
||||
// On other platforms test both symbolic and numeric scopes.
|
||||
if (addr instanceof Inet6Address) {
|
||||
Inet6Address addr6 = (Inet6Address)addr;
|
||||
if (addr6.getScopedInterface() != null) {
|
||||
continue;
|
||||
NetworkInterface scopeIface = addr6.getScopedInterface();
|
||||
if (scopeIface != null && scopeIface.getName() != null) {
|
||||
// On some test machines VPN creates link local addresses
|
||||
// which we cannot connect to.
|
||||
// Skip them.
|
||||
if (scopeIface.isPointToPoint()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// the same address with numeric scope
|
||||
addAddr(result, Inet6Address.getByAddress(null, addr6.getAddress(), addr6.getScopeId()));
|
||||
} catch (UnknownHostException e) {
|
||||
// cannot happen!
|
||||
throw new RuntimeException("Unexpected", e);
|
||||
}
|
||||
|
||||
if (IsWindows) {
|
||||
// don't add addresses with symbolic scope
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
log(" - (" + addr.getClass().getSimpleName() + ") " + addr.getHostAddress());
|
||||
result.add(addr);
|
||||
addAddr(result, addr);
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
|
Loading…
Reference in New Issue
Block a user