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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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)) {
|
if (isWildcardPort(args)) {
|
||||||
String[] address = listener.address().split(":");
|
String[] address = listener.address().split(":");
|
||||||
if (address.length > 1) {
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -79,12 +79,7 @@ public class SocketTransportService extends TransportService {
|
|||||||
try {
|
try {
|
||||||
address = InetAddress.getLocalHost();
|
address = InetAddress.getLocalHost();
|
||||||
} catch (UnknownHostException uhe) {
|
} catch (UnknownHostException uhe) {
|
||||||
byte[] loopback = {0x7f,0x00,0x00,0x01};
|
address = InetAddress.getLoopbackAddress();
|
||||||
try {
|
|
||||||
address = InetAddress.getByAddress("127.0.0.1", loopback);
|
|
||||||
} catch (UnknownHostException x) {
|
|
||||||
throw new InternalError("unable to get local hostname");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
* Attach to the specified address with optional attach and handshake
|
||||||
* timeout.
|
* timeout.
|
||||||
@ -215,31 +248,14 @@ public class SocketTransportService extends TransportService {
|
|||||||
throw new IllegalArgumentException("timeout is negative");
|
throw new IllegalArgumentException("timeout is negative");
|
||||||
}
|
}
|
||||||
|
|
||||||
int splitIndex = address.indexOf(':');
|
HostPort hostPort = HostPort.parse(address);
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// open TCP connection to VM
|
// 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();
|
Socket s = new Socket();
|
||||||
try {
|
try {
|
||||||
s.connect(sa, (int)attachTimeout);
|
s.connect(sa, (int)attachTimeout);
|
||||||
@ -290,26 +306,8 @@ public class SocketTransportService extends TransportService {
|
|||||||
*/
|
*/
|
||||||
public ListenKey startListening(String address) throws IOException {
|
public ListenKey startListening(String address) throws IOException {
|
||||||
// use ephemeral port if address isn't specified.
|
// use ephemeral port if address isn't specified.
|
||||||
if (address == null || address.length() == 0) {
|
HostPort hostPort = HostPort.parse((address == null || address.isEmpty()) ? "0" : address);
|
||||||
address = "0";
|
return startListening(hostPort.host, hostPort.port);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.
|
* Service Provider Interface - see src/share/javavm/export/jdwpTransport.h.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static int serverSocketFD;
|
static int serverSocketFD = -1;
|
||||||
static int socketFD = -1;
|
static int socketFD = -1;
|
||||||
static jdwpTransportCallback *callback;
|
static jdwpTransportCallback *callback;
|
||||||
static JavaVM *jvm;
|
static JavaVM *jvm;
|
||||||
@ -78,8 +78,9 @@ static jint send_fully(int, char *, int);
|
|||||||
|
|
||||||
/* version >= JDWPTRANSPORT_VERSION_1_1 */
|
/* version >= JDWPTRANSPORT_VERSION_1_1 */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t subnet;
|
/* subnet and mask are stored as IPv6 addresses, IPv4 is stored as mapped IPv6 */
|
||||||
uint32_t netmask;
|
struct in6_addr subnet;
|
||||||
|
struct in6_addr netmask;
|
||||||
} AllowedPeerInfo;
|
} AllowedPeerInfo;
|
||||||
|
|
||||||
#define STR(x) #x
|
#define STR(x) #x
|
||||||
@ -89,6 +90,9 @@ static AllowedPeerInfo _peers[MAX_PEER_ENTRIES];
|
|||||||
static int _peers_cnt = 0;
|
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.
|
* Record the last error for this thread.
|
||||||
*/
|
*/
|
||||||
@ -137,13 +141,19 @@ getLastError() {
|
|||||||
|
|
||||||
/* Set options common to client and server sides */
|
/* Set options common to client and server sides */
|
||||||
static jdwpTransportError
|
static jdwpTransportError
|
||||||
setOptionsCommon(int fd)
|
setOptionsCommon(int domain, int fd)
|
||||||
{
|
{
|
||||||
jvalue dontcare;
|
jvalue dontcare;
|
||||||
int err;
|
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);
|
err = dbgsysSetSocketOption(fd, TCP_NODELAY, JNI_TRUE, dontcare);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
RETURN_IO_ERROR("setsockopt TCPNODELAY failed");
|
RETURN_IO_ERROR("setsockopt TCPNODELAY failed");
|
||||||
@ -223,31 +233,6 @@ handshake(int fd, jlong timeout) {
|
|||||||
return JDWPTRANSPORT_ERROR_NONE;
|
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
|
static int
|
||||||
getPortNumber(const char *s_port) {
|
getPortNumber(const char *s_port) {
|
||||||
u_long n;
|
u_long n;
|
||||||
@ -274,199 +259,290 @@ getPortNumber(const char *s_port) {
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jdwpTransportError
|
static unsigned short getPort(struct sockaddr *sa)
|
||||||
parseAddress(const char *address, struct sockaddr_in *sa) {
|
{
|
||||||
char *colon;
|
return dbgsysNetworkToHostShort(sa->sa_family == AF_INET
|
||||||
int port;
|
? (((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;
|
|
||||||
|
|
||||||
/* check for host:port or port */
|
|
||||||
colon = strchr(address, ':');
|
|
||||||
port = getPortNumber((colon == NULL) ? address : colon +1);
|
|
||||||
if (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) {
|
|
||||||
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.
|
* Result must be released with dbgsysFreeAddrInfo.
|
||||||
* If not then try to resolve it.
|
|
||||||
*/
|
*/
|
||||||
addr = dbgsysInetAddr(hostname);
|
static jdwpTransportError
|
||||||
if (addr == 0xffffffff) {
|
parseAddress(const char *address, struct addrinfo **result) {
|
||||||
|
const char *colon;
|
||||||
|
size_t hostLen;
|
||||||
|
char *host = NULL;
|
||||||
|
const char *port;
|
||||||
struct addrinfo hints;
|
struct addrinfo hints;
|
||||||
struct addrinfo *results = NULL;
|
int res;
|
||||||
|
|
||||||
|
*result = NULL;
|
||||||
|
|
||||||
|
/* check for host:port or port */
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
memset (&hints, 0, sizeof(hints));
|
memset (&hints, 0, sizeof(hints));
|
||||||
hints.ai_family = AF_INET;
|
hints.ai_family = allowOnlyIPv4 ? AF_INET : AF_UNSPEC;
|
||||||
hints.ai_socktype = SOCK_STREAM;
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
hints.ai_protocol = IPPROTO_TCP;
|
hints.ai_protocol = IPPROTO_TCP;
|
||||||
|
hints.ai_flags = AI_NUMERICSERV; // port must be a number
|
||||||
|
|
||||||
ai = dbgsysGetAddrInfo(hostname, NULL, &hints, &results);
|
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");
|
||||||
|
}
|
||||||
|
strncpy(host, address, hostLen);
|
||||||
|
host[hostLen] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
if (ai != 0) {
|
res = dbgsysGetAddrInfo(host, port, &hints, result);
|
||||||
/* don't use RETURN_IO_ERROR as unknown host is normal */
|
if (host != NULL) {
|
||||||
setLastError(0, "getaddrinfo: unknown host");
|
(*callback->free)(host);
|
||||||
(*callback->free)(buf);
|
}
|
||||||
|
if (res != 0) {
|
||||||
|
setLastError(res, "getaddrinfo: unknown host");
|
||||||
return JDWPTRANSPORT_ERROR_IO_ERROR;
|
return JDWPTRANSPORT_ERROR_IO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* lookup was successful */
|
return JDWPTRANSPORT_ERROR_NONE;
|
||||||
sa->sin_addr = ((struct sockaddr_in *)results->ai_addr)->sin_addr;
|
|
||||||
freeaddrinfo(results);
|
|
||||||
} else {
|
|
||||||
sa->sin_addr.s_addr = addr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(*callback->free)(buf);
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return JDWPTRANSPORT_ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
/*
|
||||||
ip_s2u(const char *instr, uint32_t *ip) {
|
* Internal implementation of parseAllowedPeers (requires writable buffer).
|
||||||
// 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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
static jdwpTransportError
|
static jdwpTransportError
|
||||||
parseAllowedPeers(const char *allowed_peers) {
|
parseAllowedPeersInternal(char *buffer) {
|
||||||
// Build a list of allowed peers from char string
|
char *next;
|
||||||
// of format 192.168.0.10+192.168.0.0/24
|
int isIPv4 = 0;
|
||||||
const char *s = NULL;
|
|
||||||
const char *p = allowed_peers;
|
|
||||||
uint32_t ip = 0;
|
|
||||||
uint32_t mask = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
while (1) {
|
do {
|
||||||
s = ip_s2u(p, &ip);
|
char *mask = NULL;
|
||||||
if (s == p) {
|
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;
|
_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,
|
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
|
||||||
"invalid IP address in allow option");
|
"invalid IP address in allow option");
|
||||||
}
|
}
|
||||||
|
if (mask != NULL) {
|
||||||
if (*s == '/') {
|
if (parseAllowedMask(mask, isIPv4, &(_peers[_peers_cnt].netmask)) != JDWPTRANSPORT_ERROR_NONE) {
|
||||||
// netmask specified
|
|
||||||
s = mask_s2u(s + 1, &mask);
|
|
||||||
if (*(s - 1) == '/') {
|
|
||||||
// Input is not consumed, something bad happened
|
|
||||||
_peers_cnt = 0;
|
_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,
|
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT,
|
||||||
"invalid netmask in allow option");
|
"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 {
|
} else {
|
||||||
// reset netmask
|
memset(&(_peers[_peers_cnt].netmask), 0xFF, sizeof(_peers[_peers_cnt].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++;
|
_peers_cnt++;
|
||||||
if (*s == 0) {
|
buffer = next;
|
||||||
// end of options
|
} while (next != NULL);
|
||||||
break;
|
|
||||||
}
|
|
||||||
// advance to next IP block
|
|
||||||
p = s + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JDWPTRANSPORT_ERROR_NONE;
|
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
|
static int
|
||||||
isPeerAllowed(struct sockaddr_in *peer) {
|
isAddressInSubnet(const struct in6_addr *address, const struct in6_addr *subnet, const struct in6_addr *mask) {
|
||||||
int i;
|
for (size_t i = 0; i < sizeof(struct in6_addr); i++) {
|
||||||
for (i = 0; i < _peers_cnt; ++i) {
|
if ((address->s6_addr[i] & mask->s6_addr[i]) != subnet->s6_addr[i]) {
|
||||||
int peer_ip = peer->sin_addr.s_addr;
|
return 0;
|
||||||
if (ip_in_subnet(_peers[i].subnet, _peers[i].netmask, peer_ip)) {
|
}
|
||||||
|
}
|
||||||
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -490,65 +566,58 @@ socketTransport_getCapabilities(jdwpTransportEnv* env,
|
|||||||
return JDWPTRANSPORT_ERROR_NONE;
|
return JDWPTRANSPORT_ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
static jdwpTransportError JNICALL
|
* Starts listening on the specified addrinfo,
|
||||||
socketTransport_startListening(jdwpTransportEnv* env, const char* address,
|
* returns listening socket and actual listening port.
|
||||||
char** actualAddress)
|
* 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;
|
int err;
|
||||||
|
|
||||||
memset((void *)&sa,0,sizeof(struct sockaddr_in));
|
*socket = dbgsysSocket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
||||||
sa.sin_family = AF_INET;
|
if (*socket < 0) {
|
||||||
|
|
||||||
/* 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) {
|
|
||||||
RETURN_IO_ERROR("socket creation failed");
|
RETURN_IO_ERROR("socket creation failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setOptionsCommon(serverSocketFD);
|
err = setOptionsCommon(ai->ai_family, *socket);
|
||||||
if (err) {
|
if (err) {
|
||||||
return 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
|
* Only need SO_REUSEADDR if we're using a fixed port. If we
|
||||||
* start seeing EADDRINUSE due to collisions in free ports
|
* start seeing EADDRINUSE due to collisions in free ports
|
||||||
* then we should retry the dbgsysBind() a few times.
|
* then we should retry the dbgsysBind() a few times.
|
||||||
*/
|
*/
|
||||||
err = setReuseAddrOption(serverSocketFD);
|
err = setReuseAddrOption(*socket);
|
||||||
if (err) {
|
if (err) {
|
||||||
return 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) {
|
if (err < 0) {
|
||||||
RETURN_IO_ERROR("bind failed");
|
RETURN_IO_ERROR("bind failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dbgsysListen(serverSocketFD, 1);
|
err = dbgsysListen(*socket, 1); // only 1 debugger can attach
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
RETURN_IO_ERROR("listen failed");
|
RETURN_IO_ERROR("listen failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
char buf[20];
|
char buf[20];
|
||||||
socklen_t len = sizeof(sa);
|
struct sockaddr_storage addr;
|
||||||
|
socklen_t len = sizeof(addr);
|
||||||
jint portNum;
|
jint portNum;
|
||||||
err = dbgsysGetSocketName(serverSocketFD,
|
err = dbgsysGetSocketName(*socket, (struct sockaddr *)&addr, &len);
|
||||||
(struct sockaddr *)&sa, &len);
|
if (err != 0) {
|
||||||
portNum = dbgsysNetworkToHostShort(sa.sin_port);
|
RETURN_IO_ERROR("getsockname failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
portNum = getPort((struct sockaddr *)&addr);
|
||||||
sprintf(buf, "%d", portNum);
|
sprintf(buf, "%d", portNum);
|
||||||
*actualAddress = (*callback->alloc)((int)strlen(buf) + 1);
|
*actualAddress = (*callback->alloc)((int)strlen(buf) + 1);
|
||||||
if (*actualAddress == NULL) {
|
if (*actualAddress == NULL) {
|
||||||
@ -561,13 +630,63 @@ socketTransport_startListening(jdwpTransportEnv* env, const char* address,
|
|||||||
return JDWPTRANSPORT_ERROR_NONE;
|
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
|
static jdwpTransportError JNICALL
|
||||||
socketTransport_accept(jdwpTransportEnv* env, jlong acceptTimeout, jlong handshakeTimeout)
|
socketTransport_accept(jdwpTransportEnv* env, jlong acceptTimeout, jlong handshakeTimeout)
|
||||||
{
|
{
|
||||||
socklen_t socketLen;
|
|
||||||
int err = JDWPTRANSPORT_ERROR_NONE;
|
int err = JDWPTRANSPORT_ERROR_NONE;
|
||||||
struct sockaddr_in socket;
|
struct sockaddr_storage clientAddr;
|
||||||
jlong startTime = (jlong)0;
|
socklen_t clientAddrLen;
|
||||||
|
jlong startTime = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use a default handshake timeout if not specified - this avoids an indefinite
|
* 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
|
* Accept the connection
|
||||||
*/
|
*/
|
||||||
memset((void *)&socket,0,sizeof(struct sockaddr_in));
|
clientAddrLen = sizeof(clientAddr);
|
||||||
socketLen = sizeof(socket);
|
|
||||||
socketFD = dbgsysAccept(serverSocketFD,
|
socketFD = dbgsysAccept(serverSocketFD,
|
||||||
(struct sockaddr *)&socket,
|
(struct sockaddr *)&clientAddr,
|
||||||
&socketLen);
|
&clientAddrLen);
|
||||||
/* set the last error here as could be overridden by configureBlocking */
|
/* set the last error here as could be overridden by configureBlocking */
|
||||||
if (socketFD < 0) {
|
if (socketFD < 0) {
|
||||||
setLastError(JDWPTRANSPORT_ERROR_IO_ERROR, "accept failed");
|
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.
|
* Verify that peer is allowed to connect.
|
||||||
*/
|
*/
|
||||||
if (_peers_cnt > 0) {
|
if (_peers_cnt > 0) {
|
||||||
if (!isPeerAllowed(&socket)) {
|
if (!isPeerAllowed(&clientAddr)) {
|
||||||
char ebuf[64] = { 0 };
|
char ebuf[64] = { 0 };
|
||||||
char buf[INET_ADDRSTRLEN] = { 0 };
|
char addrStr[INET_ADDRSTRLEN] = { 0 };
|
||||||
const char* addr_str = inet_ntop(AF_INET, &(socket.sin_addr), buf, INET_ADDRSTRLEN);
|
int err2 = getnameinfo((struct sockaddr *)&clientAddr, clientAddrLen,
|
||||||
|
addrStr, sizeof(addrStr), NULL, 0,
|
||||||
|
NI_NUMERICHOST);
|
||||||
sprintf(ebuf, "ERROR: Peer not allowed to connect: %s\n",
|
sprintf(ebuf, "ERROR: Peer not allowed to connect: %s\n",
|
||||||
(addr_str == NULL) ? "<bad address>" : addr_str);
|
(err2 != 0) ? "<bad address>" : addrStr);
|
||||||
dbgsysSocketClose(socketFD);
|
dbgsysSocketClose(socketFD);
|
||||||
socketFD = -1;
|
socketFD = -1;
|
||||||
err = JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
|
err = JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT;
|
||||||
@ -686,28 +806,19 @@ socketTransport_stopListening(jdwpTransportEnv *env)
|
|||||||
return JDWPTRANSPORT_ERROR_NONE;
|
return JDWPTRANSPORT_ERROR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static jdwpTransportError JNICALL
|
/*
|
||||||
socketTransport_attach(jdwpTransportEnv* env, const char* addressString, jlong attachTimeout,
|
* Tries to connect to the specified addrinfo, returns connected socket.
|
||||||
jlong handshakeTimeout)
|
* If the function fails and returned socket != -1, the socket should be closed.
|
||||||
{
|
*/
|
||||||
struct sockaddr_in sa;
|
static jdwpTransportError connectToAddr(struct addrinfo *ai, jlong timeout, int *socket) {
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
if (addressString == NULL || addressString[0] == '\0') {
|
*socket = dbgsysSocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
||||||
RETURN_ERROR(JDWPTRANSPORT_ERROR_ILLEGAL_ARGUMENT, "address is missing");
|
if (*socket < 0) {
|
||||||
}
|
|
||||||
|
|
||||||
err = parseAddress(addressString, &sa);
|
|
||||||
if (err != JDWPTRANSPORT_ERROR_NONE) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
socketFD = dbgsysSocket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (socketFD < 0) {
|
|
||||||
RETURN_IO_ERROR("unable to create socket");
|
RETURN_IO_ERROR("unable to create socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setOptionsCommon(socketFD);
|
err = setOptionsCommon(ai->ai_family, socketFD);
|
||||||
if (err) {
|
if (err) {
|
||||||
return 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
|
* To do a timed connect we make the socket non-blocking
|
||||||
* and poll with a timeout;
|
* and poll with a timeout;
|
||||||
*/
|
*/
|
||||||
if (attachTimeout > 0) {
|
if (timeout > 0) {
|
||||||
dbgsysConfigureBlocking(socketFD, JNI_FALSE);
|
dbgsysConfigureBlocking(socketFD, JNI_FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = dbgsysConnect(socketFD, (struct sockaddr *)&sa, sizeof(sa));
|
err = dbgsysConnect(socketFD, ai->ai_addr, (socklen_t)ai->ai_addrlen);
|
||||||
if (err == DBG_EINPROGRESS && attachTimeout > 0) {
|
if (err == DBG_EINPROGRESS && timeout > 0) {
|
||||||
err = dbgsysFinishConnect(socketFD, (long)attachTimeout);
|
err = dbgsysFinishConnect(socketFD, (long)timeout);
|
||||||
|
|
||||||
if (err == DBG_ETIMEOUT) {
|
if (err == DBG_ETIMEOUT) {
|
||||||
dbgsysConfigureBlocking(socketFD, JNI_TRUE);
|
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_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) {
|
if (attachTimeout > 0) {
|
||||||
dbgsysConfigureBlocking(socketFD, JNI_TRUE);
|
dbgsysConfigureBlocking(socketFD, JNI_TRUE);
|
||||||
}
|
}
|
||||||
@ -1010,7 +1166,7 @@ socketTransport_setConfiguration(jdwpTransportEnv* env, jdwpTransportConfigurati
|
|||||||
"allow option '*' cannot be expanded");
|
"allow option '*' cannot be expanded");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int err = parseAllowedPeers(allowed_peers);
|
int err = parseAllowedPeers(allowed_peers, len);
|
||||||
if (err != JDWPTRANSPORT_ERROR_NONE) {
|
if (err != JDWPTRANSPORT_ERROR_NONE) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@ -1019,10 +1175,46 @@ socketTransport_setConfiguration(jdwpTransportEnv* env, jdwpTransportConfigurati
|
|||||||
return JDWPTRANSPORT_ERROR_NONE;
|
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
|
JNIEXPORT jint JNICALL
|
||||||
jdwpTransport_OnLoad(JavaVM *vm, jdwpTransportCallback* cbTablePtr,
|
jdwpTransport_OnLoad(JavaVM *vm, jdwpTransportCallback* cbTablePtr,
|
||||||
jint version, jdwpTransportEnv** env)
|
jint version, jdwpTransportEnv** env)
|
||||||
{
|
{
|
||||||
|
JNIEnv* jniEnv = NULL;
|
||||||
|
|
||||||
if (version < JDWPTRANSPORT_VERSION_1_0 ||
|
if (version < JDWPTRANSPORT_VERSION_1_0 ||
|
||||||
version > JDWPTRANSPORT_VERSION_1_1) {
|
version > JDWPTRANSPORT_VERSION_1_1) {
|
||||||
return JNI_EVERSION;
|
return JNI_EVERSION;
|
||||||
@ -1055,5 +1247,33 @@ jdwpTransport_OnLoad(JavaVM *vm, jdwpTransportCallback* cbTablePtr,
|
|||||||
|
|
||||||
/* initialized TLS */
|
/* initialized TLS */
|
||||||
tlsIndex = dbgsysTlsAlloc();
|
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;
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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 dbgsysListen(int fd, int backlog);
|
||||||
int dbgsysRecv(int fd, char *buf, size_t nBytes, int flags);
|
int dbgsysRecv(int fd, char *buf, size_t nBytes, int flags);
|
||||||
int dbgsysSend(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 dbgsysSocket(int domain, int type, int protocol);
|
||||||
int dbgsysBind(int fd, struct sockaddr *name, socklen_t namelen);
|
int dbgsysBind(int fd, struct sockaddr *name, socklen_t namelen);
|
||||||
int dbgsysSetSocketOption(int fd, jint cmd, jboolean on, jvalue value);
|
int dbgsysSetSocketOption(int fd, jint cmd, jboolean on, jvalue value);
|
||||||
uint32_t dbgsysInetAddr(const char* cp);
|
|
||||||
uint32_t dbgsysHostToNetworkLong(uint32_t hostlong);
|
uint32_t dbgsysHostToNetworkLong(uint32_t hostlong);
|
||||||
unsigned short dbgsysHostToNetworkShort(unsigned short hostshort);
|
unsigned short dbgsysHostToNetworkShort(unsigned short hostshort);
|
||||||
uint32_t dbgsysNetworkToHostLong(uint32_t netlong);
|
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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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
|
int
|
||||||
dbgsysGetAddrInfo(char *hostname, char *service,
|
dbgsysGetAddrInfo(const char *hostname, const char *service,
|
||||||
struct addrinfo *hints,
|
const struct addrinfo *hints,
|
||||||
struct addrinfo **results) {
|
struct addrinfo **results) {
|
||||||
return getaddrinfo(hostname, service, hints, results);
|
return getaddrinfo(hostname, service, hints, results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dbgsysFreeAddrInfo(struct addrinfo *info) {
|
||||||
|
freeaddrinfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
unsigned short
|
unsigned short
|
||||||
dbgsysHostToNetworkShort(unsigned short hostshort) {
|
dbgsysHostToNetworkShort(unsigned short hostshort) {
|
||||||
return htons(hostshort);
|
return htons(hostshort);
|
||||||
@ -163,11 +168,6 @@ dbgsysBind(int fd, struct sockaddr *name, socklen_t namelen) {
|
|||||||
return bind(fd, name, namelen);
|
return bind(fd, name, namelen);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t
|
|
||||||
dbgsysInetAddr(const char* cp) {
|
|
||||||
return (uint32_t)inet_addr(cp);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t
|
uint32_t
|
||||||
dbgsysHostToNetworkLong(uint32_t hostlong) {
|
dbgsysHostToNetworkLong(uint32_t hostlong) {
|
||||||
return htonl(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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -199,12 +199,17 @@ dbgsysSend(int fd, char *buf, size_t nBytes, int flags) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
dbgsysGetAddrInfo(char *hostname, char *service,
|
dbgsysGetAddrInfo(const char *hostname, const char *service,
|
||||||
struct addrinfo *hints,
|
const struct addrinfo *hints,
|
||||||
struct addrinfo **result) {
|
struct addrinfo **result) {
|
||||||
return getaddrinfo(hostname, service, hints, result);
|
return getaddrinfo(hostname, service, hints, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dbgsysFreeAddrInfo(struct addrinfo *info) {
|
||||||
|
freeaddrinfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
unsigned short
|
unsigned short
|
||||||
dbgsysHostToNetworkShort(unsigned short hostshort) {
|
dbgsysHostToNetworkShort(unsigned short hostshort) {
|
||||||
return htons(hostshort);
|
return htons(hostshort);
|
||||||
@ -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
|
uint32_t
|
||||||
dbgsysHostToNetworkLong(uint32_t hostlong) {
|
dbgsysHostToNetworkLong(uint32_t hostlong) {
|
||||||
return (uint32_t)htonl((u_long)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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* 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.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import nsk.share.*;
|
import nsk.share.*;
|
||||||
import nsk.share.jpda.*;
|
import nsk.share.jpda.*;
|
||||||
@ -85,7 +88,6 @@ public class startlis001 {
|
|||||||
private int runIt(String argv[], PrintStream out) {
|
private int runIt(String argv[], PrintStream out) {
|
||||||
String port;
|
String port;
|
||||||
String addr;
|
String addr;
|
||||||
InetAddress inetAddr = null;
|
|
||||||
ArgumentHandler argHandler = new ArgumentHandler(argv);
|
ArgumentHandler argHandler = new ArgumentHandler(argv);
|
||||||
|
|
||||||
// pass if CONNECTOR_NAME is not implemented
|
// pass if CONNECTOR_NAME is not implemented
|
||||||
@ -98,33 +100,39 @@ public class startlis001 {
|
|||||||
long timeout = argHandler.getWaitTime() * 60 * 1000;
|
long timeout = argHandler.getWaitTime() * 60 * 1000;
|
||||||
|
|
||||||
/* Check that listening address returned by ListeningConnector.startListening()
|
/* Check that listening address returned by ListeningConnector.startListening()
|
||||||
matches the address which was set via connector's arguments */
|
* 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 {
|
try {
|
||||||
inetAddr = InetAddress.getLocalHost();
|
Arrays.stream(InetAddress.getAllByName(hostname))
|
||||||
|
.forEach(address -> validAddresses.add(address.getHostAddress()));
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
log.complain("FAILURE: caught UnknownHostException " +
|
log.complain("FAILURE: caught UnknownHostException " +
|
||||||
e.getMessage());
|
e.getMessage());
|
||||||
totalRes = false;
|
totalRes = false;
|
||||||
}
|
}
|
||||||
String hostname = inetAddr.getHostName();
|
|
||||||
String ip = inetAddr.getHostAddress();
|
|
||||||
port = argHandler.getTransportPortIfNotDynamic();
|
port = argHandler.getTransportPortIfNotDynamic();
|
||||||
|
|
||||||
initConnector(port);
|
initConnector(port);
|
||||||
if ((addr = startListen()) == null) {
|
if ((addr = startListen()) == null) {
|
||||||
log.complain("Test case #1 FAILED: unable to start listening");
|
log.complain("Test case #1 FAILED: unable to start listening");
|
||||||
totalRes = false;
|
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("Test case #1: start listening the address " + addr);
|
||||||
log.display("Expected address: "+ hostname + ":" + port +
|
log.display("Expected addresses: " + validAddrList);
|
||||||
"\n\tor "+ ip + ":" + port);
|
final String listenAddr = addr;
|
||||||
if ( (!addr.startsWith(hostname) && !addr.startsWith(ip)) ||
|
boolean isValid = validAddresses.stream()
|
||||||
(port != null && !addr.endsWith(port)) ) {
|
.anyMatch(value -> listenAddr.startsWith(value) && (port == null || listenAddr.endsWith(port)));
|
||||||
|
if (!isValid) {
|
||||||
log.complain("Test case #1 FAILED: listening address " + addr +
|
log.complain("Test case #1 FAILED: listening address " + addr +
|
||||||
"\ndoes not match expected address:\n" +
|
"\ndoes not match expected address:\n" + validAddrList);
|
||||||
hostname + ":" + port + " or " +
|
|
||||||
ip + ":" + port);
|
|
||||||
totalRes = false;
|
totalRes = false;
|
||||||
}
|
}
|
||||||
if (!stopListen()) {
|
if (!stopListen()) {
|
||||||
|
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,11 +25,12 @@
|
|||||||
* @test
|
* @test
|
||||||
* @summary Smoke test for JDWP hardening
|
* @summary Smoke test for JDWP hardening
|
||||||
* @library /test/lib
|
* @library /test/lib
|
||||||
* @run driver BasicJDWPConnectionTest
|
* @run driver JdwpAllowTest
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
|
|
||||||
@ -37,24 +38,28 @@ import jdk.test.lib.Utils;
|
|||||||
import jdk.test.lib.apps.LingeredApp;
|
import jdk.test.lib.apps.LingeredApp;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
public class BasicJDWPConnectionTest {
|
public class JdwpAllowTest {
|
||||||
|
|
||||||
public static int handshake(int port) throws IOException {
|
public static int handshake(int port) throws IOException {
|
||||||
// Connect to the debuggee and handshake
|
// Connect to the debuggee and handshake
|
||||||
int res = -1;
|
int res = -1;
|
||||||
Socket s = null;
|
Socket s = null;
|
||||||
try {
|
try {
|
||||||
s = new Socket("localhost", port);
|
s = new Socket(localAddr, port);
|
||||||
s.getOutputStream().write("JDWP-Handshake".getBytes("UTF-8"));
|
s.getOutputStream().write("JDWP-Handshake".getBytes("UTF-8"));
|
||||||
byte[] buffer = new byte[24];
|
byte[] buffer = new byte[24];
|
||||||
res = s.getInputStream().read(buffer);
|
res = s.getInputStream().read(buffer);
|
||||||
}
|
}
|
||||||
catch (SocketException ex) {
|
catch (SocketException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
// pass
|
// pass
|
||||||
} finally {
|
} finally {
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
@ -68,7 +73,8 @@ public class BasicJDWPConnectionTest {
|
|||||||
ArrayList<String> cmd = new ArrayList<>();
|
ArrayList<String> cmd = new ArrayList<>();
|
||||||
|
|
||||||
String jdwpArgs = "-agentlib:jdwp=transport=dt_socket,server=y," +
|
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);
|
cmd.add(jdwpArgs);
|
||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
@ -153,68 +159,102 @@ public class BasicJDWPConnectionTest {
|
|||||||
throw new RuntimeException(testName + " FAILED");
|
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
|
* Generate allow address by changing random bit in the local address
|
||||||
String allowOpt = "";
|
* and calculate 2 masks (prefix length) - one is matches original local address
|
||||||
positiveTest("DefaultTest", allowOpt);
|
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ExplicitDefaultTest() throws InterruptedException, IOException {
|
private static boolean getBit(byte[] bytes, int pos) {
|
||||||
// Explicit permission for connections from everywhere
|
return (bytes[pos / 8] & (1 << (7 - (pos % 8)))) != 0;
|
||||||
String allowOpt = ",allow=*";
|
|
||||||
positiveTest("ExplicitDefaultTest" ,allowOpt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AllowTest() throws InterruptedException, IOException {
|
private static void setBit(byte[] bytes, int pos, boolean value) {
|
||||||
String allowOpt = ",allow=127.0.0.1";
|
byte byteValue = (byte)(1 << (7 - (pos % 8)));
|
||||||
positiveTest("AllowTest", allowOpt);
|
if (value) {
|
||||||
|
bytes[pos / 8] = (byte)(bytes[pos / 8] | byteValue);
|
||||||
|
} else {
|
||||||
|
bytes[pos / 8] &= (~byteValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MultiAllowTest() throws InterruptedException, IOException {
|
private static String localAddr;
|
||||||
String allowOpt = ",allow=127.0.0.1+10.0.0.0/8+172.16.0.0/12+192.168.0.0/24";
|
private static List<MaskTest> maskTests = new LinkedList<>();
|
||||||
positiveTest("MultiAllowTest", allowOpt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DenyTest() throws InterruptedException, IOException {
|
private static void init() throws Exception {
|
||||||
// Bad allow address
|
InetAddress addrs[] = InetAddress.getAllByName("localhost");
|
||||||
String allowOpt = ",allow=0.0.0.0";
|
if (addrs.length == 0) {
|
||||||
negativeTest("DenyTest", allowOpt);
|
throw new RuntimeException("No addresses is returned for 'localhost'");
|
||||||
}
|
}
|
||||||
|
localAddr = addrs[0].getHostAddress();
|
||||||
|
System.err.println("localhost address: " + localAddr);
|
||||||
|
|
||||||
public static void MultiDenyTest() throws InterruptedException, IOException {
|
for (int i = 0; i < addrs.length; i++) {
|
||||||
// Wrong separator ';' is used for allow option
|
maskTests.add(new MaskTest(addrs[i]));
|
||||||
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 {
|
public static void main(String[] args) throws Exception {
|
||||||
DefaultTest();
|
init();
|
||||||
ExplicitDefaultTest();
|
|
||||||
AllowTest();
|
// No allow option is the same as the allow option ',allow=*' is passed
|
||||||
MultiAllowTest();
|
positiveTest("DefaultTest", null);
|
||||||
DenyTest();
|
|
||||||
MultiDenyTest();
|
// Explicit permission for connections from everywhere
|
||||||
EmptyAllowOptionTest();
|
positiveTest("ExplicitDefaultTest", "*");
|
||||||
ExplicitMultiDefault1Test();
|
|
||||||
ExplicitMultiDefault2Test();
|
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");
|
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