Skip to content

Commit 07661dd

Browse files
authored
Merge pull request #5782 from dizzzz/bugfix/repair_jmxserver_ipv6
[bugfix] Repair JMXservlet for ipv6 addresses.
2 parents fec6740 + 99c3712 commit 07661dd

File tree

1 file changed

+46
-39
lines changed

1 file changed

+46
-39
lines changed

exist-core/src/main/java/org/exist/management/client/JMXServlet.java

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,13 @@
2626
import java.io.OutputStream;
2727
import java.io.OutputStreamWriter;
2828
import java.io.Writer;
29-
import java.net.InetAddress;
30-
import java.net.UnknownHostException;
29+
import java.net.*;
3130
import java.nio.charset.StandardCharsets;
3231
import java.nio.file.Files;
3332
import java.nio.file.Path;
3433
import java.nio.file.Paths;
35-
import java.util.HashSet;
36-
import java.util.Properties;
37-
import java.util.Set;
34+
import java.util.*;
35+
import java.util.function.Predicate;
3836
import javax.management.*;
3937
import jakarta.servlet.ServletConfig;
4038
import jakarta.servlet.ServletException;
@@ -57,22 +55,22 @@
5755
* A servlet to monitor the database. It returns status information for the database based on the JMX interface. For
5856
* simplicity, the JMX beans provided by eXist are organized into categories. One calls the servlet with one or more
5957
* categories in parameter "c", e.g.:
60-
*
58+
* <p>
6159
* /exist/jmx?c=instances&amp;c=memory
62-
*
60+
* <p>
6361
* If no parameter is specified, all categories will be returned. Valid categories are "memory", "instances", "disk",
6462
* "system", "caches", "locking", "processes", "sanity", "all".
65-
*
63+
* <p>
6664
* The servlet can also be used to test if the database is responsive by using parameter "operation=ping" and a timeout
6765
* (t=timeout-in-milliseconds). For example, the following call
68-
*
66+
* <p>
6967
* /exist/jmx?operation=ping&amp;t=1000
70-
*
68+
* <p>
7169
* will wait for a response within 1000ms. If the ping returns within the specified timeout, the servlet returns the
7270
* attributes of the SanityReport JMX bean, which will include an element &lt;jmx:Status&gt;PING_OK&lt;/jmx:Status&gt;.
7371
* If the ping takes longer than the timeout, you'll instead find an element &lt;jmx:error&gt; in the returned XML. In
7472
* this case, additional information on running queries, memory consumption and database locks will be provided.
75-
*
73+
* <p>
7674
* @author wolf
7775
*/
7876
public class JMXServlet extends HttpServlet {
@@ -96,9 +94,8 @@ public class JMXServlet extends HttpServlet {
9694
private Path dataDir;
9795
private Path tokenFile;
9896

99-
10097
@Override
101-
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
98+
protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
10299

103100
// Verify if request is from localhost or if user has specific servlet/container managed role.
104101
if (isFromLocalHost(request)) {
@@ -119,8 +116,8 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t
119116
writeXmlData(request, response);
120117
}
121118

122-
private void writeXmlData(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
123-
Element root = null;
119+
private void writeXmlData(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
120+
final Element root;
124121

125122
final String operation = request.getParameter("operation");
126123
if ("ping".equals(operation)) {
@@ -146,15 +143,15 @@ private void writeXmlData(HttpServletRequest request, HttpServletResponse respon
146143
if (mbean == null) {
147144
throw new ServletException("to call an operation, you also need to specify parameter 'mbean'");
148145
}
149-
String[] args = request.getParameterValues("args");
146+
final String[] args = request.getParameterValues("args");
150147
try {
151148
root = client.invoke(mbean, operation, args);
152149
if (root == null) {
153150
throw new ServletException("operation " + operation + " not found on " + mbean);
154151
}
155-
} catch (InstanceNotFoundException e) {
152+
} catch (final InstanceNotFoundException e) {
156153
throw new ServletException("mbean " + mbean + " not found: " + e.getMessage(), e);
157-
} catch (MalformedObjectNameException | IntrospectionException | ReflectionException | MBeanException e) {
154+
} catch (final MalformedObjectNameException | IntrospectionException | ReflectionException | MBeanException e) {
158155
throw new ServletException(e.getMessage(), e);
159156
}
160157
} else {
@@ -185,7 +182,7 @@ private void writeXmlData(HttpServletRequest request, HttpServletResponse respon
185182
}
186183

187184
@Override
188-
public void init(ServletConfig config) throws ServletException {
185+
public void init(final ServletConfig config) throws ServletException {
189186
super.init(config);
190187

191188
// Setup JMS client
@@ -217,24 +214,32 @@ public void init(ServletConfig config) throws ServletException {
217214
* Register all known IP-addresses for localhost.
218215
*/
219216
void registerLocalHostAddresses() {
220-
// The external IP address of the server
221-
try {
222-
localhostAddresses.add(InetAddress.getLocalHost().getHostAddress());
223-
} catch (UnknownHostException ex) {
224-
LOG.warn("Unable to get HostAddress for localhost: {}", ex.getMessage());
225-
}
226217

227-
// The configured Localhost addresses
228218
try {
229-
for (InetAddress address : InetAddress.getAllByName("localhost")) {
230-
localhostAddresses.add(address.getHostAddress());
231-
}
232-
} catch (UnknownHostException ex) {
233-
LOG.warn("Unable to retrieve ipaddresses for localhost: {}", ex.getMessage());
219+
final Predicate<NetworkInterface> isLoopback = networkInterface -> {
220+
try {
221+
return networkInterface.isLoopback();
222+
} catch (final SocketException e) {
223+
LOG.error("Unable to determine if {} is a loopback interface", e.getMessage(), e);
224+
return false;
225+
}
226+
};
227+
228+
NetworkInterface.networkInterfaces()
229+
.filter(isLoopback)
230+
.flatMap(NetworkInterface::inetAddresses)
231+
.map(InetAddress::getHostAddress)
232+
.map(a -> a.replaceFirst("%.+", ""))
233+
.forEach(localhostAddresses::add);
234+
235+
} catch (final SocketException e) {
236+
LOG.error("Unable to determine localhost loopback addresses: {} ", e.getMessage(), e);
234237
}
235238

236239
if (localhostAddresses.isEmpty()) {
237-
LOG.error("Unable to determine addresses for localhost, jmx servlet might be disfunctional.");
240+
LOG.error("Unable to determine addresses for localhost, jmx servlet might be dysfunctional.");
241+
} else {
242+
LOG.info("Detected loopback addresses: {}", localhostAddresses);
238243
}
239244
}
240245

@@ -244,8 +249,10 @@ void registerLocalHostAddresses() {
244249
* @param request The HTTP request
245250
* @return TRUE if request is from LOCALHOST otherwise FALSE
246251
*/
247-
boolean isFromLocalHost(HttpServletRequest request) {
248-
return localhostAddresses.contains(request.getRemoteAddr());
252+
boolean isFromLocalHost(final HttpServletRequest request) {
253+
String remoteAddr = request.getRemoteAddr();
254+
remoteAddr = remoteAddr.startsWith("[") ? remoteAddr.substring(1, remoteAddr.length() - 1) : remoteAddr;
255+
return localhostAddresses.contains(remoteAddr);
249256
}
250257

251258
/**
@@ -254,8 +261,8 @@ boolean isFromLocalHost(HttpServletRequest request) {
254261
* @param request The HTTP request
255262
* @return TRUE if request contains correct value for token, else FALSE
256263
*/
257-
boolean hasSecretToken(HttpServletRequest request, String token) {
258-
String[] tokenValue = request.getParameterValues(TOKEN_KEY);
264+
boolean hasSecretToken(final HttpServletRequest request, final String token) {
265+
final String[] tokenValue = request.getParameterValues(TOKEN_KEY);
259266
return ArrayUtils.contains(tokenValue, token);
260267
}
261268

@@ -277,7 +284,7 @@ private void obtainTokenFileReference() {
277284
*/
278285
private String getToken() {
279286

280-
Properties props = new Properties();
287+
final Properties props = new Properties();
281288
String token = null;
282289

283290
// Read if possible
@@ -286,7 +293,7 @@ private String getToken() {
286293
try (final InputStream is = Files.newInputStream(tokenFile)) {
287294
props.load(is);
288295
token = props.getProperty(TOKEN_KEY);
289-
} catch (IOException ex) {
296+
} catch (final IOException ex) {
290297
LOG.error(ex.getMessage());
291298
}
292299

@@ -304,7 +311,7 @@ private String getToken() {
304311
// Write data to file
305312
try (final OutputStream os = Files.newOutputStream(tokenFile)) {
306313
props.store(os, "JMXservlet token: http://localhost:8080/exist/status?token=......");
307-
} catch (IOException ex) {
314+
} catch (final IOException ex) {
308315
LOG.error(ex.getMessage());
309316
}
310317

0 commit comments

Comments
 (0)