From 2af85874240a994684de6837bbcdb6dd91497e3f Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Wed, 27 Aug 2014 15:32:06 +0200 Subject: [PATCH] SONAR-4898 use loopback address instead of hardcoding 127.0.0.1 for JMX connections --- .../main/java/org/sonar/process/JmxUtils.java | 19 +++++ .../org/sonar/process/LoopbackAddress.java | 71 +++++++++++++++++++ .../org/sonar/process/ProcessWrapper.java | 27 ++----- .../java/org/sonar/process/JmxUtilsTest.java | 40 ++++++++++- .../sonar/process/LoopbackAddressTest.java | 51 +++++++++++++ 5 files changed, 185 insertions(+), 23 deletions(-) create mode 100644 server/process/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java create mode 100644 server/process/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java diff --git a/server/process/sonar-process/src/main/java/org/sonar/process/JmxUtils.java b/server/process/sonar-process/src/main/java/org/sonar/process/JmxUtils.java index 0f35d65212b..58ffb3af3e0 100644 --- a/server/process/sonar-process/src/main/java/org/sonar/process/JmxUtils.java +++ b/server/process/sonar-process/src/main/java/org/sonar/process/JmxUtils.java @@ -22,7 +22,12 @@ package org.sonar.process; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; +import javax.management.remote.JMXServiceURL; + import java.lang.management.ManagementFactory; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.MalformedURLException; public class JmxUtils { @@ -58,4 +63,18 @@ public class JmxUtils { throw new IllegalStateException("Fail to register JMX MBean named " + name, e); } } + + public static JMXServiceURL serviceUrl(InetAddress host, int port) { + String address = host.getHostAddress(); + if (host instanceof Inet6Address) { + // See http://docs.oracle.com/javase/7/docs/api/javax/management/remote/JMXServiceURL.html + // "The host is a host name, an IPv4 numeric host address, or an IPv6 numeric address enclosed in square brackets." + address = String.format("[%s]", address); + } + try { + return new JMXServiceURL("rmi", address, port, String.format("/jndi/rmi://%s:%d/jmxrmi", address, port)); + } catch (MalformedURLException e) { + throw new IllegalStateException("JMX url does not look well formed", e); + } + } } diff --git a/server/process/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java b/server/process/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java new file mode 100644 index 00000000000..da70cac778c --- /dev/null +++ b/server/process/sonar-process/src/main/java/org/sonar/process/LoopbackAddress.java @@ -0,0 +1,71 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + +public class LoopbackAddress { + + private static InetAddress instance; + + private LoopbackAddress() { + // only static stuff + } + + /** + * Similar to InetAddress.getLoopbackAddress() which was introduced in Java 7. This + * method aims to support Java 6. + */ + public static InetAddress get() { + if (instance == null) { + try { + instance = doGet(NetworkInterface.getNetworkInterfaces()); + } catch (SocketException e) { + throw new IllegalStateException("Fail to browse network interfaces", e); + } + + } + return instance; + } + + static InetAddress doGet(Enumeration ifaces) { + InetAddress result = null; + while (ifaces.hasMoreElements() && result == null) { + NetworkInterface iface = ifaces.nextElement(); + Enumeration addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr.isLoopbackAddress()) { + result = addr; + break; + } + } + } + if (result == null) { + throw new IllegalStateException("Impossible to get a IP loopback address"); + } + return result; + } +} diff --git a/server/process/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java b/server/process/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java index 31ede432301..c014122288c 100644 --- a/server/process/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java +++ b/server/process/sonar-process/src/main/java/org/sonar/process/ProcessWrapper.java @@ -33,6 +33,7 @@ import javax.management.NotificationListener; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; + import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; @@ -40,7 +41,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -129,7 +129,6 @@ public class ProcessWrapper extends Thread implements Terminable { return this; } - @CheckForNull public Process process() { return process; @@ -221,7 +220,7 @@ public class ProcessWrapper extends Thread implements Terminable { "-Dcom.sun.management.jmxremote.port=" + jmxPort, "-Dcom.sun.management.jmxremote.authenticate=false", "-Dcom.sun.management.jmxremote.ssl=false", - "-Djava.rmi.server.hostname=" + localAddress()); + "-Djava.rmi.server.hostname=" + LoopbackAddress.get().getHostAddress()); } private List buildClasspath() { @@ -249,41 +248,27 @@ public class ProcessWrapper extends Thread implements Terminable { */ @CheckForNull private ProcessMXBean waitForJMX() { - String loopbackAddress = localAddress(); - String path = "/jndi/rmi://" + loopbackAddress + ":" + jmxPort + "/jmxrmi"; - JMXServiceURL jmxUrl; - try { - jmxUrl = new JMXServiceURL("rmi", loopbackAddress, jmxPort, path); - } catch (MalformedURLException e) { - throw new IllegalStateException("JMX url does not look well formed", e); - } - + JMXServiceURL jmxUrl = JmxUtils.serviceUrl(LoopbackAddress.get(), jmxPort); for (int i = 0; i < 5; i++) { try { Thread.sleep(1000L); - LOGGER.debug("Try #{} to connect to JMX server for process '{}'", i, processName); JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl, null); jmxConnector.addConnectionNotificationListener(new NotificationListener() { @Override public void handleNotification(Notification notification, Object handback) { - LOGGER.info("JMX Connection Notification:{}", notification.getMessage()); + LOGGER.debug("JMX Connection Notification:{}", notification.getMessage()); } }, null, null); MBeanServerConnection mBeanServer = jmxConnector.getMBeanServerConnection(); return JMX.newMBeanProxy(mBeanServer, JmxUtils.objectName(processName), ProcessMXBean.class); } catch (Exception ignored) { - LOGGER.trace("Could not connect JMX yet", ignored); + LOGGER.info(String.format("Could not connect to JMX (attempt %d). Trying again.", i), ignored); } } // failed to connect return null; } - private String localAddress() { - // to be replaced by InetAddress.getLoopbackAddress() in Java 7 ? - return "127.0.0.1"; - } - @Override public void terminate() { if (processMXBean != null && process != null) { @@ -344,7 +329,7 @@ public class ProcessWrapper extends Thread implements Terminable { this.join(); } } catch (InterruptedException e) { - //Expected to be interrupted :) + // Expected to be interrupted :) } } diff --git a/server/process/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java b/server/process/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java index 7d9dcc54094..d2ab21157ef 100644 --- a/server/process/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java +++ b/server/process/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java @@ -23,8 +23,15 @@ import org.junit.Test; import javax.management.MBeanServer; import javax.management.ObjectName; +import javax.management.remote.JMXServiceURL; import java.lang.management.ManagementFactory; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; @@ -61,7 +68,7 @@ public class JmxUtilsTest { @Test public void fail_jmx_objectName() throws Exception { try { - ObjectName objectName = JmxUtils.objectName(":"); + JmxUtils.objectName(":"); fail(); } catch (Exception e) { assertThat(e.getMessage()).isEqualTo("Cannot create ObjectName for :"); @@ -86,4 +93,33 @@ public class JmxUtilsTest { JmxUtils.registerMBean(mxBean, mxBean.getClass().getSimpleName()); assertThat(mbeanServer.isRegistered(objectName)).isTrue(); } -} \ No newline at end of file + + @Test + public void serviceUrl_ipv4() throws Exception { + JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet4Address.class), 1234); + assertThat(url).isNotNull(); + assertThat(url.getPort()).isEqualTo(1234); + } + + @Test + public void serviceUrl_ipv6() throws Exception { + JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet6Address.class), 1234); + assertThat(url).isNotNull(); + assertThat(url.getPort()).isEqualTo(1234); + } + + private static InetAddress ip(Class inetAddressClass) throws SocketException { + Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); + while (ifaces.hasMoreElements()) { + NetworkInterface iface = ifaces.nextElement(); + Enumeration addresses = iface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr.getClass().isAssignableFrom(inetAddressClass)) { + return addr; + } + } + } + throw new IllegalStateException("no ipv4 address"); + } +} diff --git a/server/process/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java b/server/process/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java new file mode 100644 index 00000000000..0e4885a6407 --- /dev/null +++ b/server/process/sonar-process/src/test/java/org/sonar/process/LoopbackAddressTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process; + +import com.google.common.collect.Iterators; +import org.junit.Test; + +import java.net.NetworkInterface; +import java.util.Collections; +import java.util.Enumeration; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; + +public class LoopbackAddressTest { + + @Test + public void get() throws Exception { + assertThat(LoopbackAddress.get()).isNotNull(); + assertThat(LoopbackAddress.get().isLoopbackAddress()).isTrue(); + assertThat(LoopbackAddress.get().getHostAddress()).isNotNull(); + } + + @Test + public void fail_to_get_loopback_address() throws Exception { + Enumeration ifaces = Iterators.asEnumeration(Collections.emptyList().iterator()); + try { + LoopbackAddress.doGet(ifaces); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("Impossible to get a IP loopback address"); + } + } +} -- 2.39.5