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