1/* 2 * Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.tools.jdi; 27 28import java.io.IOException; 29import java.net.Inet6Address; 30import java.net.InetAddress; 31import java.net.InetSocketAddress; 32import java.net.ServerSocket; 33import java.net.Socket; 34import java.net.SocketTimeoutException; 35import java.net.UnknownHostException; 36import java.util.ResourceBundle; 37 38import com.sun.jdi.connect.TransportTimeoutException; 39import com.sun.jdi.connect.spi.Connection; 40import com.sun.jdi.connect.spi.TransportService; 41 42/* 43 * A transport service based on a TCP connection between the 44 * debugger and debugee. 45 */ 46 47public class SocketTransportService extends TransportService { 48 private ResourceBundle messages = null; 49 50 /** 51 * The listener returned by startListening encapsulates 52 * the ServerSocket. 53 */ 54 static class SocketListenKey extends ListenKey { 55 ServerSocket ss; 56 57 SocketListenKey(ServerSocket ss) { 58 this.ss = ss; 59 } 60 61 ServerSocket socket() { 62 return ss; 63 } 64 65 /* 66 * Returns the string representation of the address that this 67 * listen key represents. 68 */ 69 public String address() { 70 InetAddress address = ss.getInetAddress(); 71 72 /* 73 * If bound to the wildcard address then use current local 74 * hostname. In the event that we don't know our own hostname 75 * then assume that host supports IPv4 and return something to 76 * represent the loopback address. 77 */ 78 if (address.isAnyLocalAddress()) { 79 try { 80 address = InetAddress.getLocalHost(); 81 } catch (UnknownHostException uhe) { 82 byte[] loopback = {0x7f,0x00,0x00,0x01}; 83 try { 84 address = InetAddress.getByAddress("127.0.0.1", loopback); 85 } catch (UnknownHostException x) { 86 throw new InternalError("unable to get local hostname"); 87 } 88 } 89 } 90 91 /* 92 * Now decide if we return a hostname or IP address. Where possible 93 * return a hostname but in the case that we are bound to an 94 * address that isn't registered in the name service then we 95 * return an address. 96 */ 97 String result; 98 String hostname = address.getHostName(); 99 String hostaddr = address.getHostAddress(); 100 if (hostname.equals(hostaddr)) { 101 if (address instanceof Inet6Address) { 102 result = "[" + hostaddr + "]"; 103 } else { 104 result = hostaddr; 105 } 106 } else { 107 result = hostname; 108 } 109 110 /* 111 * Finally return "hostname:port", "ipv4-address:port" or 112 * "[ipv6-address]:port". 113 */ 114 return result + ":" + ss.getLocalPort(); 115 } 116 117 public String toString() { 118 return address(); 119 } 120 } 121 122 /** 123 * Handshake with the debuggee 124 */ 125 void handshake(Socket s, long timeout) throws IOException { 126 s.setSoTimeout((int)timeout); 127 128 byte[] hello = "JDWP-Handshake".getBytes("UTF-8"); 129 s.getOutputStream().write(hello); 130 131 byte[] b = new byte[hello.length]; 132 int received = 0; 133 while (received < hello.length) { 134 int n; 135 try { 136 n = s.getInputStream().read(b, received, hello.length-received); 137 } catch (SocketTimeoutException x) { 138 throw new IOException("handshake timeout"); 139 } 140 if (n < 0) { 141 s.close(); 142 throw new IOException("handshake failed - connection prematurally closed"); 143 } 144 received += n; 145 } 146 for (int i=0; i<hello.length; i++) { 147 if (b[i] != hello[i]) { 148 throw new IOException("handshake failed - unrecognized message from target VM"); 149 } 150 } 151 152 // disable read timeout 153 s.setSoTimeout(0); 154 } 155 156 /** 157 * No-arg constructor 158 */ 159 public SocketTransportService() { 160 } 161 162 /** 163 * The name of this transport service 164 */ 165 public String name() { 166 return "Socket"; 167 } 168 169 /** 170 * Return localized description of this transport service 171 */ 172 public String description() { 173 synchronized (this) { 174 if (messages == null) { 175 messages = ResourceBundle.getBundle("com.sun.tools.jdi.resources.jdi"); 176 } 177 } 178 return messages.getString("socket_transportservice.description"); 179 } 180 181 /** 182 * Return the capabilities of this transport service 183 */ 184 public Capabilities capabilities() { 185 return new TransportService.Capabilities() { 186 public boolean supportsMultipleConnections() { 187 return true; 188 } 189 190 public boolean supportsAttachTimeout() { 191 return true; 192 } 193 194 public boolean supportsAcceptTimeout() { 195 return true; 196 } 197 198 public boolean supportsHandshakeTimeout() { 199 return true; 200 } 201 }; 202 } 203 204 /** 205 * Attach to the specified address with optional attach and handshake 206 * timeout. 207 */ 208 public Connection attach(String address, long attachTimeout, long handshakeTimeout) 209 throws IOException { 210 211 if (address == null) { 212 throw new NullPointerException("address is null"); 213 } 214 if (attachTimeout < 0 || handshakeTimeout < 0) { 215 throw new IllegalArgumentException("timeout is negative"); 216 } 217 218 int splitIndex = address.indexOf(':'); 219 String host; 220 String portStr; 221 if (splitIndex < 0) { 222 host = "localhost"; 223 portStr = address; 224 } else { 225 host = address.substring(0, splitIndex); 226 portStr = address.substring(splitIndex+1); 227 } 228 229 if (host.equals("*")) { 230 host = InetAddress.getLocalHost().getHostName(); 231 } 232 233 int port; 234 try { 235 port = Integer.decode(portStr).intValue(); 236 } catch (NumberFormatException e) { 237 throw new IllegalArgumentException( 238 "unable to parse port number in address"); 239 } 240 241 // open TCP connection to VM 242 InetSocketAddress sa = new InetSocketAddress(host, port); 243 Socket s = new Socket(); 244 try { 245 s.connect(sa, (int)attachTimeout); 246 } catch (SocketTimeoutException exc) { 247 try { 248 s.close(); 249 } catch (IOException x) { } 250 throw new TransportTimeoutException("timed out trying to establish connection"); 251 } 252 253 // handshake with the target VM 254 try { 255 handshake(s, handshakeTimeout); 256 } catch (IOException exc) { 257 try { 258 s.close(); 259 } catch (IOException x) { } 260 throw exc; 261 } 262 263 return new SocketConnection(s); 264 } 265 266 /* 267 * Listen on the specified address and port. Return a listener 268 * that encapsulates the ServerSocket. 269 */ 270 ListenKey startListening(String localaddress, int port) throws IOException { 271 InetSocketAddress sa; 272 if (localaddress == null) { 273 sa = new InetSocketAddress(port); 274 } else { 275 sa = new InetSocketAddress(localaddress, port); 276 } 277 ServerSocket ss = new ServerSocket(); 278 if (port == 0) { 279 // Only need SO_REUSEADDR if we're using a fixed port. If we 280 // start seeing EADDRINUSE due to collisions in free ports 281 // then we should retry the bind() a few times. 282 ss.setReuseAddress(false); 283 } 284 ss.bind(sa); 285 return new SocketListenKey(ss); 286 } 287 288 /** 289 * Listen on the specified address 290 */ 291 public ListenKey startListening(String address) throws IOException { 292 // use ephemeral port if address isn't specified. 293 if (address == null || address.length() == 0) { 294 address = "0"; 295 } 296 297 int splitIndex = address.indexOf(':'); 298 String localaddr = null; 299 if (splitIndex >= 0) { 300 localaddr = address.substring(0, splitIndex); 301 address = address.substring(splitIndex+1); 302 } 303 304 int port; 305 try { 306 port = Integer.decode(address).intValue(); 307 } catch (NumberFormatException e) { 308 throw new IllegalArgumentException( 309 "unable to parse port number in address"); 310 } 311 312 return startListening(localaddr, port); 313 } 314 315 /** 316 * Listen on the default address 317 */ 318 public ListenKey startListening() throws IOException { 319 return startListening(null, 0); 320 } 321 322 /** 323 * Stop the listener 324 */ 325 public void stopListening(ListenKey listener) throws IOException { 326 if (!(listener instanceof SocketListenKey)) { 327 throw new IllegalArgumentException("Invalid listener"); 328 } 329 330 synchronized (listener) { 331 ServerSocket ss = ((SocketListenKey)listener).socket(); 332 333 // if the ServerSocket has been closed it means 334 // the listener is invalid 335 if (ss.isClosed()) { 336 throw new IllegalArgumentException("Invalid listener"); 337 } 338 ss.close(); 339 } 340 } 341 342 /** 343 * Accept a connection from a debuggee and handshake with it. 344 */ 345 public Connection accept(ListenKey listener, long acceptTimeout, long handshakeTimeout) throws IOException { 346 if (acceptTimeout < 0 || handshakeTimeout < 0) { 347 throw new IllegalArgumentException("timeout is negative"); 348 } 349 if (!(listener instanceof SocketListenKey)) { 350 throw new IllegalArgumentException("Invalid listener"); 351 } 352 ServerSocket ss; 353 354 // obtain the ServerSocket from the listener - if the 355 // socket is closed it means the listener is invalid 356 synchronized (listener) { 357 ss = ((SocketListenKey)listener).socket(); 358 if (ss.isClosed()) { 359 throw new IllegalArgumentException("Invalid listener"); 360 } 361 } 362 363 // from here onwards it's possible that the ServerSocket 364 // may be closed by a call to stopListening - that's okay 365 // because the ServerSocket methods will throw an 366 // IOException indicating the socket is closed. 367 // 368 // Additionally, it's possible that another thread calls accept 369 // with a different accept timeout - that creates a same race 370 // condition between setting the timeout and calling accept. 371 // As it is such an unlikely scenario (requires both threads 372 // to be using the same listener we've chosen to ignore the issue). 373 374 ss.setSoTimeout((int)acceptTimeout); 375 Socket s; 376 try { 377 s = ss.accept(); 378 } catch (SocketTimeoutException x) { 379 throw new TransportTimeoutException("timeout waiting for connection"); 380 } 381 382 // handshake here 383 handshake(s, handshakeTimeout); 384 385 return new SocketConnection(s); 386 } 387 388 public String toString() { 389 return name(); 390 } 391} 392