1 /* 2 * Copyright (c) 2013, 2018, 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 26 package sun.management.jdp; 27 28 import java.io.IOException; 29 import java.lang.management.ManagementFactory; 30 import java.lang.management.RuntimeMXBean; 31 import java.lang.reflect.Field; 32 import java.lang.reflect.Method; 33 import java.net.InetAddress; 34 import java.net.UnknownHostException; 35 import java.util.UUID; 36 37 /** 38 * JdpController is responsible to create and manage a broadcast loop. 39 * 40 * <p> Other part of code has no access to broadcast loop and have to use 41 * provided static methods 42 * {@link #startDiscoveryService(InetAddress,int,String,String) startDiscoveryService} 43 * and {@link #stopDiscoveryService() stopDiscoveryService} 44 * <p>{@link #startDiscoveryService(InetAddress,int,String,String) startDiscoveryService} could be called multiple 45 * times as it stops the running service if it is necessary. 46 * Call to {@link #stopDiscoveryService() stopDiscoveryService} 47 * ignored if service isn't run. 48 * 49 * 50 * <p> System properties below could be used to control broadcast loop behavior. 51 * Property below have to be set explicitly in command line. It's not possible to 52 * set it in management.config file. Careless changes of these properties could 53 * lead to security or network issues. 54 * <ul> 55 * <li>com.sun.management.jdp.ttl - set ttl for broadcast packet</li> 56 * <li>com.sun.management.jdp.pause - set broadcast interval in seconds</li> 57 * <li>com.sun.management.jdp.source_addr - an address of interface to use for broadcast</li> 58 * </ul> 59 * 60 * <p>null parameters values are filtered out on {@link JdpPacketWriter} level and 61 * corresponding keys are not placed to packet. 62 */ 63 public final class JdpController { 64 65 private static class JDPControllerRunner implements Runnable { 66 67 private final JdpJmxPacket packet; 68 private final JdpBroadcaster bcast; 69 private final int pause; 70 private volatile boolean shutdown = false; 71 JDPControllerRunner(JdpBroadcaster bcast, JdpJmxPacket packet, int pause)72 private JDPControllerRunner(JdpBroadcaster bcast, JdpJmxPacket packet, int pause) { 73 this.bcast = bcast; 74 this.packet = packet; 75 this.pause = pause; 76 } 77 78 @Override run()79 public void run() { 80 try { 81 while (!shutdown) { 82 bcast.sendPacket(packet); 83 try { 84 Thread.sleep(this.pause); 85 } catch (InterruptedException e) { 86 // pass 87 } 88 } 89 90 } catch (IOException e) { 91 // pass; 92 } 93 94 // It's not possible to re-use controller, 95 // nevertheless reset shutdown variable 96 try { 97 stop(); 98 bcast.shutdown(); 99 } catch (IOException ex) { 100 // pass - ignore IOException during shutdown 101 } 102 } 103 stop()104 public void stop() { 105 shutdown = true; 106 } 107 } 108 private static JDPControllerRunner controller = null; 109 JdpController()110 private JdpController(){ 111 // Don't allow to instantiate this class. 112 } 113 114 // Utility to handle optional system properties 115 // Parse an integer from string or return default if provided string is null getInteger(String val, int dflt, String msg)116 private static int getInteger(String val, int dflt, String msg) throws JdpException { 117 try { 118 return (val == null) ? dflt : Integer.parseInt(val); 119 } catch (NumberFormatException ex) { 120 throw new JdpException(msg); 121 } 122 } 123 124 // Parse an inet address from string or return default if provided string is null getInetAddress(String val, InetAddress dflt, String msg)125 private static InetAddress getInetAddress(String val, InetAddress dflt, String msg) throws JdpException { 126 try { 127 return (val == null) ? dflt : InetAddress.getByName(val); 128 } catch (UnknownHostException ex) { 129 throw new JdpException(msg); 130 } 131 } 132 133 // Get the process id of the current running Java process getProcessId()134 private static Long getProcessId() { 135 try { 136 // Get the current process id 137 return ProcessHandle.current().pid(); 138 } catch(UnsupportedOperationException ex) { 139 return null; 140 } 141 } 142 143 144 /** 145 * Starts discovery service 146 * 147 * @param address - multicast group address 148 * @param port - udp port to use 149 * @param instanceName - name of running JVM instance 150 * @param url - JMX service url 151 * @throws IOException 152 */ startDiscoveryService(InetAddress address, int port, String instanceName, String url)153 public static synchronized void startDiscoveryService(InetAddress address, int port, String instanceName, String url) 154 throws IOException, JdpException { 155 156 // Limit packet to local subnet by default 157 int ttl = getInteger( 158 System.getProperty("com.sun.management.jdp.ttl"), 1, 159 "Invalid jdp packet ttl"); 160 161 // Broadcast once a 5 seconds by default 162 int pause = getInteger( 163 System.getProperty("com.sun.management.jdp.pause"), 5, 164 "Invalid jdp pause"); 165 166 // Converting seconds to milliseconds 167 pause = pause * 1000; 168 169 // Allow OS to choose broadcast source 170 InetAddress sourceAddress = getInetAddress( 171 System.getProperty("com.sun.management.jdp.source_addr"), null, 172 "Invalid source address provided"); 173 174 // Generate session id 175 UUID id = UUID.randomUUID(); 176 177 JdpJmxPacket packet = new JdpJmxPacket(id, url); 178 179 // Don't broadcast whole command line for security reason. 180 // Strip everything after first space 181 String javaCommand = System.getProperty("sun.java.command"); 182 if (javaCommand != null) { 183 String[] arr = javaCommand.split(" ", 2); 184 packet.setMainClass(arr[0]); 185 } 186 187 // Put optional explicit java instance name to packet, if user doesn't specify 188 // it the key is skipped. PacketWriter is responsible to skip keys having null value. 189 packet.setInstanceName(instanceName); 190 191 // Set rmi server hostname if it explicitly specified by user with 192 // java.rmi.server.hostname 193 String rmiHostname = System.getProperty("java.rmi.server.hostname"); 194 packet.setRmiHostname(rmiHostname); 195 196 // Set broadcast interval 197 packet.setBroadcastInterval(Integer.toString(pause)); 198 199 // Set process id 200 Long pid = getProcessId(); 201 if (pid != null) { 202 packet.setProcessId(pid.toString()); 203 } 204 205 JdpBroadcaster bcast = new JdpBroadcaster(address, sourceAddress, port, ttl); 206 207 // Stop discovery service if it's already running 208 stopDiscoveryService(); 209 210 controller = new JDPControllerRunner(bcast, packet, pause); 211 212 Thread t = new Thread(null, controller, "JDP broadcaster", 0, false); 213 t.setDaemon(true); 214 t.start(); 215 } 216 217 /** 218 * Stop running discovery service, 219 * it's safe to attempt to stop not started service 220 */ stopDiscoveryService()221 public static synchronized void stopDiscoveryService() { 222 if ( controller != null ){ 223 controller.stop(); 224 controller = null; 225 } 226 } 227 } 228