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