# HG changeset patch # User amenkov # Date 1572556986 25200 # Node ID 1a8d65e71a66da59e0befe7ae76a8eb9f4c2891a # Parent 8c0e8cff877ff89b124a8e9a7ff42e1e6193c9a6 8224159: JDWP IPv6 scope support Reviewed-by: sspitsyn, cjplummer diff -r 8c0e8cff877f -r 1a8d65e71a66 make/lib/Lib-jdk.jdwp.agent.gmk --- 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) diff -r 8c0e8cff877f -r 1a8d65e71a66 src/jdk.jdwp.agent/share/native/libdt_socket/socketTransport.c --- 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 #include + #include #else #include #include + #include #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); diff -r 8c0e8cff877f -r 1a8d65e71a66 test/jdk/com/sun/jdi/JdwpAttachTest.java --- 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 * cases, each case fails by timeout). + private static boolean testFailedAttach = false; + public static void main(String[] args) throws Exception { List 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 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)() -> { 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 list, InetAddress addr) { + log(" - (" + addr.getClass().getSimpleName() + ") " + addr.getHostAddress()); + list.add(addr); + } + private static List getAddresses() { List result = new LinkedList<>(); try { @@ -136,17 +166,37 @@ Enumeration 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))); } } diff -r 8c0e8cff877f -r 1a8d65e71a66 test/jdk/com/sun/jdi/JdwpListenTest.java --- 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 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 list, InetAddress addr) { + log(" - (" + addr.getClass().getSimpleName() + ") " + addr.getHostAddress()); + list.add(addr); + } + private static List getAddresses() { List result = new LinkedList<>(); try { @@ -109,17 +121,37 @@ Enumeration 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) {