8184770: JDWP support for IPv6
Reviewed-by: sspitsyn, chegar
This commit is contained in:
parent
23301277c5
commit
e18cecafb9
@ -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 @@ public class SocketListeningConnector extends GenericListeningConnector {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 @@ public class SocketTransportService extends TransportService {
|
||||
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 @@ public class SocketTransportService extends TransportService {
|
||||
};
|
||||
}
|
||||
|
||||
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 @@ public class SocketTransportService extends TransportService {
|
||||
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 class SocketTransportService extends TransportService {
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 @@ static jint send_fully(int, char *, int);
|
||||
|
||||
/* 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 AllowedPeerInfo _peers[MAX_PEER_ENTRIES];
|
||||
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 @@ getLastError() {
|
||||
|
||||
/* Set options common to client and server sides */
|
||||
static jdwpTransportError
|
||||
setOptionsCommon(int fd)
|
||||
setOptionsCommon(int domain, int fd)
|
||||
{
|
||||
jvalue dontcare;
|
||||
int err;
|
||||
|
||||
dontcare.i = 0; /* keep compiler happy */
|
||||
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 @@ handshake(int fd, jlong timeout) {
|
||||
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 @@ getPortNumber(const char *s_port) {
|
||||
return n;
|
||||
}
|
||||
|
||||
static jdwpTransportError
|
||||
parseAddress(const char *address, struct sockaddr_in *sa) {
|
||||
char *colon;
|
||||
int port;
|
||||
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));
|
||||
}
|
||||
|
||||
memset((void *)sa, 0, sizeof(struct sockaddr_in));
|
||||
sa->sin_family = AF_INET;
|
||||
/*
|
||||
* Result must be released with dbgsysFreeAddrInfo.
|
||||
*/
|
||||
static jdwpTransportError
|
||||
parseAddress(const char *address, struct addrinfo **result) {
|
||||
const char *colon;
|
||||
size_t hostLen;
|
||||
char *host = NULL;
|
||||
const char *port;
|
||||
struct addrinfo hints;
|
||||
int res;
|
||||
|
||||
*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);
|
||||
|
||||
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) {
|
||||
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
|
||||
|
||||
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;
|
||||
strncpy(host, address, hostLen);
|
||||
host[hostLen] = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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
|
||||
|
||||
while (1) {
|
||||
if (*s == '.') {
|
||||
++i;
|
||||
++s;
|
||||
continue;
|
||||
}
|
||||
if (*s == 0 || *s == '+' || *s == '/') {
|
||||
break;
|
||||
}
|
||||
if (*s < '0' || *s > '9') {
|
||||
return instr;
|
||||
}
|
||||
t[i] = (t[i] * 10) + (*s - '0');
|
||||
++s;
|
||||
}
|
||||
|
||||
*ip = *(uint32_t*)(t);
|
||||
return s;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
while (1) {
|
||||
if (*s == 0 || *s == '+') {
|
||||
break;
|
||||
}
|
||||
if (*s < '0' || *s > '9') {
|
||||
return instr;
|
||||
}
|
||||
m = (m * 10) + (*s - '0');
|
||||
++s;
|
||||
}
|
||||
|
||||
if (m == 0 || m > 32) {
|
||||
// Drop invalid input
|
||||
return instr;
|
||||
}
|
||||
|
||||
*mask = htonl((uint32_t)(~0) << (32 - m));
|
||||
return s;
|
||||
}
|
||||
|
||||
static int
|
||||
ip_in_subnet(uint32_t subnet, uint32_t mask, uint32_t ipaddr) {
|
||||
return (ipaddr & mask) == subnet;
|
||||
// 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
|
||||
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;
|
||||
parseAllowedAddr(const char *buffer, struct in6_addr *result, int *isIPv4) {
|
||||
struct addrinfo hints;
|
||||
struct addrinfo *addrInfo = NULL;
|
||||
int err;
|
||||
|
||||
while (1) {
|
||||
s = ip_s2u(p, &ip);
|
||||
if (s == p) {
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
prefixLen = prefixLen * 10 + (*buffer - '0');
|
||||
if (prefixLen > maxValue) { // avoid overflow
|
||||
return JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal implementation of parseAllowedPeers (requires writable buffer).
|
||||
*/
|
||||
static jdwpTransportError
|
||||
parseAllowedPeersInternal(char *buffer) {
|
||||
char *next;
|
||||
int isIPv4 = 0;
|
||||
|
||||
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", s);
|
||||
fprintf(stderr, "Error in allow option: '%s'\n", buffer);
|
||||
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
|
||||
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", s);
|
||||
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 {
|
||||
// reset netmask
|
||||
mask = 0xFFFFFFFF;
|
||||
memset(&(_peers[_peers_cnt].netmask), 0xFF, sizeof(_peers[_peers_cnt].netmask));
|
||||
}
|
||||
_peers_cnt++;
|
||||
buffer = next;
|
||||
} while (next != NULL);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
return JDWPTRANSPORT_ERROR_NONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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';
|
||||
|
||||
jdwpTransportError err = parseAllowedPeersInternal(buffer);
|
||||
|
||||
(*callback->free)(buffer);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
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)) {
|
||||
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 1;
|
||||
}
|
||||
|
||||
static int
|
||||
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 @@ socketTransport_getCapabilities(jdwpTransportEnv* env,
|
||||
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";
|
||||
}
|
||||
|
||||
err = parseAddress(address, &sa);
|
||||
if (err != JDWPTRANSPORT_ERROR_NONE) {
|
||||
return err;
|
||||
}
|
||||
|
||||
serverSocketFD = dbgsysSocket(AF_INET, SOCK_STREAM, 0);
|
||||
if (serverSocketFD < 0) {
|
||||
*socket = dbgsysSocket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (*socket < 0) {
|
||||
RETURN_IO_ERROR("socket creation failed");
|
||||
}
|
||||
|
||||
err = setOptionsCommon(serverSocketFD);
|
||||
err = setOptionsCommon(ai->ai_family, *socket);
|
||||
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) {
|
||||
@ -561,13 +630,63 @@ socketTransport_startListening(jdwpTransportEnv* env, const char* address,
|
||||
return JDWPTRANSPORT_ERROR_NONE;
|
||||
}
|
||||
|
||||
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 @@ socketTransport_accept(jdwpTransportEnv* env, jlong acceptTimeout, jlong handsha
|
||||
/*
|
||||
* 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 @@ socketTransport_accept(jdwpTransportEnv* env, jlong acceptTimeout, jlong handsha
|
||||
* 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 @@ socketTransport_stopListening(jdwpTransportEnv *env)
|
||||
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 @@ socketTransport_attach(jdwpTransportEnv* env, const char* addressString, jlong a
|
||||
* 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 @@ socketTransport_attach(jdwpTransportEnv* env, const char* addressString, jlong a
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ socketTransport_setConfiguration(jdwpTransportEnv* env, jdwpTransportConfigurati
|
||||
"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 @@ socketTransport_setConfiguration(jdwpTransportEnv* env, jdwpTransportConfigurati
|
||||
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 @@ jdwpTransport_OnLoad(JavaVM *vm, jdwpTransportCallback* cbTablePtr,
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
@ -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 dbgsysRecvFrom(int fd, char *buf, size_t nBytes, int flags, struct sockaddr
|
||||
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);
|
||||
|
@ -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 @@ dbgsysSend(int fd, char *buf, size_t nBytes, int flags) {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -163,11 +168,6 @@ dbgsysBind(int fd, struct sockaddr *name, socklen_t namelen) {
|
||||
return bind(fd, name, namelen);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
dbgsysInetAddr(const char* cp) {
|
||||
return (uint32_t)inet_addr(cp);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
dbgsysHostToNetworkLong(uint32_t hostlong) {
|
||||
return htonl(hostlong);
|
||||
|
@ -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 @@ dbgsysSend(int fd, char *buf, size_t nBytes, int flags) {
|
||||
}
|
||||
|
||||
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 @@ int
|
||||
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;
|
||||
}
|
||||
@ -240,15 +245,6 @@ dbgsysBind(int fd, struct sockaddr *name, socklen_t namelen) {
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
@ -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.io.*;
|
||||
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 @@ public class startlis001 {
|
||||
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 @@ public class startlis001 {
|
||||
|
||||
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 @@ public class startlis001 {
|
||||
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");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* 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
|
||||
@ -25,11 +25,12 @@
|
||||
* @test
|
||||
* @summary Smoke test for JDWP hardening
|
||||
* @library /test/lib
|
||||
* @run driver BasicJDWPConnectionTest
|
||||
* @run driver JdwpAllowTest
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
|
||||
@ -37,24 +38,28 @@ 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 BasicJDWPConnectionTest {
|
||||
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("localhost", port);
|
||||
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) {
|
||||
@ -68,7 +73,8 @@ public class BasicJDWPConnectionTest {
|
||||
ArrayList<String> cmd = new ArrayList<>();
|
||||
|
||||
String jdwpArgs = "-agentlib:jdwp=transport=dt_socket,server=y," +
|
||||
"suspend=n,address=*:0" + allowOpt;
|
||||
"suspend=n,address=*:0"
|
||||
+ (allowOpt == null ? "" : ",allow=" + allowOpt);
|
||||
cmd.add(jdwpArgs);
|
||||
return cmd;
|
||||
}
|
||||
@ -153,68 +159,102 @@ public class BasicJDWPConnectionTest {
|
||||
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);
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ExplicitDefaultTest() throws InterruptedException, IOException {
|
||||
// Explicit permission for connections from everywhere
|
||||
String allowOpt = ",allow=*";
|
||||
positiveTest("ExplicitDefaultTest" ,allowOpt);
|
||||
}
|
||||
private static String localAddr;
|
||||
private static List<MaskTest> maskTests = new LinkedList<>();
|
||||
|
||||
public static void AllowTest() throws InterruptedException, IOException {
|
||||
String allowOpt = ",allow=127.0.0.1";
|
||||
positiveTest("AllowTest", allowOpt);
|
||||
}
|
||||
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);
|
||||
|
||||
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);
|
||||
for (int i = 0; i < addrs.length; i++) {
|
||||
maskTests.add(new MaskTest(addrs[i]));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
DefaultTest();
|
||||
ExplicitDefaultTest();
|
||||
AllowTest();
|
||||
MultiAllowTest();
|
||||
DenyTest();
|
||||
MultiDenyTest();
|
||||
EmptyAllowOptionTest();
|
||||
ExplicitMultiDefault1Test();
|
||||
ExplicitMultiDefault2Test();
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
191
test/jdk/com/sun/jdi/JdwpAttachTest.java
Normal file
191
test/jdk/com/sun/jdi/JdwpAttachTest.java
Normal file
@ -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));
|
||||
}
|
||||
|
||||
}
|
177
test/jdk/com/sun/jdi/JdwpListenTest.java
Normal file
177
test/jdk/com/sun/jdi/JdwpListenTest.java
Normal file
@ -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));
|
||||
}
|
||||
|
||||
}
|
204
test/jdk/com/sun/jdi/JdwpNetProps.java
Normal file
204
test/jdk/com/sun/jdi/JdwpNetProps.java
Normal file
@ -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));
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user