1 /* 2 * Copyright (c) 2003, 2015, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 4865397 27 * @summary Tests remote JMX connections 28 * @author Eamonn McManus 29 * @modules java.management.rmi 30 * @run clean ConnectionTest 31 * @run build ConnectionTest 32 * @run main ConnectionTest 33 */ 34 35 import java.io.IOException; 36 import java.net.MalformedURLException; 37 import java.util.Collections; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 import java.util.Iterator; 41 import java.util.LinkedList; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 import java.util.StringTokenizer; 46 47 import java.security.Principal; 48 import java.util.regex.Pattern; 49 import javax.security.auth.Subject; 50 51 import javax.management.MBeanServer; 52 import javax.management.MBeanServerConnection; 53 import javax.management.MBeanServerFactory; 54 import javax.management.Notification; 55 import javax.management.NotificationListener; 56 import javax.management.ObjectName; 57 58 import javax.management.remote.JMXAuthenticator; 59 import javax.management.remote.JMXConnectionNotification; 60 import javax.management.remote.JMXConnector; 61 import javax.management.remote.JMXConnectorFactory; 62 import javax.management.remote.JMXConnectorServer; 63 import javax.management.remote.JMXConnectorServerFactory; 64 import javax.management.remote.JMXPrincipal; 65 import javax.management.remote.JMXServiceURL; 66 67 public class ConnectionTest { 68 69 public static void main(String[] args) { 70 // System.setProperty("java.util.logging.config.file", 71 // "../../../../logging.properties"); 72 // // we are in <workspace>/build/test/JTwork/scratch 73 // java.util.logging.LogManager.getLogManager().readConfiguration(); 74 boolean ok = true; 75 String[] protocols = {"rmi", "iiop", "jmxmp"}; 76 if (args.length > 0) 77 protocols = args; 78 for (int i = 0; i < protocols.length; i++) { 79 final String proto = protocols[i]; 80 System.out.println("Testing for protocol " + proto); 81 try { 82 ok &= test(proto); 83 } catch (Exception e) { 84 System.err.println("Unexpected exception: " + e); 85 e.printStackTrace(); 86 ok = false; 87 } 88 } 89 90 if (ok) 91 System.out.println("Test passed"); 92 else { 93 System.out.println("TEST FAILED"); 94 System.exit(1); 95 } 96 } 97 98 private static boolean test(String proto) throws Exception { 99 ObjectName serverName = ObjectName.getInstance("d:type=server"); 100 MBeanServer mbs = MBeanServerFactory.newMBeanServer(); 101 JMXAuthenticator authenticator = new BogusAuthenticator(); 102 Map env = Collections.singletonMap("jmx.remote.authenticator", 103 authenticator); 104 JMXServiceURL url = new JMXServiceURL("service:jmx:" + proto + "://"); 105 JMXConnectorServer server; 106 try { 107 server = 108 JMXConnectorServerFactory.newJMXConnectorServer(url, env, 109 null); 110 } catch (MalformedURLException e) { 111 System.out.println("Protocol " + proto + 112 " not supported, ignoring"); 113 return true; 114 } 115 System.out.println("Created connector server"); 116 mbs.registerMBean(server, serverName); 117 System.out.println("Registered connector server in MBean server"); 118 mbs.addNotificationListener(serverName, logListener, null, null); 119 mbs.invoke(serverName, "start", null, null); 120 System.out.println("Started connector server"); 121 JMXServiceURL address = 122 (JMXServiceURL) mbs.getAttribute(serverName, "Address"); 123 System.out.println("Retrieved address: " + address); 124 125 if (address.getHost().length() == 0) { 126 System.out.println("Generated address has empty hostname"); 127 return false; 128 } 129 130 JMXConnector client = JMXConnectorFactory.connect(address); 131 System.out.println("Client connected"); 132 133 String clientConnId = client.getConnectionId(); 134 System.out.println("Got connection ID on client: " + clientConnId); 135 boolean ok = checkConnectionId(proto, clientConnId); 136 if (!ok) 137 return false; 138 System.out.println("Connection ID is OK"); 139 140 // 4901826: connection ids need some time to be updated using jmxmp 141 // we don't get the notif immediately either 142 // this was originally timeout 1ms, which was not enough 143 Notification notif = waitForNotification(1000); 144 System.out.println("Server got notification: " + notif); 145 146 ok = mustBeConnectionNotification(notif, clientConnId, 147 JMXConnectionNotification.OPENED); 148 if (!ok) 149 return false; 150 151 client.close(); 152 System.out.println("Closed client"); 153 154 notif = waitForNotification(1000); 155 System.out.println("Got notification: " + notif); 156 157 ok = mustBeConnectionNotification(notif, clientConnId, 158 JMXConnectionNotification.CLOSED); 159 if (!ok) 160 return false; 161 162 client = JMXConnectorFactory.connect(address); 163 System.out.println("Second client connected"); 164 165 String clientConnId2 = client.getConnectionId(); 166 if (clientConnId.equals(clientConnId2)) { 167 System.out.println("Same connection ID for two connections: " + 168 clientConnId2); 169 return false; 170 } 171 System.out.println("Second client connection ID is different"); 172 173 notif = waitForNotification(1); 174 ok = mustBeConnectionNotification(notif, clientConnId2, 175 JMXConnectionNotification.OPENED); 176 if (!ok) 177 return false; 178 179 MBeanServerConnection mbsc = client.getMBeanServerConnection(); 180 Map attrs = (Map) mbsc.getAttribute(serverName, "Attributes"); 181 System.out.println("Server attributes received by client: " + attrs); 182 183 server.stop(); 184 System.out.println("Server stopped"); 185 186 notif = waitForNotification(1000); 187 System.out.println("Server got connection-closed notification: " + 188 notif); 189 190 ok = mustBeConnectionNotification(notif, clientConnId2, 191 JMXConnectionNotification.CLOSED); 192 if (!ok) 193 return false; 194 195 try { 196 mbsc.getDefaultDomain(); 197 System.out.println("Connection still working but should not be"); 198 return false; 199 } catch (IOException e) { 200 System.out.println("Connection correctly got exception: " + e); 201 } 202 203 try { 204 client = JMXConnectorFactory.connect(address); 205 System.out.println("Connector server still working but should " + 206 "not be"); 207 return false; 208 } catch (IOException e) { 209 System.out.println("New connection correctly got exception: " + e); 210 } 211 212 return true; 213 } 214 215 private static boolean 216 mustBeConnectionNotification(Notification notif, 217 String requiredConnId, 218 String requiredType) { 219 220 if (!(notif instanceof JMXConnectionNotification)) { 221 System.out.println("Should have been a " + 222 "JMXConnectionNotification: " + 223 notif.getClass()); 224 return false; 225 } 226 227 JMXConnectionNotification cnotif = (JMXConnectionNotification) notif; 228 if (!cnotif.getType().equals(requiredType)) { 229 System.out.println("Wrong type notif: is \"" + cnotif.getType() + 230 "\", should be \"" + requiredType + "\""); 231 return false; 232 } 233 234 if (!cnotif.getConnectionId().equals(requiredConnId)) { 235 System.out.println("Wrong connection id: is \"" + 236 cnotif.getConnectionId() + "\", should be \"" + 237 requiredConnId); 238 return false; 239 } 240 241 return true; 242 } 243 244 private static final String IPV4_PTN = "^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}(\\:[1-9][0-9]{3})?$"; 245 246 /** 247 * Checks the connection id for validity. 248 * The {@link 249 * javax.management.remote package description} describes the 250 * conventions for connection IDs. 251 * @param proto Connection protocol 252 * @param clientConnId The connection ID 253 * @return Returns {@code true} if the connection id conforms to the specification; {@code false} otherwise. 254 * @throws Exception 255 */ 256 private static boolean checkConnectionId(String proto, String clientConnId) 257 throws Exception { 258 StringTokenizer tok = new StringTokenizer(clientConnId, " ", true); 259 String s; 260 s = tok.nextToken(); 261 if (!s.startsWith(proto + ":")) { 262 System.out.println("Expected \"" + proto + ":\", found \"" + s + 263 "\""); 264 return false; 265 } 266 267 int hostAddrInd = s.indexOf("//"); 268 if (hostAddrInd > -1) { 269 s = s.substring(hostAddrInd + 2); 270 if (!Pattern.matches(IPV4_PTN, s)) { 271 if (!s.startsWith("[") || !s.endsWith("]")) { 272 System.out.println("IPv6 address must be enclosed in \"[]\""); 273 return false; 274 } 275 } 276 } 277 s = tok.nextToken(); 278 if (!s.equals(" ")) { 279 System.out.println("Expected \" \", found \"" + s + "\""); 280 return false; 281 } 282 s = tok.nextToken(); 283 StringTokenizer tok2 = new StringTokenizer(s, ";", true); 284 Set principalNames = new HashSet(); 285 String s2; 286 s2 = tok2.nextToken(); 287 if (s2.equals(";")) { 288 System.out.println("In identity \"" + s + 289 "\", expected name, found \";\""); 290 return false; 291 } 292 principalNames.add(s2); 293 s2 = tok2.nextToken(); 294 if (!s2.equals(";")) 295 throw new Exception("Can't happen"); 296 s2 = tok2.nextToken(); 297 if (s2.equals(";")) { 298 System.out.println("In identity \"" + s + 299 "\", expected name, found \";\""); 300 return false; 301 } 302 principalNames.add(s2); 303 if (tok2.hasMoreTokens()) { 304 System.out.println("In identity \"" + s + "\", too many tokens"); 305 return false; 306 } 307 if (principalNames.size() != bogusPrincipals.size()) { 308 System.out.println("Wrong number of principal names: " + 309 principalNames.size() + " != " + 310 bogusPrincipals.size()); 311 return false; 312 } 313 for (Iterator it = bogusPrincipals.iterator(); it.hasNext(); ) { 314 Principal p = (Principal) it.next(); 315 if (!principalNames.contains(p.getName())) { 316 System.out.println("Principal names don't contain \"" + 317 p.getName() + "\""); 318 return false; 319 } 320 } 321 s = tok.nextToken(); 322 if (!s.equals(" ")) { 323 System.out.println("Expected \" \", found \"" + s + "\""); 324 return false; 325 } 326 return true; 327 } 328 329 private static Notification waitForNotification(long timeout) 330 throws InterruptedException { 331 synchronized (log) { 332 if (log.isEmpty()) { 333 long remainingTime = timeout; 334 final long startTime = System.currentTimeMillis(); 335 336 while (log.isEmpty() && remainingTime >0) { 337 log.wait(remainingTime); 338 remainingTime = timeout - (System.currentTimeMillis() - startTime); 339 } 340 341 if (log.isEmpty()) { 342 throw new InterruptedException("Timed out waiting for " + 343 "notification!"); 344 } 345 } 346 return (Notification) log.remove(0); 347 } 348 } 349 350 private static class LogListener implements NotificationListener { 351 LogListener(List log) { 352 this.log = log; 353 } 354 355 public void handleNotification(Notification n, Object h) { 356 synchronized (log) { 357 log.add(n); 358 log.notifyAll(); 359 } 360 } 361 362 private final List log; 363 } 364 365 private static List log = new LinkedList(); 366 private static NotificationListener logListener = new LogListener(log); 367 368 private static class BogusAuthenticator implements JMXAuthenticator { 369 public Subject authenticate(Object credentials) { 370 Subject subject = 371 new Subject(true, bogusPrincipals, 372 Collections.EMPTY_SET, Collections.EMPTY_SET); 373 System.out.println("Authenticator returns: " + subject); 374 return subject; 375 } 376 } 377 378 private static final Set bogusPrincipals = new HashSet(); 379 static { 380 bogusPrincipals.add(new JMXPrincipal("foo")); 381 bogusPrincipals.add(new JMXPrincipal("bar")); 382 } 383 }