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 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 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 104 public void stop() { 105 shutdown = true; 106 } 107 } 108 private static JDPControllerRunner controller = null; 109 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 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 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 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 */ 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 */ 221 public static synchronized void stopDiscoveryService() { 222 if ( controller != null ){ 223 controller.stop(); 224 controller = null; 225 } 226 } 227 }