--- a/make/lib/Lib-jdk.jdwp.agent.gmk Tue Oct 29 15:08:19 2019 +0100
+++ b/make/lib/Lib-jdk.jdwp.agent.gmk Thu Oct 31 14:23:06 2019 -0700
@@ -38,7 +38,7 @@
$(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)
--- a/src/jdk.jdwp.agent/share/native/libdt_socket/socketTransport.c Tue Oct 29 15:08:19 2019 +0100
+++ b/src/jdk.jdwp.agent/share/native/libdt_socket/socketTransport.c Thu Oct 31 14:23:06 2019 -0700
@@ -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
/*
@@ -267,16 +269,101 @@
}
/*
+ * 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 @@
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 @@
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 @@
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 @@
}
err = dbgsysConnect(socketFD, ai->ai_addr, (socklen_t)ai->ai_addrlen);
+
if (err == DBG_EINPROGRESS && timeout > 0) {
err = dbgsysFinishConnect(socketFD, (long)timeout);
--- a/test/jdk/com/sun/jdi/JdwpAttachTest.java Tue Oct 29 15:08:19 2019 +0100
+++ b/test/jdk/com/sun/jdi/JdwpAttachTest.java Thu Oct 31 14:23:06 2019 -0700
@@ -32,6 +32,7 @@
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.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
/*
* @test
@@ -54,24 +54,36 @@
*/
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 @@
// 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 @@
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 @@
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 @@
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)));
}
}
--- a/test/jdk/com/sun/jdi/JdwpListenTest.java Tue Oct 29 15:08:19 2019 +0100
+++ b/test/jdk/com/sun/jdi/JdwpListenTest.java Thu Oct 31 14:23:06 2019 -0700
@@ -34,6 +34,7 @@
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 @@
*/
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 @@
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 @@
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 @@
}
}
+ 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 @@
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) {