8184770: JDWP support for IPv6

Reviewed-by: sspitsyn, chegar
This commit is contained in:
Alex Menkov 2019-05-15 11:06:33 -07:00
parent 23301277c5
commit e18cecafb9
11 changed files with 1243 additions and 409 deletions

View File

@ -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]);
}
}
}

View File

@ -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);
}
/**

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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");

View File

@ -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");
}
}

View 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));
}
}

View 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));
}
}

View 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));
}
}