--- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/SocketListeningConnector.java Fri May 10 17:13:02 2019 -0700
+++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/SocketListeningConnector.java Wed May 15 11:06:33 2019 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -101,7 +101,7 @@
if (isWildcardPort(args)) {
String[] address = listener.address().split(":");
if (address.length > 1) {
- args.get(ARG_PORT).setValue(address[1]);
+ args.get(ARG_PORT).setValue(address[address.length - 1]);
}
}
}
--- a/src/jdk.jdi/share/classes/com/sun/tools/jdi/SocketTransportService.java Fri May 10 17:13:02 2019 -0700
+++ b/src/jdk.jdi/share/classes/com/sun/tools/jdi/SocketTransportService.java Wed May 15 11:06:33 2019 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -79,12 +79,7 @@
try {
address = InetAddress.getLocalHost();
} catch (UnknownHostException uhe) {
- byte[] loopback = {0x7f,0x00,0x00,0x01};
- try {
- address = InetAddress.getByAddress("127.0.0.1", loopback);
- } catch (UnknownHostException x) {
- throw new InternalError("unable to get local hostname");
- }
+ address = InetAddress.getLoopbackAddress();
}
}
@@ -201,6 +196,44 @@
};
}
+ private static class HostPort {
+ public final String host;
+ public final int port;
+ private HostPort(String host, int port) {
+ this.host = host;
+ this.port = port;
+ }
+
+ /**
+ * Creates an instance for given URN, which can be either <port> or <host>:<port>.
+ * If host is '*', the returned HostPort instance has host set to null.
+ * If <code>host</code> is a literal IPv6 address, it may be in square brackets.
+ */
+ public static HostPort parse(String hostPort) {
+ int splitIndex = hostPort.lastIndexOf(':');
+
+ int port;
+ try {
+ port = Integer.decode(hostPort.substring(splitIndex + 1));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("unable to parse port number in address");
+ }
+ if (port < 0 || port > 0xFFFF) {
+ throw new IllegalArgumentException("port out of range");
+ }
+
+ if (splitIndex <= 0) { // empty host means local connection
+ return new HostPort(InetAddress.getLoopbackAddress().getHostAddress(), port);
+ } else if (splitIndex == 1 && hostPort.charAt(0) == '*') {
+ return new HostPort(null, port);
+ } else if (hostPort.charAt(0) == '[' && hostPort.charAt(splitIndex - 1) == ']') {
+ return new HostPort(hostPort.substring(1, splitIndex - 1), port);
+ } else {
+ return new HostPort(hostPort.substring(0, splitIndex), port);
+ }
+ }
+ }
+
/**
* Attach to the specified address with optional attach and handshake
* timeout.
@@ -215,31 +248,14 @@
throw new IllegalArgumentException("timeout is negative");
}
- int splitIndex = address.indexOf(':');
- String host;
- String portStr;
- if (splitIndex < 0) {
- host = "localhost";
- portStr = address;
- } else {
- host = address.substring(0, splitIndex);
- portStr = address.substring(splitIndex+1);
- }
-
- if (host.equals("*")) {
- host = InetAddress.getLocalHost().getHostName();
- }
-
- int port;
- try {
- port = Integer.decode(portStr).intValue();
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException(
- "unable to parse port number in address");
- }
+ HostPort hostPort = HostPort.parse(address);
// open TCP connection to VM
- InetSocketAddress sa = new InetSocketAddress(host, port);
+ // formally "*" is not correct hostname to attach
+ // but lets connect to localhost
+ InetSocketAddress sa = new InetSocketAddress(hostPort.host == null
+ ? InetAddress.getLoopbackAddress().getHostAddress()
+ : hostPort.host, hostPort.port);
Socket s = new Socket();
try {
s.connect(sa, (int)attachTimeout);
@@ -290,26 +306,8 @@
*/
public ListenKey startListening(String address) throws IOException {
// use ephemeral port if address isn't specified.
- if (address == null || address.length() == 0) {
- address = "0";
- }
-
- int splitIndex = address.indexOf(':');
- String localaddr = null;
- if (splitIndex >= 0) {
- localaddr = address.substring(0, splitIndex);
- address = address.substring(splitIndex+1);
- }
-
- int port;
- try {
- port = Integer.decode(address).intValue();
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException(
- "unable to parse port number in address");
- }
-
- return startListening(localaddr, port);
+ HostPort hostPort = HostPort.parse((address == null || address.isEmpty()) ? "0" : address);
+ return startListening(hostPort.host, hostPort.port);
}
/**
--- a/src/jdk.jdwp.agent/share/native/libdt_socket/socketTransport.c Fri May 10 17:13:02 2019 -0700
+++ b/src/jdk.jdwp.agent/share/native/libdt_socket/socketTransport.c Wed May 15 11:06:33 2019 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -47,7 +47,7 @@
* Service Provider Interface - see src/share/javavm/export/jdwpTransport.h.
*/
-static int serverSocketFD;
+static int serverSocketFD = -1;
static int socketFD = -1;
static jdwpTransportCallback *callback;
static JavaVM *jvm;
@@ -78,8 +78,9 @@
/* version >= JDWPTRANSPORT_VERSION_1_1 */
typedef struct {
- uint32_t subnet;
- uint32_t netmask;
+ /* subnet and mask are stored as IPv6 addresses, IPv4 is stored as mapped IPv6 */
+ struct in6_addr subnet;
+ struct in6_addr netmask;
} AllowedPeerInfo;
#define STR(x) #x
@@ -89,6 +90,9 @@
static int _peers_cnt = 0;
+static int allowOnlyIPv4 = 0; // reflects "java.net.preferIPv4Stack" sys. property
+static int preferredAddressFamily = AF_INET; // "java.net.preferIPv6Addresses"
+
/*
* Record the last error for this thread.
*/
@@ -137,13 +141,19 @@
/* Set options common to client and server sides */
static jdwpTransportError
-setOptionsCommon(int fd)
+setOptionsCommon(int domain, int fd)
{
jvalue dontcare;
int err;
+ if (domain == AF_INET6) {
+ int off = 0;
+ // make the socket a dual mode socket
+ // this may fail if IPv4 is not supported - it's ok
+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&off, sizeof(off));
+ }
+
dontcare.i = 0; /* keep compiler happy */
-
err = dbgsysSetSocketOption(fd, TCP_NODELAY, JNI_TRUE, dontcare);
if (err < 0) {
RETURN_IO_ERROR("setsockopt TCPNODELAY failed");
@@ -223,31 +233,6 @@
return JDWPTRANSPORT_ERROR_NONE;
}
-static uint32_t
-getLocalHostAddress() {
- // Simple routine to guess localhost address.
- // it looks up "localhost" and returns 127.0.0.1 if lookup
- // fails.
- struct addrinfo hints, *res = NULL;
- uint32_t addr;
- int err;
-
- // Use portable way to initialize the structure
- memset((void *)&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET;
-
- err = getaddrinfo("localhost", NULL, &hints, &res);
- if (err < 0 || res == NULL) {
- return dbgsysHostToNetworkLong(INADDR_LOOPBACK);
- }
-
- // getaddrinfo might return more than one address
- // but we are using first one only
- addr = ((struct sockaddr_in *)(res->ai_addr))->sin_addr.s_addr;
- freeaddrinfo(res);
- return addr;
-}
-
static int
getPortNumber(const char *s_port) {
u_long n;
@@ -274,199 +259,290 @@
return n;
}
+static unsigned short getPort(struct sockaddr *sa)
+{
+ return dbgsysNetworkToHostShort(sa->sa_family == AF_INET
+ ? (((struct sockaddr_in*)sa)->sin_port)
+ : (((struct sockaddr_in6*)sa)->sin6_port));
+}
+
+/*
+ * Result must be released with dbgsysFreeAddrInfo.
+ */
static jdwpTransportError
-parseAddress(const char *address, struct sockaddr_in *sa) {
- char *colon;
- int port;
+parseAddress(const char *address, struct addrinfo **result) {
+ const char *colon;
+ size_t hostLen;
+ char *host = NULL;
+ const char *port;
+ struct addrinfo hints;
+ int res;
- memset((void *)sa, 0, sizeof(struct sockaddr_in));
- sa->sin_family = AF_INET;
+ *result = NULL;
/* check for host:port or port */
- colon = strchr(address, ':');
- port = getPortNumber((colon == NULL) ? address : colon +1);
- if (port < 0) {
+ colon = strrchr(address, ':');
+ port = (colon == NULL ? address : colon + 1);
+
+ /* ensure the port is valid (getaddrinfo allows port to be empty) */
+ if (getPortNumber(port) < 0) {
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "invalid port number specified");
}
- sa->sin_port = dbgsysHostToNetworkShort((u_short)port);
+
+ memset (&hints, 0, sizeof(hints));
+ hints.ai_family = allowOnlyIPv4 ? AF_INET : AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_NUMERICSERV; // port must be a number
- if (colon == NULL) {
- // bind to localhost only if no address specified
- sa->sin_addr.s_addr = getLocalHostAddress();
- } else if (strncmp(address, "localhost:", 10) == 0) {
- // optimize for common case
- sa->sin_addr.s_addr = getLocalHostAddress();
- } else if (*address == '*' && *(address+1) == ':') {
- // we are explicitly asked to bind server to all available IP addresses
- // has no meaning for client.
- sa->sin_addr.s_addr = dbgsysHostToNetworkLong(INADDR_ANY);
- } else {
- char *buf;
- char *hostname;
- uint32_t addr;
- int ai;
- buf = (*callback->alloc)((int)strlen(address) + 1);
- if (buf == NULL) {
+ hostLen = (colon == NULL ? 0 : colon - address);
+ if (hostLen == 0) {
+ /* no hostname - use localhost address (pass NULL to getaddrinfo) */
+ } else if (*address == '*' && hostLen == 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");
}
- strcpy(buf, address);
- buf[colon - address] = '\0';
- hostname = buf;
-
- /*
- * First see if the host is a literal IP address.
- * If not then try to resolve it.
- */
- addr = dbgsysInetAddr(hostname);
- if (addr == 0xffffffff) {
- struct addrinfo hints;
- struct addrinfo *results = NULL;
- memset (&hints, 0, sizeof(hints));
- hints.ai_family = AF_INET;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = IPPROTO_TCP;
+ strncpy(host, address, hostLen);
+ host[hostLen] = '\0';
+ }
- ai = dbgsysGetAddrInfo(hostname, NULL, &hints, &results);
-
- if (ai != 0) {
- /* don't use RETURN_IO_ERROR as unknown host is normal */
- setLastError(0, "getaddrinfo: unknown host");
- (*callback->free)(buf);
- return JDWPTRANSPORT_ERROR_IO_ERROR;
- }
-
- /* lookup was successful */
- sa->sin_addr = ((struct sockaddr_in *)results->ai_addr)->sin_addr;
- freeaddrinfo(results);
- } else {
- sa->sin_addr.s_addr = addr;
- }
-
- (*callback->free)(buf);
+ 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;
}
-static const char *
-ip_s2u(const char *instr, uint32_t *ip) {
- // Convert string representation of ip to integer
- // in network byte order (big-endian)
- char t[4] = { 0, 0, 0, 0 };
- const char *s = instr;
- int i = 0;
+/*
+ * Input is sockaddr just because all clients have it.
+ */
+static void convertIPv4ToIPv6(const struct sockaddr *addr4, struct in6_addr *addr6) {
+ // Implement in a platform-independent way.
+ // Spec requires in_addr has s_addr member, in6_addr has s6_addr[16] member.
+ struct in_addr *a4 = &(((struct sockaddr_in*)addr4)->sin_addr);
+ memset(addr6, 0, sizeof(*addr6)); // for safety
+
+ // Mapped address contains 80 zero bits, then 16 "1" bits, then IPv4 address (4 bytes).
+ addr6->s6_addr[10] = addr6->s6_addr[11] = 0xFF;
+ memcpy(&(addr6->s6_addr[12]), &(a4->s_addr), 4);
+}
+
+/*
+ * Parses address (IPv4 or IPv6), fills in result by parsed address.
+ * For IPv4 mapped IPv6 is returned in result, isIPv4 is set.
+ */
+static jdwpTransportError
+parseAllowedAddr(const char *buffer, struct in6_addr *result, int *isIPv4) {
+ struct addrinfo hints;
+ struct addrinfo *addrInfo = NULL;
+ int err;
+
+ /*
+ * To parse both IPv4 and IPv6 need to specify AF_UNSPEC family
+ * (with AF_INET6 IPv4 addresses are not parsed even with AI_V4MAPPED and AI_ALL flags).
+ */
+ memset (&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; // IPv6 or mapped IPv4
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_NUMERICHOST; // only numeric addresses, no resolution
+
+ err = dbgsysGetAddrInfo(buffer, NULL, &hints, &addrInfo);
+
+ if (err != 0) {
+ setLastError(err, "getaddrinfo: failed to parse address");
+ return JDWPTRANSPORT_ERROR_IO_ERROR;
+ }
- while (1) {
- if (*s == '.') {
- ++i;
- ++s;
- continue;
+ if (addrInfo->ai_family == AF_INET6) {
+ memcpy(result, &(((struct sockaddr_in6 *)(addrInfo->ai_addr))->sin6_addr), sizeof(*result));
+ *isIPv4 = 0;
+ } else { // IPv4 address - convert to mapped IPv6
+ struct in6_addr addr6;
+ convertIPv4ToIPv6(addrInfo->ai_addr, &addr6);
+ memcpy(result, &addr6, sizeof(*result));
+ *isIPv4 = 1;
+ }
+
+ dbgsysFreeAddrInfo(addrInfo);
+
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+/*
+ * Parses prefix length from buffer (integer value), fills in result with corresponding net mask.
+ * For IPv4 (isIPv4 is set), maximum prefix length is 32 bit, for IPv6 - 128 bit.
+ */
+static jdwpTransportError
+parseAllowedMask(const char *buffer, int isIPv4, struct in6_addr *result) {
+ int prefixLen = 0;
+ int maxValue = isIPv4 ? 32 : 128;
+
+ do {
+ if (*buffer < '0' || *buffer > '9') {
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
}
- if (*s == 0 || *s == '+' || *s == '/') {
- break;
+ prefixLen = prefixLen * 10 + (*buffer - '0');
+ if (prefixLen > maxValue) { // avoid overflow
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
}
- if (*s < '0' || *s > '9') {
- return instr;
- }
- t[i] = (t[i] * 10) + (*s - '0');
- ++s;
+ buffer++;
+ } while (*buffer != '\0');
+
+ if (isIPv4) {
+ // IPv4 are stored as mapped IPv6, prefixLen needs to be converted too
+ prefixLen += 96;
+ }
+
+ if (prefixLen == 0) {
+ return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
}
- *ip = *(uint32_t*)(t);
- return s;
+ // generate mask for prefix length
+ memset(result, 0, sizeof(*result));
+
+ // prefixLen <= 128, so we won't go over result's size
+ for (int i = 0; prefixLen > 0; i++, prefixLen -= 8) {
+ if (prefixLen >= 8) {
+ // set the whole byte
+ result->s6_addr[i] = 0xFF;
+ } else {
+ // set only "prefixLen" bits
+ result->s6_addr[i] = (char)(0xFF << (8 - prefixLen));
+ }
+ }
+
+ return JDWPTRANSPORT_ERROR_NONE;
}
-static const char *
-mask_s2u(const char *instr, uint32_t *mask) {
- // Convert the number of bits to a netmask
- // in network byte order (big-endian)
- unsigned char m = 0;
- const char *s = instr;
+/*
+ * Internal implementation of parseAllowedPeers (requires writable buffer).
+ */
+static jdwpTransportError
+parseAllowedPeersInternal(char *buffer) {
+ char *next;
+ int isIPv4 = 0;
- while (1) {
- if (*s == 0 || *s == '+') {
- break;
+ do {
+ char *mask = NULL;
+ char *endOfAddr = strpbrk(buffer, "/+");
+ if (endOfAddr == NULL) {
+ // this is the last address and there is no prefix length
+ next = NULL;
+ } else {
+ next = endOfAddr + 1;
+ if (*endOfAddr == '/') {
+ // mask (prefix length) presents
+ char *endOfMask = strchr(next, '+');
+ mask = next;
+ if (endOfMask == NULL) {
+ // no more addresses
+ next = NULL;
+ } else {
+ next = endOfMask + 1;
+ *endOfMask = '\0';
+ }
+ }
+ *endOfAddr = '\0';
+ }
+
+ // parse subnet address (IPv4 is stored as mapped IPv6)
+ if (parseAllowedAddr(buffer, &(_peers[_peers_cnt].subnet), &isIPv4) != JDWPTRANSPORT_ERROR_NONE) {
+ _peers_cnt = 0;
+ fprintf(stderr, "Error in allow option: '%s'\n", buffer);
+ RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
+ "invalid IP address in allow option");
}
- if (*s < '0' || *s > '9') {
- return instr;
+ if (mask != NULL) {
+ if (parseAllowedMask(mask, isIPv4, &(_peers[_peers_cnt].netmask)) != JDWPTRANSPORT_ERROR_NONE) {
+ _peers_cnt = 0;
+ fprintf(stderr, "Error in allow option: '%s'\n", mask);
+ RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
+ "invalid netmask in allow option");
+ }
+ // for safety update subnet to satisfy the mask
+ for (size_t i = 0; i < sizeof(_peers[_peers_cnt].subnet); i++) {
+ _peers[_peers_cnt].subnet.s6_addr[i] &= _peers[_peers_cnt].netmask.s6_addr[i];
+ }
+ } else {
+ memset(&(_peers[_peers_cnt].netmask), 0xFF, sizeof(_peers[_peers_cnt].netmask));
}
- m = (m * 10) + (*s - '0');
- ++s;
- }
+ _peers_cnt++;
+ buffer = next;
+ } while (next != NULL);
+
+ return JDWPTRANSPORT_ERROR_NONE;
+}
- if (m == 0 || m > 32) {
- // Drop invalid input
- return instr;
+/*
+ * Parses 'allow' argument (fills in list of allowed peers (global _peers variable)).
+ * 'Allow' value consists of tokens separated by '+',
+ * each token contains IP address (IPv4 or IPv6) and optional prefixLength:
+ * '<addr>[/<prefixLength>]'.
+ * Example: '192.168.1.10+192.168.0.0/24'
+ * - connections are allowed from 192.168.1.10 and subnet 192.168.0.XX.
+ */
+static jdwpTransportError
+parseAllowedPeers(const char *allowed_peers, size_t len) {
+ // Build a list of allowed peers from char string
+ // of format 192.168.0.10+192.168.0.0/24
+
+ // writable copy of the value
+ char *buffer = (*callback->alloc)((int)len + 1);
+ if (buffer == NULL) {
+ RETURN_ERROR(JDWPTRANSPORT_ERROR_OUT_OF_MEMORY, "out of memory");
}
+ strncpy(buffer, allowed_peers, len);
+ buffer[len] = '\0';
- *mask = htonl((uint32_t)(~0) << (32 - m));
- return s;
+ jdwpTransportError err = parseAllowedPeersInternal(buffer);
+
+ (*callback->free)(buffer);
+
+ return err;
}
static int
-ip_in_subnet(uint32_t subnet, uint32_t mask, uint32_t ipaddr) {
- return (ipaddr & mask) == subnet;
-}
-
-static jdwpTransportError
-parseAllowedPeers(const char *allowed_peers) {
- // Build a list of allowed peers from char string
- // of format 192.168.0.10+192.168.0.0/24
- const char *s = NULL;
- const char *p = allowed_peers;
- uint32_t ip = 0;
- uint32_t mask = 0xFFFFFFFF;
-
- while (1) {
- s = ip_s2u(p, &ip);
- if (s == p) {
- _peers_cnt = 0;
- fprintf(stderr, "Error in allow option: '%s'\n", s);
- RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
- "invalid IP address in allow option");
- }
-
- if (*s == '/') {
- // netmask specified
- s = mask_s2u(s + 1, &mask);
- if (*(s - 1) == '/') {
- // Input is not consumed, something bad happened
- _peers_cnt = 0;
- fprintf(stderr, "Error in allow option: '%s'\n", s);
- RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
- "invalid netmask in allow option");
- }
- } else {
- // reset netmask
- mask = 0xFFFFFFFF;
- }
-
- if (*s == '+' || *s == 0) {
- if (_peers_cnt >= MAX_PEER_ENTRIES) {
- fprintf(stderr, "Error in allow option: '%s'\n", allowed_peers);
- RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
- "exceeded max number of allowed peers: " MAX_PEERS_STR);
- }
- _peers[_peers_cnt].subnet = ip;
- _peers[_peers_cnt].netmask = mask;
- _peers_cnt++;
- if (*s == 0) {
- // end of options
- break;
- }
- // advance to next IP block
- p = s + 1;
+isAddressInSubnet(const struct in6_addr *address, const struct in6_addr *subnet, const struct in6_addr *mask) {
+ for (size_t i = 0; i < sizeof(struct in6_addr); i++) {
+ if ((address->s6_addr[i] & mask->s6_addr[i]) != subnet->s6_addr[i]) {
+ return 0;
}
}
- return JDWPTRANSPORT_ERROR_NONE;
+ return 1;
}
static int
-isPeerAllowed(struct sockaddr_in *peer) {
- int i;
- for (i = 0; i < _peers_cnt; ++i) {
- int peer_ip = peer->sin_addr.s_addr;
- if (ip_in_subnet(_peers[i].subnet, _peers[i].netmask, peer_ip)) {
+isPeerAllowed(struct sockaddr_storage *peer) {
+ struct in6_addr tmp;
+ struct in6_addr *addr6;
+ // _peers contains IPv6 subnet and mask (IPv4 is converted to mapped IPv6)
+ if (peer->ss_family == AF_INET) {
+ convertIPv4ToIPv6((struct sockaddr *)peer, &tmp);
+ addr6 = &tmp;
+ } else {
+ addr6 = &(((struct sockaddr_in6 *)peer)->sin6_addr);
+ }
+
+ for (int i = 0; i < _peers_cnt; ++i) {
+ if (isAddressInSubnet(addr6, &(_peers[i].subnet), &(_peers[i].netmask))) {
return 1;
}
}
@@ -490,65 +566,58 @@
return JDWPTRANSPORT_ERROR_NONE;
}
-
-static jdwpTransportError JNICALL
-socketTransport_startListening(jdwpTransportEnv* env, const char* address,
- char** actualAddress)
+/*
+ * Starts listening on the specified addrinfo,
+ * returns listening socket and actual listening port.
+ * If the function fails and returned socket != -1, the socket should be closed.
+ */
+static jdwpTransportError startListening(struct addrinfo *ai, int *socket, char** actualAddress)
{
- struct sockaddr_in sa;
int err;
- memset((void *)&sa,0,sizeof(struct sockaddr_in));
- sa.sin_family = AF_INET;
-
- /* no address provided */
- if ((address == NULL) || (address[0] == '\0')) {
- address = "0";
+ *socket = dbgsysSocket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP);
+ if (*socket < 0) {
+ RETURN_IO_ERROR("socket creation failed");
}
- err = parseAddress(address, &sa);
- if (err != JDWPTRANSPORT_ERROR_NONE) {
+ err = setOptionsCommon(ai->ai_family, *socket);
+ if (err) {
return err;
}
- serverSocketFD = dbgsysSocket(AF_INET, SOCK_STREAM, 0);
- if (serverSocketFD < 0) {
- RETURN_IO_ERROR("socket creation failed");
- }
-
- err = setOptionsCommon(serverSocketFD);
- if (err) {
- return err;
- }
- if (sa.sin_port != 0) {
+ if (getPort(ai->ai_addr) != 0) {
/*
* Only need SO_REUSEADDR if we're using a fixed port. If we
* start seeing EADDRINUSE due to collisions in free ports
* then we should retry the dbgsysBind() a few times.
*/
- err = setReuseAddrOption(serverSocketFD);
+ err = setReuseAddrOption(*socket);
if (err) {
return err;
}
}
- err = dbgsysBind(serverSocketFD, (struct sockaddr *)&sa, sizeof(sa));
+ err = dbgsysBind(*socket, ai->ai_addr, (socklen_t)ai->ai_addrlen);
if (err < 0) {
RETURN_IO_ERROR("bind failed");
}
- err = dbgsysListen(serverSocketFD, 1);
+ err = dbgsysListen(*socket, 1); // only 1 debugger can attach
if (err < 0) {
RETURN_IO_ERROR("listen failed");
}
{
char buf[20];
- socklen_t len = sizeof(sa);
+ struct sockaddr_storage addr;
+ socklen_t len = sizeof(addr);
jint portNum;
- err = dbgsysGetSocketName(serverSocketFD,
- (struct sockaddr *)&sa, &len);
- portNum = dbgsysNetworkToHostShort(sa.sin_port);
+ err = dbgsysGetSocketName(*socket, (struct sockaddr *)&addr, &len);
+ if (err != 0) {
+ RETURN_IO_ERROR("getsockname failed");
+ }
+
+ portNum = getPort((struct sockaddr *)&addr);
sprintf(buf, "%d", portNum);
*actualAddress = (*callback->alloc)((int)strlen(buf) + 1);
if (*actualAddress == NULL) {
@@ -562,12 +631,62 @@
}
static jdwpTransportError JNICALL
+socketTransport_startListening(jdwpTransportEnv* env, const char* address,
+ char** actualAddress)
+{
+ int err;
+ struct addrinfo *addrInfo = NULL;
+ struct addrinfo *listenAddr = NULL;
+
+ /* no address provided */
+ if ((address == NULL) || (address[0] == '\0')) {
+ address = "0";
+ }
+
+ err = parseAddress(address, &addrInfo);
+ if (err != JDWPTRANSPORT_ERROR_NONE) {
+ return err;
+ }
+
+ /* 1st pass - preferredAddressFamily (by default IPv4), 2nd pass - the rest */
+ for (int pass = 0; pass < 2 && listenAddr == NULL; pass++) {
+ for (struct addrinfo *ai = addrInfo; ai != NULL; ai = ai->ai_next) {
+ if ((pass == 0 && ai->ai_family == preferredAddressFamily) ||
+ (pass == 1 && ai->ai_family != preferredAddressFamily))
+ {
+ listenAddr = ai;
+ break;
+ }
+ }
+ }
+
+ if (listenAddr == NULL) {
+ dbgsysFreeAddrInfo(addrInfo);
+ RETURN_ERROR(JDWPTRANSPORT_ERROR_INTERNAL, "listen failed: wrong address");
+ }
+
+ err = startListening(listenAddr, &serverSocketFD, actualAddress);
+
+ dbgsysFreeAddrInfo(addrInfo);
+
+ if (err != JDWPTRANSPORT_ERROR_NONE) {
+ if (serverSocketFD >= 0) {
+ dbgsysSocketClose(serverSocketFD);
+ serverSocketFD = -1;
+ }
+ return err;
+ }
+
+ return JDWPTRANSPORT_ERROR_NONE;
+}
+
+static jdwpTransportError JNICALL
socketTransport_accept(jdwpTransportEnv* env, jlong acceptTimeout, jlong handshakeTimeout)
{
- socklen_t socketLen;
int err = JDWPTRANSPORT_ERROR_NONE;
- struct sockaddr_in socket;
- jlong startTime = (jlong)0;
+ struct sockaddr_storage clientAddr;
+ socklen_t clientAddrLen;
+ jlong startTime = 0;
/*
* Use a default handshake timeout if not specified - this avoids an indefinite
@@ -605,11 +724,10 @@
/*
* Accept the connection
*/
- memset((void *)&socket,0,sizeof(struct sockaddr_in));
- socketLen = sizeof(socket);
+ clientAddrLen = sizeof(clientAddr);
socketFD = dbgsysAccept(serverSocketFD,
- (struct sockaddr *)&socket,
- &socketLen);
+ (struct sockaddr *)&clientAddr,
+ &clientAddrLen);
/* set the last error here as could be overridden by configureBlocking */
if (socketFD < 0) {
setLastError(JDWPTRANSPORT_ERROR_IO_ERROR, "accept failed");
@@ -632,12 +750,14 @@
* Verify that peer is allowed to connect.
*/
if (_peers_cnt > 0) {
- if (!isPeerAllowed(&socket)) {
+ if (!isPeerAllowed(&clientAddr)) {
char ebuf[64] = { 0 };
- char buf[INET_ADDRSTRLEN] = { 0 };
- const char* addr_str = inet_ntop(AF_INET, &(socket.sin_addr), buf, INET_ADDRSTRLEN);
+ char addrStr[INET_ADDRSTRLEN] = { 0 };
+ int err2 = getnameinfo((struct sockaddr *)&clientAddr, clientAddrLen,
+ addrStr, sizeof(addrStr), NULL, 0,
+ NI_NUMERICHOST);
sprintf(ebuf, "ERROR: Peer not allowed to connect: %s\n",
- (addr_str == NULL) ? "<bad address>" : addr_str);
+ (err2 != 0) ? "<bad address>" : addrStr);
dbgsysSocketClose(socketFD);
socketFD = -1;
err = JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
@@ -686,28 +806,19 @@
return JDWPTRANSPORT_ERROR_NONE;
}
-static jdwpTransportError JNICALL
-socketTransport_attach(jdwpTransportEnv* env, const char* addressString, jlong attachTimeout,
- jlong handshakeTimeout)
-{
- struct sockaddr_in sa;
+/*
+ * Tries to connect to the specified addrinfo, returns connected socket.
+ * If the function fails and returned socket != -1, the socket should be closed.
+ */
+static jdwpTransportError connectToAddr(struct addrinfo *ai, jlong timeout, int *socket) {
int err;
- if (addressString == NULL || addressString[0] == '\0') {
- RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "address is missing");
- }
-
- err = parseAddress(addressString, &sa);
- if (err != JDWPTRANSPORT_ERROR_NONE) {
- return err;
- }
-
- socketFD = dbgsysSocket(AF_INET, SOCK_STREAM, 0);
- if (socketFD < 0) {
+ *socket = dbgsysSocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (*socket < 0) {
RETURN_IO_ERROR("unable to create socket");
}
- err = setOptionsCommon(socketFD);
+ err = setOptionsCommon(ai->ai_family, socketFD);
if (err) {
return err;
}
@@ -722,13 +833,13 @@
* To do a timed connect we make the socket non-blocking
* and poll with a timeout;
*/
- if (attachTimeout > 0) {
+ if (timeout > 0) {
dbgsysConfigureBlocking(socketFD, JNI_FALSE);
}
- err = dbgsysConnect(socketFD, (struct sockaddr *)&sa, sizeof(sa));
- if (err == DBG_EINPROGRESS && attachTimeout > 0) {
- err = dbgsysFinishConnect(socketFD, (long)attachTimeout);
+ err = dbgsysConnect(socketFD, ai->ai_addr, (socklen_t)ai->ai_addrlen);
+ if (err == DBG_EINPROGRESS && timeout > 0) {
+ err = dbgsysFinishConnect(socketFD, (long)timeout);
if (err == DBG_ETIMEOUT) {
dbgsysConfigureBlocking(socketFD, JNI_TRUE);
@@ -736,10 +847,55 @@
}
}
- if (err < 0) {
+ if (err) {
RETURN_IO_ERROR("connect failed");
}
+ return err;
+}
+
+
+static jdwpTransportError JNICALL
+socketTransport_attach(jdwpTransportEnv* env, const char* addressString, jlong attachTimeout,
+ jlong handshakeTimeout)
+{
+ int err;
+ struct addrinfo *addrInfo = NULL;
+
+ if (addressString == NULL || addressString[0] == '\0') {
+ RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "address is missing");
+ }
+
+ err = parseAddress(addressString, &addrInfo);
+ if (err) {
+ return err;
+ }
+
+ /* 1st pass - preferredAddressFamily (by default IPv4), 2nd pass - the rest */
+ for (int pass = 0; pass < 2 && socketFD < 0; pass++) {
+ for (struct addrinfo *ai = addrInfo; ai != NULL; ai = ai->ai_next) {
+ if ((pass == 0 && ai->ai_family == preferredAddressFamily) ||
+ (pass == 1 && ai->ai_family != preferredAddressFamily))
+ {
+ err = connectToAddr(ai, attachTimeout, &socketFD);
+ if (err == JDWPTRANSPORT_ERROR_NONE) {
+ break;
+ }
+ if (socketFD >= 0) {
+ dbgsysSocketClose(socketFD);
+ socketFD = -1;
+ }
+ }
+ }
+ }
+
+ freeaddrinfo(addrInfo);
+
+ /* err from the last connectToAddr() call */
+ if (err != 0) {
+ return err;
+ }
+
if (attachTimeout > 0) {
dbgsysConfigureBlocking(socketFD, JNI_TRUE);
}
@@ -1010,7 +1166,7 @@
"allow option '*' cannot be expanded");
}
} else {
- int err = parseAllowedPeers(allowed_peers);
+ int err = parseAllowedPeers(allowed_peers, len);
if (err != JDWPTRANSPORT_ERROR_NONE) {
return err;
}
@@ -1019,10 +1175,46 @@
return JDWPTRANSPORT_ERROR_NONE;
}
+/*
+ * Reads boolean system value, sets *result to
+ * - trueValue if the property is "true";
+ * - falseValue if the property is "false".
+ * Doesn't change *result if the property is not set or failed to read.
+ */
+static int readBooleanSysProp(int *result, int trueValue, int falseValue,
+ JNIEnv* jniEnv, jclass sysClass, jmethodID getPropMethod, const char *propName)
+{
+ jstring value;
+ jstring name = (*jniEnv)->NewStringUTF(jniEnv, propName);
+
+ if (name == NULL) {
+ return JNI_ERR;
+ }
+ value = (jstring)(*jniEnv)->CallStaticObjectMethod(jniEnv, sysClass, getPropMethod, name);
+ if ((*jniEnv)->ExceptionCheck(jniEnv)) {
+ return JNI_ERR;
+ }
+ if (value != NULL) {
+ const char *theValue = (*jniEnv)->GetStringUTFChars(jniEnv, value, NULL);
+ if (theValue == NULL) {
+ return JNI_ERR;
+ }
+ if (strcmp(theValue, "true") == 0) {
+ *result = trueValue;
+ } else if (strcmp(theValue, "false") == 0) {
+ *result = falseValue;
+ }
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, value, theValue);
+ }
+ return JNI_OK;
+}
+
JNIEXPORT jint JNICALL
jdwpTransport_OnLoad(JavaVM *vm, jdwpTransportCallback* cbTablePtr,
jint version, jdwpTransportEnv** env)
{
+ JNIEnv* jniEnv = NULL;
+
if (version < JDWPTRANSPORT_VERSION_1_0 ||
version > JDWPTRANSPORT_VERSION_1_1) {
return JNI_EVERSION;
@@ -1055,5 +1247,33 @@
/* initialized TLS */
tlsIndex = dbgsysTlsAlloc();
+
+ // retrieve network-related system properties
+ do {
+ jclass sysClass;
+ jmethodID getPropMethod;
+ if ((*vm)->GetEnv(vm, (void **)&jniEnv, JNI_VERSION_9) != JNI_OK) {
+ break;
+ }
+ sysClass = (*jniEnv)->FindClass(jniEnv, "java/lang/System");
+ if (sysClass == NULL) {
+ break;
+ }
+ getPropMethod = (*jniEnv)->GetStaticMethodID(jniEnv, sysClass,
+ "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
+ if (getPropMethod == NULL) {
+ break;
+ }
+ readBooleanSysProp(&allowOnlyIPv4, 1, 0,
+ jniEnv, sysClass, getPropMethod, "java.net.preferIPv4Stack");
+ readBooleanSysProp(&preferredAddressFamily, AF_INET6, AF_INET,
+ jniEnv, sysClass, getPropMethod, "java.net.preferIPv6Addresses");
+ } while (0);
+
+ if (jniEnv != NULL && (*jniEnv)->ExceptionCheck(jniEnv)) {
+ (*jniEnv)->ExceptionClear(jniEnv);
+ }
+
+
return JNI_OK;
}
--- a/src/jdk.jdwp.agent/share/native/libdt_socket/sysSocket.h Fri May 10 17:13:02 2019 -0700
+++ b/src/jdk.jdwp.agent/share/native/libdt_socket/sysSocket.h Wed May 15 11:06:33 2019 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -47,11 +47,11 @@
int dbgsysListen(int fd, int backlog);
int dbgsysRecv(int fd, char *buf, size_t nBytes, int flags);
int dbgsysSend(int fd, char *buf, size_t nBytes, int flags);
-int dbgsysGetAddrInfo(char *hostname, char *service, struct addrinfo *hints, struct addrinfo **results);
+int dbgsysGetAddrInfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **results);
+void dbgsysFreeAddrInfo(struct addrinfo *info);
int dbgsysSocket(int domain, int type, int protocol);
int dbgsysBind(int fd, struct sockaddr *name, socklen_t namelen);
int dbgsysSetSocketOption(int fd, jint cmd, jboolean on, jvalue value);
-uint32_t dbgsysInetAddr(const char* cp);
uint32_t dbgsysHostToNetworkLong(uint32_t hostlong);
unsigned short dbgsysHostToNetworkShort(unsigned short hostshort);
uint32_t dbgsysNetworkToHostLong(uint32_t netlong);
--- a/src/jdk.jdwp.agent/unix/native/libdt_socket/socket_md.c Fri May 10 17:13:02 2019 -0700
+++ b/src/jdk.jdwp.agent/unix/native/libdt_socket/socket_md.c Wed May 15 11:06:33 2019 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -127,12 +127,17 @@
}
int
-dbgsysGetAddrInfo(char *hostname, char *service,
- struct addrinfo *hints,
+dbgsysGetAddrInfo(const char *hostname, const char *service,
+ const struct addrinfo *hints,
struct addrinfo **results) {
return getaddrinfo(hostname, service, hints, results);
}
+void
+dbgsysFreeAddrInfo(struct addrinfo *info) {
+ freeaddrinfo(info);
+}
+
unsigned short
dbgsysHostToNetworkShort(unsigned short hostshort) {
return htons(hostshort);
@@ -164,11 +169,6 @@
}
uint32_t
-dbgsysInetAddr(const char* cp) {
- return (uint32_t)inet_addr(cp);
-}
-
-uint32_t
dbgsysHostToNetworkLong(uint32_t hostlong) {
return htonl(hostlong);
}
--- a/src/jdk.jdwp.agent/windows/native/libdt_socket/socket_md.c Fri May 10 17:13:02 2019 -0700
+++ b/src/jdk.jdwp.agent/windows/native/libdt_socket/socket_md.c Wed May 15 11:06:33 2019 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -199,10 +199,15 @@
}
int
-dbgsysGetAddrInfo(char *hostname, char *service,
- struct addrinfo *hints,
+dbgsysGetAddrInfo(const char *hostname, const char *service,
+ const struct addrinfo *hints,
struct addrinfo **result) {
- return getaddrinfo(hostname, service, hints, result);
+ return getaddrinfo(hostname, service, hints, result);
+}
+
+void
+dbgsysFreeAddrInfo(struct addrinfo *info) {
+ freeaddrinfo(info);
}
unsigned short
@@ -214,7 +219,7 @@
dbgsysSocket(int domain, int type, int protocol) {
int fd = (int)socket(domain, type, protocol);
if (fd != SOCKET_ERROR) {
- SetHandleInformation((HANDLE)(UINT_PTR)fd, HANDLE_FLAG_INHERIT, FALSE);
+ SetHandleInformation((HANDLE)(UINT_PTR)fd, HANDLE_FLAG_INHERIT, FALSE);
}
return fd;
}
@@ -241,15 +246,6 @@
uint32_t
-dbgsysInetAddr(const char* cp) {
- uint32_t addr;
- if (inet_pton(AF_INET, cp, &addr) < 1) {
- return -1;
- }
- return addr;
-}
-
-uint32_t
dbgsysHostToNetworkLong(uint32_t hostlong) {
return (uint32_t)htonl((u_long)hostlong);
}
--- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/ListeningConnector/startListening/startlis001.java Fri May 10 17:13:02 2019 -0700
+++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/ListeningConnector/startListening/startlis001.java Wed May 15 11:06:33 2019 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -32,9 +32,12 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.Arrays;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import nsk.share.*;
import nsk.share.jpda.*;
@@ -85,7 +88,6 @@
private int runIt(String argv[], PrintStream out) {
String port;
String addr;
- InetAddress inetAddr = null;
ArgumentHandler argHandler = new ArgumentHandler(argv);
// pass if CONNECTOR_NAME is not implemented
@@ -97,34 +99,40 @@
long timeout = argHandler.getWaitTime() * 60 * 1000;
-/* Check that listening address returned by ListeningConnector.startListening()
- matches the address which was set via connector's arguments */
+ /* Check that listening address returned by ListeningConnector.startListening()
+ * matches the address which was set via connector's arguments.
+ * Empty host address causes listening for local connections only (loopback interface).
+ * */
+ String hostname = "localhost";
+ List<String> validAddresses = new LinkedList<>();
+ validAddresses.add(hostname);
try {
- inetAddr = InetAddress.getLocalHost();
+ Arrays.stream(InetAddress.getAllByName(hostname))
+ .forEach(address -> validAddresses.add(address.getHostAddress()));
} catch (UnknownHostException e) {
log.complain("FAILURE: caught UnknownHostException " +
- e.getMessage());
+ e.getMessage());
totalRes = false;
}
- String hostname = inetAddr.getHostName();
- String ip = inetAddr.getHostAddress();
+
port = argHandler.getTransportPortIfNotDynamic();
initConnector(port);
if ((addr = startListen()) == null) {
log.complain("Test case #1 FAILED: unable to start listening");
totalRes = false;
- }
- else {
+ } else {
+ String validAddrList = validAddresses.stream()
+ .map(value -> value + ":" + port)
+ .collect(Collectors.joining(" or "));
log.display("Test case #1: start listening the address " + addr);
- log.display("Expected address: "+ hostname + ":" + port +
- "\n\tor "+ ip + ":" + port);
- if ( (!addr.startsWith(hostname) && !addr.startsWith(ip)) ||
- (port != null && !addr.endsWith(port)) ) {
+ log.display("Expected addresses: " + validAddrList);
+ final String listenAddr = addr;
+ boolean isValid = validAddresses.stream()
+ .anyMatch(value -> listenAddr.startsWith(value) && (port == null || listenAddr.endsWith(port)));
+ if (!isValid) {
log.complain("Test case #1 FAILED: listening address " + addr +
- "\ndoes not match expected address:\n" +
- hostname + ":" + port + " or " +
- ip + ":" + port);
+ "\ndoes not match expected address:\n" + validAddrList);
totalRes = false;
}
if (!stopListen()) {
@@ -135,8 +143,8 @@
log.display("Test case #1 PASSED: listening address matches expected address");
}
-/* Check that an address generated by ListeningConnector.startListening()
- is valid i.e. debugee VM is accessible via this address */
+ /* Check that an address generated by ListeningConnector.startListening()
+ is valid i.e. debugee VM is accessible via this address */
initConnector(null);
if ((addr = startListen()) == null) {
log.complain("Test case #2 FAILED: unable to start listening");
--- a/test/jdk/com/sun/jdi/BasicJDWPConnectionTest.java Fri May 10 17:13:02 2019 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,220 +0,0 @@
-/*
- * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- * @test
- * @summary Smoke test for JDWP hardening
- * @library /test/lib
- * @run driver BasicJDWPConnectionTest
- */
-
-import java.io.IOException;
-
-import java.net.Socket;
-import java.net.SocketException;
-
-import jdk.test.lib.Utils;
-import jdk.test.lib.apps.LingeredApp;
-
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-
-public class BasicJDWPConnectionTest {
-
- public static int handshake(int port) throws IOException {
- // Connect to the debuggee and handshake
- int res = -1;
- Socket s = null;
- try {
- s = new Socket("localhost", port);
- s.getOutputStream().write("JDWP-Handshake".getBytes("UTF-8"));
- byte[] buffer = new byte[24];
- res = s.getInputStream().read(buffer);
- }
- catch (SocketException ex) {
- // pass
- } finally {
- if (s != null) {
- s.close();
- }
- }
- return res;
- }
-
- public static ArrayList<String> prepareCmd(String allowOpt) {
- ArrayList<String> cmd = new ArrayList<>();
-
- String jdwpArgs = "-agentlib:jdwp=transport=dt_socket,server=y," +
- "suspend=n,address=*:0" + allowOpt;
- cmd.add(jdwpArgs);
- return cmd;
- }
-
- private static Pattern listenRegexp = Pattern.compile("Listening for transport \\b(.+)\\b at address: \\b(\\d+)\\b");
- private static int detectPort(LingeredApp app) {
- long maxWaitTime = System.currentTimeMillis()
- + Utils.adjustTimeout(10000); // 10 seconds adjusted for TIMEOUT_FACTOR
- while (true) {
- String s = app.getProcessStdout();
- Matcher m = listenRegexp.matcher(s);
- if (m.find()) {
- // m.group(1) is transport, m.group(2) is port
- return Integer.parseInt(m.group(2));
- }
- if (System.currentTimeMillis() > maxWaitTime) {
- throw new RuntimeException("Could not detect port from '" + s + "' (timeout)");
- }
- try {
- if (app.getProcess().waitFor(500, TimeUnit.MILLISECONDS)) {
- throw new RuntimeException("Could not detect port from '" + s + "' (debuggee is terminated)");
- }
- } catch (InterruptedException e) {
- // ignore
- }
- }
- }
-
- public static void positiveTest(String testName, String allowOpt)
- throws InterruptedException, IOException {
- System.err.println("\nStarting " + testName);
- ArrayList<String> cmd = prepareCmd(allowOpt);
-
- LingeredApp a = LingeredApp.startApp(cmd);
- int res;
- try {
- res = handshake(detectPort(a));
- } finally {
- a.stopApp();
- }
- if (res < 0) {
- throw new RuntimeException(testName + " FAILED");
- }
- System.err.println(testName + " PASSED");
- }
-
- public static void negativeTest(String testName, String allowOpt)
- throws InterruptedException, IOException {
- System.err.println("\nStarting " + testName);
- ArrayList<String> cmd = prepareCmd(allowOpt);
-
- LingeredApp a = LingeredApp.startApp(cmd);
- int res;
- try {
- res = handshake(detectPort(a));
- } finally {
- a.stopApp();
- }
- if (res > 0) {
- System.err.println(testName + ": res=" + res);
- throw new RuntimeException(testName + " FAILED");
- }
- System.err.println(testName + ": returned a negative code as expected: " + res);
- System.err.println(testName + " PASSED");
- }
-
- public static void badAllowOptionTest(String testName, String allowOpt)
- throws InterruptedException, IOException {
- System.err.println("\nStarting " + testName);
- ArrayList<String> cmd = prepareCmd(allowOpt);
-
- LingeredApp a;
- try {
- a = LingeredApp.startApp(cmd);
- } catch (IOException ex) {
- System.err.println(testName + ": caught expected IOException");
- System.err.println(testName + " PASSED");
- return;
- }
- // LingeredApp.startApp is expected to fail, but if not, terminate the app
- a.stopApp();
- throw new RuntimeException(testName + " FAILED");
- }
-
- public static void DefaultTest() throws InterruptedException, IOException {
- // No allow option is the same as the allow option ',allow=*' is passed
- String allowOpt = "";
- positiveTest("DefaultTest", allowOpt);
- }
-
- static void ExplicitDefaultTest() throws InterruptedException, IOException {
- // Explicit permission for connections from everywhere
- String allowOpt = ",allow=*";
- positiveTest("ExplicitDefaultTest" ,allowOpt);
- }
-
- public static void AllowTest() throws InterruptedException, IOException {
- String allowOpt = ",allow=127.0.0.1";
- positiveTest("AllowTest", allowOpt);
- }
-
- public static void MultiAllowTest() throws InterruptedException, IOException {
- String allowOpt = ",allow=127.0.0.1+10.0.0.0/8+172.16.0.0/12+192.168.0.0/24";
- positiveTest("MultiAllowTest", allowOpt);
- }
-
- public static void DenyTest() throws InterruptedException, IOException {
- // Bad allow address
- String allowOpt = ",allow=0.0.0.0";
- negativeTest("DenyTest", allowOpt);
- }
-
- public static void MultiDenyTest() throws InterruptedException, IOException {
- // Wrong separator ';' is used for allow option
- String allowOpt = ",allow=127.0.0.1;192.168.0.0/24";
- badAllowOptionTest("MultiDenyTest", allowOpt);
- }
-
- public static void EmptyAllowOptionTest() throws InterruptedException, IOException {
- // Empty allow option
- String allowOpt = ",allow=";
- badAllowOptionTest("EmptyAllowOptionTest", allowOpt);
- }
-
- public static void ExplicitMultiDefault1Test() throws InterruptedException, IOException {
- // Bad mix of allow option '*' with address value
- String allowOpt = ",allow=*+allow=127.0.0.1";
- badAllowOptionTest("ExplicitMultiDefault1Test", allowOpt);
- }
-
- public static void ExplicitMultiDefault2Test() throws InterruptedException, IOException {
- // Bad mix of allow address value with '*'
- String allowOpt = ",allow=allow=127.0.0.1+*";
- badAllowOptionTest("ExplicitMultiDefault2Test", allowOpt);
- }
-
- public static void main(String[] args) throws Exception {
- DefaultTest();
- ExplicitDefaultTest();
- AllowTest();
- MultiAllowTest();
- DenyTest();
- MultiDenyTest();
- EmptyAllowOptionTest();
- ExplicitMultiDefault1Test();
- ExplicitMultiDefault2Test();
- System.err.println("\nTest PASSED");
- }
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jdi/JdwpAllowTest.java Wed May 15 11:06:33 2019 -0700
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Smoke test for JDWP hardening
+ * @library /test/lib
+ * @run driver JdwpAllowTest
+ */
+
+import java.io.IOException;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+
+import jdk.test.lib.Utils;
+import jdk.test.lib.apps.LingeredApp;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class JdwpAllowTest {
+
+ public static int handshake(int port) throws IOException {
+ // Connect to the debuggee and handshake
+ int res = -1;
+ Socket s = null;
+ try {
+ s = new Socket(localAddr, port);
+ s.getOutputStream().write("JDWP-Handshake".getBytes("UTF-8"));
+ byte[] buffer = new byte[24];
+ res = s.getInputStream().read(buffer);
+ }
+ catch (SocketException ex) {
+ ex.printStackTrace();
+ // pass
+ } finally {
+ if (s != null) {
+ s.close();
+ }
+ }
+ return res;
+ }
+
+ public static ArrayList<String> prepareCmd(String allowOpt) {
+ ArrayList<String> cmd = new ArrayList<>();
+
+ String jdwpArgs = "-agentlib:jdwp=transport=dt_socket,server=y," +
+ "suspend=n,address=*:0"
+ + (allowOpt == null ? "" : ",allow=" + allowOpt);
+ cmd.add(jdwpArgs);
+ return cmd;
+ }
+
+ private static Pattern listenRegexp = Pattern.compile("Listening for transport \\b(.+)\\b at address: \\b(\\d+)\\b");
+ private static int detectPort(LingeredApp app) {
+ long maxWaitTime = System.currentTimeMillis()
+ + Utils.adjustTimeout(10000); // 10 seconds adjusted for TIMEOUT_FACTOR
+ while (true) {
+ String s = app.getProcessStdout();
+ Matcher m = listenRegexp.matcher(s);
+ if (m.find()) {
+ // m.group(1) is transport, m.group(2) is port
+ return Integer.parseInt(m.group(2));
+ }
+ if (System.currentTimeMillis() > maxWaitTime) {
+ throw new RuntimeException("Could not detect port from '" + s + "' (timeout)");
+ }
+ try {
+ if (app.getProcess().waitFor(500, TimeUnit.MILLISECONDS)) {
+ throw new RuntimeException("Could not detect port from '" + s + "' (debuggee is terminated)");
+ }
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ }
+
+ public static void positiveTest(String testName, String allowOpt)
+ throws InterruptedException, IOException {
+ System.err.println("\nStarting " + testName);
+ ArrayList<String> cmd = prepareCmd(allowOpt);
+
+ LingeredApp a = LingeredApp.startApp(cmd);
+ int res;
+ try {
+ res = handshake(detectPort(a));
+ } finally {
+ a.stopApp();
+ }
+ if (res < 0) {
+ throw new RuntimeException(testName + " FAILED");
+ }
+ System.err.println(testName + " PASSED");
+ }
+
+ public static void negativeTest(String testName, String allowOpt)
+ throws InterruptedException, IOException {
+ System.err.println("\nStarting " + testName);
+ ArrayList<String> cmd = prepareCmd(allowOpt);
+
+ LingeredApp a = LingeredApp.startApp(cmd);
+ int res;
+ try {
+ res = handshake(detectPort(a));
+ } finally {
+ a.stopApp();
+ }
+ if (res > 0) {
+ System.err.println(testName + ": res=" + res);
+ throw new RuntimeException(testName + " FAILED");
+ }
+ System.err.println(testName + ": returned a negative code as expected: " + res);
+ System.err.println(testName + " PASSED");
+ }
+
+ public static void badAllowOptionTest(String testName, String allowOpt)
+ throws InterruptedException, IOException {
+ System.err.println("\nStarting " + testName);
+ ArrayList<String> cmd = prepareCmd(allowOpt);
+
+ LingeredApp a;
+ try {
+ a = LingeredApp.startApp(cmd);
+ } catch (IOException ex) {
+ System.err.println(testName + ": caught expected IOException");
+ System.err.println(testName + " PASSED");
+ return;
+ }
+ // LingeredApp.startApp is expected to fail, but if not, terminate the app
+ a.stopApp();
+ throw new RuntimeException(testName + " FAILED");
+ }
+
+ /*
+ * Generate allow address by changing random bit in the local address
+ * and calculate 2 masks (prefix length) - one is matches original local address
+ * and another doesn't.
+ */
+ private static class MaskTest {
+ public final String localAddress;
+ public final String allowAddress;
+ public final int prefixLengthGood;
+ public final int prefixLengthBad;
+
+ public MaskTest(InetAddress addr) throws Exception {
+ localAddress = addr.getHostAddress();
+ byte[] bytes = addr.getAddress();
+ Random r = new Random();
+ // prefix length must be >= 1, so bitToChange must be >= 2
+ int bitToChange = r.nextInt(bytes.length * 8 - 3) + 2;
+ setBit(bytes, bitToChange, !getBit(bytes, bitToChange));
+ // clear rest of the bits for mask address
+ for (int i = bitToChange + 1; i < bytes.length * 8; i++) {
+ setBit(bytes, i, false);
+ }
+ allowAddress = InetAddress.getByAddress(bytes).getHostAddress();
+
+ prefixLengthBad = bitToChange;
+ prefixLengthGood = bitToChange - 1;
+ }
+
+ private static boolean getBit(byte[] bytes, int pos) {
+ return (bytes[pos / 8] & (1 << (7 - (pos % 8)))) != 0;
+ }
+
+ private static void setBit(byte[] bytes, int pos, boolean value) {
+ byte byteValue = (byte)(1 << (7 - (pos % 8)));
+ if (value) {
+ bytes[pos / 8] = (byte)(bytes[pos / 8] | byteValue);
+ } else {
+ bytes[pos / 8] &= (~byteValue);
+ }
+ }
+ }
+
+ private static String localAddr;
+ private static List<MaskTest> maskTests = new LinkedList<>();
+
+ private static void init() throws Exception {
+ InetAddress addrs[] = InetAddress.getAllByName("localhost");
+ if (addrs.length == 0) {
+ throw new RuntimeException("No addresses is returned for 'localhost'");
+ }
+ localAddr = addrs[0].getHostAddress();
+ System.err.println("localhost address: " + localAddr);
+
+ for (int i = 0; i < addrs.length; i++) {
+ maskTests.add(new MaskTest(addrs[i]));
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ init();
+
+ // No allow option is the same as the allow option ',allow=*' is passed
+ positiveTest("DefaultTest", null);
+
+ // Explicit permission for connections from everywhere
+ positiveTest("ExplicitDefaultTest", "*");
+
+ positiveTest("AllowTest", localAddr);
+
+ positiveTest("MultiAllowTest", localAddr + "+10.0.0.0/8+172.16.0.0/12+192.168.0.0/24");
+
+ // Bad allow address
+ negativeTest("DenyTest", "0.0.0.0");
+
+ // Wrong separator ';' is used for allow option
+ badAllowOptionTest("MultiDenyTest", localAddr + ";192.168.0.0/24");
+
+ // Empty allow option
+ badAllowOptionTest("EmptyAllowOptionTest", "");
+
+ // Bad mix of allow option '*' with address value
+ badAllowOptionTest("ExplicitMultiDefault1Test", "*+" + localAddr);
+
+ // Bad mix of allow address value with '*'
+ badAllowOptionTest("ExplicitMultiDefault2Test", localAddr + "+*");
+
+ for (MaskTest test: maskTests) {
+ // override localAddr (to connect to required IPv4 or IPv6 address)
+ localAddr = test.localAddress;
+ positiveTest("PositiveMaskTest(" + test.localAddress + ")",
+ test.allowAddress + "/" + test.prefixLengthGood);
+ positiveTest("NegativeMaskTest(" + test.localAddress + ")",
+ test.allowAddress + "/" + test.prefixLengthBad);
+ }
+
+ System.err.println("\nTest PASSED");
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jdi/JdwpAttachTest.java Wed May 15 11:06:33 2019 -0700
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import com.sun.jdi.Bootstrap;
+import com.sun.jdi.VirtualMachine;
+import com.sun.jdi.connect.Connector;
+import com.sun.jdi.connect.ListeningConnector;
+import jdk.test.lib.apps.LingeredApp;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+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
+ * @bug 8184770
+ * @summary Tests for JDWP agent attach functionality (including IPv6 support)
+ * @library /test/lib
+ *
+ * @build HelloWorld JdwpAttachTest
+ * @run main/othervm JdwpAttachTest
+ */
+public class JdwpAttachTest {
+
+ 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());
+ // listening on "*" should accept connections from all addresses
+ attachTest("*", addr.getHostAddress());
+
+ // test that addresses enclosed in square brackets are supported.
+ if (addr instanceof Inet4Address && !ipv4EnclosedTested) {
+ attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]");
+ ipv4EnclosedTested = true;
+ }
+ if (addr instanceof Inet6Address && !ipv6EnclosedTested) {
+ attachTest("[" + addr.getHostAddress() + "]", "[" + addr.getHostAddress() + "]");
+ ipv6EnclosedTested = true;
+ }
+ }
+
+ // by using "localhost" or empty hostname
+ // 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(), "");
+ }
+ }
+
+ private static void attachTest(String listenAddress, String connectAddresses)
+ throws Exception {
+ log("Starting listening at " + listenAddress);
+ ListeningConnector connector = getListenConnector();
+ Map<String, Connector.Argument> args = connector.defaultArguments();
+ setConnectorArg(args, "localAddress", listenAddress);
+ setConnectorArg(args, "port", "0");
+
+ String actualAddress = connector.startListening(args);
+ String actualPort = actualAddress.substring(actualAddress.lastIndexOf(':') + 1);
+ String port = args.get("port").value();
+ // port from connector.startListening must be the same as values from arguments
+ if (!port.equals(actualPort)) {
+ throw new RuntimeException("values from connector.startListening (" + actualPort
+ + " is not equal to values from arguments (" + port + ")");
+ }
+ log("Listening port: " + port);
+
+ log("Attaching from " + connectAddresses);
+ 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);
+ } finally {
+ connector.stopListening(args);
+ }
+ }
+
+ private static List<InetAddress> getAddresses() {
+ List<InetAddress> result = new LinkedList<>();
+ try {
+ Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+ while (networkInterfaces.hasMoreElements()) {
+ NetworkInterface iface = networkInterfaces.nextElement();
+ try {
+ if (iface.isUp()) {
+ 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.
+ if (addr instanceof Inet6Address) {
+ Inet6Address addr6 = (Inet6Address)addr;
+ if (addr6.getScopedInterface() != null) {
+ continue;
+ }
+ }
+ log(" - (" + addr.getClass().getSimpleName() + ") " + addr.getHostAddress());
+ result.add(addr);
+ }
+ }
+ } catch (SocketException e) {
+ log("Interface " + iface.getDisplayName() + ": failed to get addresses");
+ }
+ }
+ } catch (SocketException e) {
+ log("Interface enumeration error: " + e);
+ }
+ return result;
+ }
+
+ private static String LISTEN_CONNECTOR = "com.sun.jdi.SocketListen";
+
+ private static ListeningConnector getListenConnector() {
+ return (ListeningConnector)getConnector(LISTEN_CONNECTOR);
+ }
+
+ private static Connector getConnector(String name) {
+ List<Connector> connectors = Bootstrap.virtualMachineManager().allConnectors();
+ for (Iterator<Connector> iter = connectors.iterator(); iter.hasNext(); ) {
+ Connector connector = iter.next();
+ if (connector.name().equalsIgnoreCase(name)) {
+ return connector;
+ }
+ }
+ throw new IllegalArgumentException("Connector " + name + " not found");
+ }
+
+ private static void setConnectorArg(Map<String, Connector.Argument> args, String name, String value) {
+ Connector.Argument arg = args.get(name);
+ if (arg == null) {
+ throw new IllegalArgumentException("Argument " + name + " is not defined");
+ }
+ arg.setValue(value);
+ }
+
+ private static void log(Object o) {
+ System.out.println(String.valueOf(o));
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jdi/JdwpListenTest.java Wed May 15 11:06:33 2019 -0700
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import com.sun.jdi.Bootstrap;
+import com.sun.jdi.VirtualMachine;
+import com.sun.jdi.connect.AttachingConnector;
+import com.sun.jdi.connect.Connector;
+import com.sun.jdi.connect.IllegalConnectorArgumentsException;
+import lib.jdb.Debuggee;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * @test
+ * @bug 8184770
+ * @summary Tests for JDWP agent listen functionality (including IPv6 support)
+ * @library /test/lib
+ *
+ * @build HelloWorld JdwpListenTest
+ * @run main/othervm JdwpListenTest
+ */
+public class JdwpListenTest {
+
+ public static void main(String[] args) throws Exception {
+ List<InetAddress> addresses = getAddresses();
+
+ boolean ipv4EnclosedTested = false;
+ boolean ipv6EnclosedTested = false;
+ for (InetAddress listen: addresses) {
+ 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.
+ listenTest(listen.getHostAddress(), attach.getHostAddress(), attach.equals(listen));
+ }
+ // test that addresses enclosed in square brackets are supported.
+ if (listen instanceof Inet4Address && !ipv4EnclosedTested) {
+ listenTest("[" + listen.getHostAddress() + "]", "[" + listen.getHostAddress() + "]", true);
+ ipv4EnclosedTested = true;
+ }
+ if (listen instanceof Inet6Address && !ipv6EnclosedTested) {
+ listenTest("[" + listen.getHostAddress() + "]", "[" + listen.getHostAddress() + "]", true);
+ ipv6EnclosedTested = true;
+ }
+ }
+ // listen on "*" - should be accessible from any address
+ for (InetAddress attach: addresses) {
+ listenTest("*", attach.getHostAddress(), true);
+ }
+ }
+
+ private static void listenTest(String listenAddress, String connectAddress, boolean expectedResult)
+ throws IOException {
+ log("Starting listening debuggee at " + listenAddress);
+ try (Debuggee debuggee = Debuggee.launcher("HelloWorld").setAddress(listenAddress + ":0").launch()) {
+ log("Debuggee is listening on " + listenAddress + ":" + debuggee.getAddress());
+ log("Connecting from " + connectAddress + ", expected: " + (expectedResult ? "SUCCESS" : "FAILURE"));
+ try {
+ VirtualMachine vm = attach(connectAddress, debuggee.getAddress());
+ vm.dispose();
+ if (!expectedResult) {
+ throw new RuntimeException("ERROR: attached successfully");
+ }
+ } catch (IOException ex) {
+ if (expectedResult) {
+ throw new RuntimeException("ERROR: failed to attach", ex);
+ }
+ }
+ }
+ }
+
+ private static List<InetAddress> getAddresses() {
+ List<InetAddress> result = new LinkedList<>();
+ try {
+ Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+ while (networkInterfaces.hasMoreElements()) {
+ NetworkInterface iface = networkInterfaces.nextElement();
+ try {
+ if (iface.isUp()) {
+ 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.
+ if (addr instanceof Inet6Address) {
+ Inet6Address addr6 = (Inet6Address)addr;
+ if (addr6.getScopedInterface() != null) {
+ continue;
+ }
+ }
+ log(" - (" + addr.getClass().getSimpleName() + ") " + addr.getHostAddress());
+ result.add(addr);
+ }
+ }
+ } catch (SocketException e) {
+ log("Interface " + iface.getDisplayName() + ": failed to get addresses");
+ }
+ }
+ } catch (SocketException e) {
+ log("Interface enumeration error: " + e);
+ }
+ return result;
+ }
+
+ private static String ATTACH_CONNECTOR = "com.sun.jdi.SocketAttach";
+ // cache socket attaching connector
+ private static AttachingConnector attachingConnector;
+
+ private static VirtualMachine attach(String address, String port) throws IOException {
+ if (attachingConnector == null) {
+ attachingConnector = (AttachingConnector)getConnector(ATTACH_CONNECTOR);
+ }
+ Map<String, Connector.Argument> args = attachingConnector.defaultArguments();
+ setConnectorArg(args, "hostname", address);
+ setConnectorArg(args, "port", port);
+ try {
+ return attachingConnector.attach(args);
+ } catch (IllegalConnectorArgumentsException e) {
+ // unexpected.. wrap in RuntimeException
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Connector getConnector(String name) {
+ List<Connector> connectors = Bootstrap.virtualMachineManager().allConnectors();
+ for (Iterator<Connector> iter = connectors.iterator(); iter.hasNext(); ) {
+ Connector connector = iter.next();
+ if (connector.name().equalsIgnoreCase(name)) {
+ return connector;
+ }
+ }
+ throw new IllegalArgumentException("Connector " + name + " not found");
+ }
+
+ private static void setConnectorArg(Map<String, Connector.Argument> args, String name, String value) {
+ Connector.Argument arg = args.get(name);
+ if (arg == null) {
+ throw new IllegalArgumentException("Argument " + name + " is not defined");
+ }
+ arg.setValue(value);
+ }
+
+ private static void log(Object o) {
+ System.out.println(String.valueOf(o));
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jdi/JdwpNetProps.java Wed May 15 11:06:33 2019 -0700
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import com.sun.jdi.Bootstrap;
+import com.sun.jdi.VirtualMachine;
+import com.sun.jdi.connect.AttachingConnector;
+import com.sun.jdi.connect.Connector;
+import com.sun.jdi.connect.IllegalConnectorArgumentsException;
+import lib.jdb.Debuggee;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/*
+ * @test
+ * @bug 8184770
+ * @summary Tests that JDWP agent honors jdk net properties
+ * @library /test/lib
+ *
+ * @build HelloWorld JdwpNetProps
+ * @run main/othervm JdwpNetProps
+ */
+public class JdwpNetProps {
+
+ public static void main(String[] args) throws Exception {
+ InetAddress addrs[] = InetAddress.getAllByName("localhost");
+ InetAddress ipv4Address = null;
+ InetAddress ipv6Address = null;
+ for (int i = 0; i < addrs.length; i++) {
+ if (addrs[i] instanceof Inet4Address) {
+ ipv4Address = addrs[i];
+ } else if (addrs[i] instanceof Inet6Address) {
+ ipv6Address = addrs[i];
+ }
+ }
+
+ if (ipv4Address != null) {
+ new ListenTest("localhost", ipv4Address)
+ .preferIPv4Stack(true)
+ .run(TestResult.Success);
+ new ListenTest("localhost", ipv4Address)
+ .preferIPv4Stack(false)
+ .run(TestResult.Success);
+ if (ipv6Address != null) {
+ // - only IPv4, so connection prom IPv6 should fail
+ new ListenTest("localhost", ipv6Address)
+ .preferIPv4Stack(true)
+ .preferIPv6Addresses(true)
+ .run(TestResult.AttachFailed);
+ // - listen on IPv4
+ new ListenTest("localhost", ipv6Address)
+ .preferIPv6Addresses(false)
+ .run(TestResult.AttachFailed);
+ // - listen on IPv6
+ new ListenTest("localhost", ipv6Address)
+ .preferIPv6Addresses(true)
+ .run(TestResult.Success);
+ }
+ } else {
+ // IPv6-only system - expected to fail on IPv4 address
+ new ListenTest("localhost", ipv6Address)
+ .preferIPv4Stack(true)
+ .run(TestResult.ListenFailed);
+ }
+ }
+
+ private enum TestResult {
+ Success,
+ ListenFailed,
+ AttachFailed
+ }
+
+ private static class ListenTest {
+ private final String listenAddress;
+ private final InetAddress connectAddress;
+ private Boolean preferIPv4Stack;
+ private Boolean preferIPv6Addresses;
+ public ListenTest(String listenAddress, InetAddress connectAddress) {
+ this.listenAddress = listenAddress;
+ this.connectAddress = connectAddress;
+ }
+ public ListenTest preferIPv4Stack(Boolean value) {
+ preferIPv4Stack = value;
+ return this;
+ }
+ public ListenTest preferIPv6Addresses(Boolean value) {
+ preferIPv6Addresses = value;
+ return this;
+ }
+
+ public void run(TestResult expectedResult) throws Exception {
+ List<String> options = new LinkedList<>();
+ if (preferIPv4Stack != null) {
+ options.add("-Djava.net.preferIPv4Stack=" + preferIPv4Stack.toString());
+ }
+ if (preferIPv6Addresses != null) {
+ options.add("-Djava.net.preferIPv6Addresses=" + preferIPv6Addresses.toString());
+ }
+ log("Starting listening debuggee at " + listenAddress
+ + (expectedResult == TestResult.ListenFailed ? ": expected to fail" : ""));
+ Exception error = null;
+ try (Debuggee debuggee = Debuggee.launcher("HelloWorld")
+ .setAddress(listenAddress + ":0")
+ .addOptions(options).launch()) {
+ log("Debuggee is listening on " + listenAddress + ":" + debuggee.getAddress());
+ log("Connecting from " + connectAddress.getHostAddress()
+ + ", expected: " + (expectedResult == TestResult.Success ? "Success" : "Failure"));
+ try {
+ VirtualMachine vm = attach(connectAddress.getHostAddress(), debuggee.getAddress());
+ vm.dispose();
+ if (expectedResult == TestResult.Success) {
+ log("Attached successfully (as expected)");
+ } else {
+ error = new RuntimeException("ERROR: attached successfully");
+ }
+ } catch (Exception ex) {
+ if (expectedResult == TestResult.AttachFailed) {
+ log("Attach failed (as expected)");
+ } else {
+ error = new RuntimeException("ERROR: failed to attach", ex);
+ }
+ }
+ } catch (Exception ex) {
+ if (expectedResult == TestResult.ListenFailed) {
+ log("Listen failed (as expected)");
+ } else {
+ error = new RuntimeException("ERROR: listen failed", ex);
+ }
+ }
+ if (error != null) {
+ throw error;
+ }
+ }
+ }
+
+ private static String ATTACH_CONNECTOR = "com.sun.jdi.SocketAttach";
+ // cache socket attaching connector
+ private static AttachingConnector attachingConnector;
+
+ private static VirtualMachine attach(String address, String port) throws IOException {
+ if (attachingConnector == null) {
+ attachingConnector = (AttachingConnector)getConnector(ATTACH_CONNECTOR);
+ }
+ Map<String, Connector.Argument> args = attachingConnector.defaultArguments();
+ setConnectorArg(args, "hostname", address);
+ setConnectorArg(args, "port", port);
+ try {
+ return attachingConnector.attach(args);
+ } catch (IllegalConnectorArgumentsException e) {
+ // unexpected.. wrap in RuntimeException
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Connector getConnector(String name) {
+ List<Connector> connectors = Bootstrap.virtualMachineManager().allConnectors();
+ for (Iterator<Connector> iter = connectors.iterator(); iter.hasNext(); ) {
+ Connector connector = iter.next();
+ if (connector.name().equalsIgnoreCase(name)) {
+ return connector;
+ }
+ }
+ throw new IllegalArgumentException("Connector " + name + " not found");
+ }
+
+ private static void setConnectorArg(Map<String, Connector.Argument> args, String name, String value) {
+ Connector.Argument arg = args.get(name);
+ if (arg == null) {
+ throw new IllegalArgumentException("Argument " + name + " is not defined");
+ }
+ arg.setValue(value);
+ }
+
+ private static void log(Object o) {
+ System.out.println(String.valueOf(o));
+ }
+
+}