8224159: JDWP IPv6 scope support

Reviewed-by: sspitsyn, cjplummer
This commit is contained in:
Alex Menkov 2019-10-31 14:23:06 -07:00
parent a3ee39cb81
commit 3f93ec68ee
4 changed files with 222 additions and 68 deletions

View File

@ -38,7 +38,7 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBDT_SOCKET, \
$(call SET_SHARED_LIBRARY_ORIGIN), \ $(call SET_SHARED_LIBRARY_ORIGIN), \
LIBS_linux := -lpthread, \ LIBS_linux := -lpthread, \
LIBS_solaris := -lnsl -lsocket, \ 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) $(BUILD_LIBDT_SOCKET): $(call FindLib, java.base, java)

View File

@ -35,9 +35,11 @@
#ifdef _WIN32 #ifdef _WIN32
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <iphlpapi.h>
#else #else
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <net/if.h>
#endif #endif
/* /*
@ -266,17 +268,102 @@ static unsigned short getPort(struct sockaddr *sa)
: (((struct sockaddr_in6*)sa)->sin6_port)); : (((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. * Result must be released with dbgsysFreeAddrInfo.
*/ */
static jdwpTransportError static jdwpTransportError
parseAddress(const char *address, struct addrinfo **result) { parseAddress(const char *address, struct addrinfo **result) {
const char *colon; const char *colon;
size_t hostLen; size_t hostnameLen;
char *host = NULL;
const char *port; const char *port;
struct addrinfo hints; struct addrinfo hints;
int res;
*result = NULL; *result = NULL;
@ -295,39 +382,21 @@ parseAddress(const char *address, struct addrinfo **result) {
hints.ai_protocol = IPPROTO_TCP; hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_NUMERICSERV; // port must be a number hints.ai_flags = AI_NUMERICSERV; // port must be a number
hostLen = (colon == NULL ? 0 : colon - address); hostnameLen = (colon == NULL ? 0 : colon - address);
if (hostLen == 0) { if (hostnameLen == 0) {
/* no hostname - use localhost address (pass NULL to getaddrinfo) */ /* 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 /* *:port - listen on all interfaces
* use IPv6 socket (to accept IPv6 and mapped IPv4), * use IPv6 socket (to accept IPv6 and mapped IPv4),
* pass hostname == NULL to getaddrinfo. * pass hostname == NULL to getaddrinfo.
*/ */
hints.ai_family = allowOnlyIPv4 ? AF_INET : AF_INET6; hints.ai_family = allowOnlyIPv4 ? AF_INET : AF_INET6;
hints.ai_flags |= AI_PASSIVE | (allowOnlyIPv4 ? 0 : AI_V4MAPPED | AI_ALL); hints.ai_flags |= AI_PASSIVE | (allowOnlyIPv4 ? 0 : AI_V4MAPPED | AI_ALL);
} else { address = NULL;
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';
} }
res = dbgsysGetAddrInfo(host, port, &hints, result); return getAddrInfo(address, hostnameLen, 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;
} }
/* /*
@ -352,7 +421,7 @@ static jdwpTransportError
parseAllowedAddr(const char *buffer, struct in6_addr *result, int *isIPv4) { parseAllowedAddr(const char *buffer, struct in6_addr *result, int *isIPv4) {
struct addrinfo hints; struct addrinfo hints;
struct addrinfo *addrInfo = NULL; struct addrinfo *addrInfo = NULL;
int err; jdwpTransportError err;
/* /*
* To parse both IPv4 and IPv6 need to specify AF_UNSPEC family * 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_protocol = IPPROTO_TCP;
hints.ai_flags = AI_NUMERICHOST; // only numeric addresses, no resolution 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) { if (err != JDWPTRANSPORT_ERROR_NONE) {
setLastError(err, "getaddrinfo: failed to parse address"); return err;
return JDWPTRANSPORT_ERROR_IO_ERROR;
} }
if (addrInfo->ai_family == AF_INET6) { 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); err = dbgsysConnect(socketFD, ai->ai_addr, (socklen_t)ai->ai_addrlen);
if (err == DBG_EINPROGRESS && timeout > 0) { if (err == DBG_EINPROGRESS && timeout > 0) {
err = dbgsysFinishConnect(socketFD, (long)timeout); err = dbgsysFinishConnect(socketFD, (long)timeout);

View File

@ -32,6 +32,7 @@ import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Iterator; import java.util.Iterator;
@ -41,7 +42,6 @@ import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/* /*
* @test * @test
@ -54,24 +54,36 @@ import java.util.concurrent.TimeUnit;
*/ */
public class JdwpAttachTest { 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 { public static void main(String[] args) throws Exception {
List<InetAddress> addresses = getAddresses(); List<InetAddress> addresses = getAddresses();
boolean ipv4EnclosedTested = false; boolean ipv4EnclosedTested = false;
boolean ipv6EnclosedTested = false; boolean ipv6EnclosedTested = false;
for (InetAddress addr: addresses) { for (InetAddress addr: addresses) {
// also test that addresses enclosed in square brackets are supported if (testFailedAttach) {
attachTest(addr.getHostAddress(), addr.getHostAddress()); 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 // 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) { if (addr instanceof Inet4Address && !ipv4EnclosedTested) {
attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]"); attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]", true);
ipv4EnclosedTested = true; ipv4EnclosedTested = true;
} }
if (addr instanceof Inet6Address && !ipv6EnclosedTested) { if (addr instanceof Inet6Address && !ipv6EnclosedTested) {
attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]"); attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]", true);
ipv6EnclosedTested = 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) // we should be able to attach to both IPv4 and IPv6 addresses (127.0.0.1 & ::1)
InetAddress localAddresses[] = InetAddress.getAllByName("localhost"); InetAddress localAddresses[] = InetAddress.getAllByName("localhost");
for (int i = 0; i < localAddresses.length; i++) { 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 { throws Exception {
log("Starting listening at " + listenAddress); log("\nTest: listen on '" + listenAddress + "', attach to '" + connectAddress + "'");
log(" Starting listening at " + listenAddress);
ListeningConnector connector = getListenConnector(); ListeningConnector connector = getListenConnector();
Map<String, Connector.Argument> args = connector.defaultArguments(); Map<String, Connector.Argument> args = connector.defaultArguments();
setConnectorArg(args, "localAddress", listenAddress); setConnectorArg(args, "localAddress", listenAddress);
@ -100,31 +113,48 @@ public class JdwpAttachTest {
throw new RuntimeException("values from connector.startListening (" + actualPort throw new RuntimeException("values from connector.startListening (" + actualPort
+ " is not equal to values from arguments (" + port + ")"); + " 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 { try {
ExecutorService executor = Executors.newSingleThreadExecutor(); ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit((Callable<Exception>)() -> { executor.submit((Callable<Exception>)() -> {
VirtualMachine vm = connector.accept(args); VirtualMachine vm = connector.accept(args);
log("ACCEPTED.");
vm.dispose(); vm.dispose();
return null; return null;
}); });
executor.shutdown(); executor.shutdown();
LingeredApp debuggee = LingeredApp.startApp( try {
Arrays.asList("-agentlib:jdwp=transport=dt_socket" LingeredApp debuggee = LingeredApp.startApp(
+",address=" + connectAddresses + ":" + port Arrays.asList("-agentlib:jdwp=transport=dt_socket"
+ ",server=n,suspend=n")); + ",address=" + connectAddress + ":" + port
debuggee.stopApp(); + ",server=n,suspend=n"
// if failure is expected set small timeout (default is 20 sec)
executor.awaitTermination(20, TimeUnit.SECONDS); + (!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 { } finally {
connector.stopListening(args); 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() { private static List<InetAddress> getAddresses() {
List<InetAddress> result = new LinkedList<>(); List<InetAddress> result = new LinkedList<>();
try { try {
@ -136,17 +166,37 @@ public class JdwpAttachTest {
Enumeration<InetAddress> addresses = iface.getInetAddresses(); Enumeration<InetAddress> addresses = iface.getInetAddresses();
while (addresses.hasMoreElements()) { while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement(); InetAddress addr = addresses.nextElement();
// Java reports link local addresses with named scope, // Java reports link local addresses with symbolic scope,
// but Windows sockets routines support only numeric scope id. // but on Windows java.net.NetworkInterface generates its own scope names
// skip such addresses. // 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) { if (addr instanceof Inet6Address) {
Inet6Address addr6 = (Inet6Address)addr; Inet6Address addr6 = (Inet6Address)addr;
if (addr6.getScopedInterface() != null) { NetworkInterface scopeIface = addr6.getScopedInterface();
continue; 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()); addAddr(result, addr);
result.add(addr);
} }
} }
} catch (SocketException e) { } catch (SocketException e) {
@ -184,8 +234,11 @@ public class JdwpAttachTest {
arg.setValue(value); arg.setValue(value);
} }
private static long startTime = System.currentTimeMillis();
private static void log(Object o) { 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)));
} }
} }

View File

@ -34,6 +34,7 @@ import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
@ -51,6 +52,8 @@ import java.util.Map;
*/ */
public class JdwpListenTest { public class JdwpListenTest {
private static final boolean IsWindows = System.getProperty("os.name").toLowerCase().contains("windows");
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
List<InetAddress> addresses = getAddresses(); List<InetAddress> addresses = getAddresses();
@ -60,6 +63,8 @@ public class JdwpListenTest {
for (InetAddress attach: addresses) { for (InetAddress attach: addresses) {
// can connect only from the same address // can connect only from the same address
// IPv6 cannot connect to IPv4 (::1 to 127.0.0.1) and vice versa. // 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)); listenTest(listen.getHostAddress(), attach.getHostAddress(), attach.equals(listen));
} }
// test that addresses enclosed in square brackets are supported. // 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) private static void listenTest(String listenAddress, String connectAddress, boolean expectedResult)
throws IOException { throws IOException {
log("\nTest: listen at " + listenAddress + ", attaching from " + connectAddress
+ ", expected: " + (expectedResult ? "SUCCESS" : "FAILURE"));
log("Starting listening debuggee at " + listenAddress); log("Starting listening debuggee at " + listenAddress);
try (Debuggee debuggee = Debuggee.launcher("HelloWorld").setAddress(listenAddress + ":0").launch()) { try (Debuggee debuggee = Debuggee.launcher("HelloWorld").setAddress(listenAddress + ":0").launch()) {
log("Debuggee is listening on " + listenAddress + ":" + debuggee.getAddress()); 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() { private static List<InetAddress> getAddresses() {
List<InetAddress> result = new LinkedList<>(); List<InetAddress> result = new LinkedList<>();
try { try {
@ -109,17 +121,37 @@ public class JdwpListenTest {
Enumeration<InetAddress> addresses = iface.getInetAddresses(); Enumeration<InetAddress> addresses = iface.getInetAddresses();
while (addresses.hasMoreElements()) { while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement(); InetAddress addr = addresses.nextElement();
// Java reports link local addresses with named scope, // Java reports link local addresses with symbolic scope,
// but Windows sockets routines support only numeric scope id. // but on Windows java.net.NetworkInterface generates its own scope names
// skip such addresses. // 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) { if (addr instanceof Inet6Address) {
Inet6Address addr6 = (Inet6Address)addr; Inet6Address addr6 = (Inet6Address)addr;
if (addr6.getScopedInterface() != null) { NetworkInterface scopeIface = addr6.getScopedInterface();
continue; 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()); addAddr(result, addr);
result.add(addr);
} }
} }
} catch (SocketException e) { } catch (SocketException e) {