[#1112] Listen on the configured listen-address in the HTTP connection handler and the JMX RMI connector - #1113
Conversation
maximthomas
left a comment
There was a problem hiding this comment.
praise: the change binds exactly where the bug is, and cleans up after itself on the failure road.
startHttpServershuts the server down whenHttpServer.start()fails (HTTPConnectionHandler.java:742), so the listeners bound before the failing one release their ports;releasesTheBoundListenAddressesWhenAnotherCannotBeBoundpins it.DirectoryRMIServerSocketFactoryputs the listen address intoequals/hashCode, so RMI cannot share one endpoint across two addresses.- The new cases run on the Linux CI cells, none skipped:
HTTPConnectionHandlerTestCase4/4 andJmxListenAddressTestCase2/2 (bothuse-sslrows) on ubuntu-latest, JDK 11.
issue (blocking): with a listen-address that is not the host's RMI address, JMX clients can no longer reach the RMI connector.
opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/RmiConnector.java:383, :361, :409
Both connector factories now bind listen-address, but OpendsRMIJRMPServerImpl is still exported through UnicastRemoteObject. Its stub advertises the JDK's RMI local host, which is java.rmi.server.hostname or else InetAddress.getLocalHost(), and nothing in the tree sets that property. Take listen-address: 127.0.0.1 on a Debian/Ubuntu host (hostname → 127.0.1.1), on a host whose name resolves to its LAN IP, or one IP of a multi-homed host. The registry lookup succeeds, then newClient() fails with ConnectException: Connection refused to host: <local host>. At master the connector listened on every address, so the same configuration worked. I measured it with a JDK 26 probe: an ssf bound to 192.168.0.39, with getLocalHost() = 127.0.0.1, gives a stub endpoint of 127.0.0.1 and the call is refused; the wildcard control answers. The default 0.0.0.0 is not affected.
// The connector's stub advertises the RMI local host, not the address its socket is bound to.
final InetAddress listenAddress = jmxConnectionHandler.getListenAddress();
if (!listenAddress.isAnyLocalAddress() && System.getProperty("java.rmi.server.hostname") == null)
{
System.setProperty("java.rmi.server.hostname", listenAddress.getHostAddress());
}
OpendsRMIJRMPServerImpl opendsRmiConnectorServer =
new OpendsRMIJRMPServerImpl(jmxConnectionHandler.getRmiPort(),
rmiClientSockeyFactory, rmiServerSockeyFactory, env);The JDK reads the property again at each export, so a connector restart picks it up. If the server set the property itself, update it when listen-address changes.
Pin: a JmxListenAddressTestCase case that opens a real JMX session through service:jmx:rmi:///jndi/rmi://<listen address>:<listen-port>/org.opends.server.protocols.jmx.client-unknown and calls getMBeanCount(). Set its listen-address to whichever of 127.0.0.1 and getNonLoopbackAddress() differs from InetAddress.getLocalHost().getHostAddress(), so that it fails at this head on every host. On a Mac whose hostname resolves to 127.0.0.1, a 127.0.0.1 connect passes and hides the bug.
issue (non-blocking): when a connector restart comes with a listen-address change, the connector and the reported listener move to the new address and the RMI registry stays on the old one.
opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/JmxConnectionHandler.java:140, :142, RmiConnector.java:510-527
The registry is rebuilt only when listen-port changed (finalizeConnectionHandler(portChanged), and startCommonRegistry skips while registry != null). Line 140 now reports the new listen-address, and the connector's factory reads it through getListenAddress(). Take one modify that sets ds-cfg-listen-address from 0.0.0.0 to B and changes ds-cfg-rmi-port: the registry stays on 0.0.0.0:listen-port, the connector moves to B, and the monitor publishes B:listen-port. The monitor then under-reports what the registry exposes, where master's 0.0.0.0 matched it. listen-address is flagged server-restart, but nothing on the server side keeps the new value out of currentConfig.
if (currentConfig.getListenPort() != config.getListenPort()
|| !currentConfig.getListenAddress().equals(config.getListenAddress())) {
rmiConnectorRestart = true;
portChanged = true;
}suggestion (non-blocking): JmxListenAddressTestCase only checks that a TCP handshake is accepted or refused. It never opens a JMX session.
opendj-server-legacy/src/test/java/org/opends/server/protocols/jmx/JmxListenAddressTestCase.java:88-100
isAcceptingConnections is a bare Socket.connect, so nothing beyond the kernel handshake is checked. This is why the blocking issue above stays green. It also means that in the use-ssl row, a plain OpendsRmiServerSocketFactory in the SSL arm of RmiConnector still passes; that swap now compiles, since the local is typed RMIServerSocketFactory. The refusals on getNonLoopbackAddress() have no positive control either: false means refused or dropped alike.
try (ServerSocket control = new ServerSocket(0))
{
assertTrue(isAcceptingConnections(external, control.getLocalPort()),
external.getHostAddress() + " is not reachable from this host, so a refusal on it proves nothing");
}Pin: the JMX session of the blocking issue, with the SSL client environment in the use-ssl row. That session fails if the SSL arm gets a plain factory.
suggestion (non-blocking): no test runs the per-listener SSL setup and transport setup.
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java:767-780, HTTPConnectionHandlerTestCase.java:189
newHandler() hard-codes ds-cfg-use-ssl: false, and the HTTP handler in config.ldif is disabled, so the setSecure/setSSLEngineConfig block inside the loop is never entered. All of these pass every test: moving the block after the loop (last listener only), guarding it to the first iteration, or deleting it. On an HTTPS handler with two listen addresses, the other address would then serve plaintext. Calling configureTransport on the first listener only also survives, because a default Grizzly transport accepts a TCP connect too.
Pin: a use-ssl: true row of listensOnEveryConfiguredListenAddress (key manager cn=JKS, nickname server-cert, as the JMX test does) that completes startHandshake() with a trust-all SSLSocketFactory on both 127.0.0.1 and the non-loopback address.
suggestion (non-blocking): getListeners() can report other addresses than the ones the HTTP server binds, which contradicts the new comment.
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java:764, :243, :432
createHttpServer binds initConfig.getListenAddress(). applyConfigurationChange replaces initConfig (:243), while listeners is filled only in initializeConnectionHandler (:432). Suppose run() disabled the instance itself (two failed starts, or configureSSL at init), and the operator then moves listen-address from A to B. The apply re-enables the handler and run() binds B, but getListeners() and the monitor still report A. The port had the same gap on master.
// Configure one network listener per listen address. HttpServer keys its listeners by name, and each listener
// owns its transport, so neither can be shared. The addresses are initConfig's: getListeners() reports them
// until a configuration change replaces initConfig.Or: fill listeners in createHttpServer from the addresses it binds.
question (non-blocking): should an overlapping listen-address set, such as 0.0.0.0 together with 127.0.0.1, keep starting as it did on master?
opendj-server-legacy/src/main/java/org/opends/server/protocols/http/HTTPConnectionHandler.java:767-771
The handler now binds one listener per address on the same port. With {0.0.0.0, 127.0.0.1} or {0.0.0.0, ::}, the second bind on Linux is expected to fail with EADDRINUSE, since a listening wildcard socket blocks a specific bind even with SO_REUSEADDR. startHttpServer would then fail twice and run() would disable the handler. The LDAP handler already binds each address separately, so failing matches LDAP. If that is intended, a release note is enough. Otherwise, collapse the set to the wildcard when it contains one. Minor either way. Not run: the kernel answer needs a Linux host.
suggestion (non-blocking): the getListeners() change in JmxConnectionHandler.applyConfigurationChange is not pinned.
opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/JmxConnectionHandler.java:140, JmxListenAddressTestCase.java:99
The one getListeners() assert runs right after initializeConnectionHandler (:306). Putting HostPort.allAddresses(config.getListenPort()) back at :140 survives, because the only tests that apply a JMX configuration change (JmxConnectTest.changePort/sslConnect) are @Test(enabled = false).
Pin: in JmxListenAddressTestCase, apply a configuration that differs only in ds-cfg-rmi-port through handler.applyConfigurationChange, then assert getListeners() still containsExactly(new HostPort("127.0.0.1", listenPort)).
ecaa491 to
29e308a
Compare
|
Round 1 is in 29e308a, on top of master 5ced345 (the branch was rebased first, with no conflict). Blocking: the stub of the RMI connector. Fixed. Before it exports the connector, A correction to the pin. It does not fail on every host. When Pin: Non-blocking: the registry stays on the old address. Fixed. A change of Non-blocking: TCP handshake only. Fixed as suggested. The session above replaces the bare connect for the positive case. Non-blocking: per-listener SSL setup. Fixed. Non-blocking: Question: overlapping listen addresses. I kept it as it is, which is what the LDAP handler does: it binds each address separately ( Non-blocking: Runs (macOS, JDK 26, TestNG directly on the reactor build):
|
maximthomas
left a comment
There was a problem hiding this comment.
praise: Round 1's blocking stub problem is fixed where it starts, and the new pins fail on every host.
RmiConnector.advertiseListenAddressruns before the export (RmiConnector.java:420). It keeps an operator'sjava.rmi.server.hostname: thermiServerHostnameSetByServerguard at:463is what lets it follow a later listen-address change as well.- The JMX fixture listens on whichever of 127.0.0.1 and the non-loopback address differs from
getLocalHost()(JmxListenAddressTestCase.java:100-101).assertOpensASessionis therefore red without the fix on this Mac as well as on Linux. listenersis now set in the same place the addresses are bound (HTTPConnectionHandler.java:801,volatileat:136).
issue (non-blocking): After a change from 127.0.0.1 to 0.0.0.0, the JMX stubs still advertise 127.0.0.1.
opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/RmiConnector.java:467-472, JmxConnectionHandler.java:117-120, :149-152
addressChanged restarts the registry and the connector, and advertiseListenAddress(0.0.0.0) clears the server's value. After a clear the JDK keeps the last host it read, as the comment at :469-470 says. Here that host is 127.0.0.1. The re-exported connector stub and every per-client RMIConnectionImpl advertise it, so a remote client, the reason to widen the address, gets Connection refused to host: 127.0.0.1 until the server restarts. "Accepts as well" holds only for a local client. The same happens at boot when a specific-address JMX handler starts before a wildcard one. JDK 26 probe: set 10.9.9.9 and export, clear, export on 0.0.0.0: the stub advertises 10.9.9.9. Master needed a restart for this change too (listen-address is <adm:server-restart/>), so this is not a regression. The runtime move added in this round is incomplete in this one direction. Nothing uses 0.0.0.0 in JmxListenAddressTestCase: dropping the clear, or setting the property to 0.0.0.0, survives the class.
// RmiConnector.advertiseListenAddress, the wildcard branch (import java.net.UnknownHostException)
if (listenAddress.isAnyLocalAddress())
{
// Cleared, the property would leave the JDK advertising the host it last read: the address the connector left.
final InetAddress localHost = localHostOrNull();
if (rmiServerHostnameSetByServer != null && localHost != null && !localHost.isLoopbackAddress())
{
rmiServerHostnameSetByServer = localHost.getHostAddress();
System.setProperty(RMI_SERVER_HOSTNAME, rmiServerHostnameSetByServer);
}
else
{
System.clearProperty(RMI_SERVER_HOSTNAME);
rmiServerHostnameSetByServer = null;
}
}
private static InetAddress localHostOrNull()
{
try
{
return InetAddress.getLocalHost();
}
catch (UnknownHostException e)
{
logger.traceException(e);
return null;
}
}
// JmxListenAddressTestCase (import java.rmi.registry.LocateRegistry, java.rmi.registry.Registry, org.testng.SkipException)
/** Back on the wildcard address, the stubs no longer advertise the loopback address the connector left. */
@Test
public void stopsAdvertisingTheLoopbackAddressOnTheWildcardAddress() throws Exception
{
if (InetAddress.getLocalHost().isLoopbackAddress())
{
throw new SkipException("the JDK advertises a loopback address anyway");
}
final int[] ports = TestCaseUtils.findFreePorts(2);
JmxConnectionHandler handler = newHandler(InetAddress.getByName("127.0.0.1"), ports[0], ports[1], false);
try
{
handler.run();
applyConfigurationChange(handler, newConfig(InetAddress.getByName("0.0.0.0"), ports[0], ports[1], false));
final Registry registry = LocateRegistry.getRegistry("127.0.0.1", ports[0]);
for (String name : registry.list())
{
assertThat(registry.lookup(name).toString()).doesNotContain("[127.0.0.1:").doesNotContain("[0.0.0.0:");
}
}
finally
{
handler.finalizeConnectionHandler(STOP_REASON);
}
}Pin: the case above fails on the clear-only branch at this head and on a 0.0.0.0 mutant. It needs the fixture change in the keepsTheOperatorsRmiServerHostname item below, because its name sorts last among the priority-0 cases. Or: leave a change to the wildcard to the server restart that listen-address is already flagged for.
issue (non-blocking): A running JMX handler goes down when only its listen-address changes to an address the host does not have.
opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/JmxConnectionHandler.java:117-120, :149-158
isConfigurationChangeAcceptable returns true, and isConfigurationAcceptable checks only ports, on the wildcard. A typo such as 10.9.9.9 is therefore accepted and written to config.ldif. finalizeConnectionHandler(true) then closes the registry socket, and createRegistry fails on 10.9.9.9 ("Can't assign requested address"). JMX stays down until the next change or restart, and the only sign is an error result. On master the same change restarted nothing, so the handler served until the restart.
// JmxConnectionHandler.isConfigurationAcceptable, before the port checks
// (import java.net.NetworkInterface, java.net.SocketException)
final InetAddress address = config.getListenAddress();
try
{
if (!address.isAnyLocalAddress() && NetworkInterface.getByInetAddress(address) == null)
{
unacceptableReasons.add(LocalizableMessage.raw("No interface of this host has the address " + address.getHostAddress()));
return false;
}
}
catch (SocketException e)
{
logger.traceException(e);
}Or: when initialize() fails, restart on the previous configuration.
issue (non-blocking): reportsTheListenAddressItStartsWithAfterAChange can still lose its change to the handler thread.
opendj-server-legacy/src/test/java/org/opends/server/protocols/http/HTTPConnectionHandlerTestCase.java:195-205, HTTPConnectionHandler.java:734-735, :262
The handler thread sends the consecutive-failures alert at :734 and sets enabled = false only at :735, after the other alert handlers and the log line. DummyAlertHandler's count is visible before that. If the thread is preempted in that window, the test's apply sets enabled = true (:262) and :735 then overwrites it. The handler idles, and the second wait fails with "the new listen address is not listened on" on correct code. This is rare, but nothing orders the two writes: there is no shared lock and enabled is not volatile.
final InetAddress loopback = loopback();
timer.repeatUntilSuccess(new TestTimer.CallableVoid()
{
@Override
public void call() throws Exception
{
// Applied again until it holds: the handler thread clears enabled just after it sends the alert.
handler.applyConfigurationChange(newConfig(false, listenPort, "127.0.0.1"));
assertTrue(isAcceptingConnections(loopback, listenPort), "the new listen address is not listened on");
}
});issue (non-blocking): keepsTheOperatorsRmiServerHostname passes only because of the order in which the priority-0 cases run.
opendj-server-legacy/src/test/java/org/opends/server/protocols/jmx/JmxListenAddressTestCase.java:252-266, RmiConnector.java:182, :463
The operator's value is otherAddress (:256), and the static rmiServerHostnameSetByServer is never reset between cases. movesToANewListenAddress leaves that field at otherAddress. If it were the last priority-0 case to run (after a rename, a new case or another TestNG order), the guard at :463 would take the operator's value for the server's own. :476-477 would then overwrite it, and :264 would fail with no product change. Today it passes because reportsTheListenAddressAfterAChangeOfTheRmiPort sorts later and records listenAddress again.
final String operatorsHostname = "203.0.113.7"; // TEST-NET-3: a value no case makes the server recordsuggestion (non-blocking): State in the docs that a specific JMX listen address sets java.rmi.server.hostname for the whole JVM.
opendj-server-legacy/src/main/java/org/opends/server/protocols/jmx/RmiConnector.java:182, :460-479
Every later RMI export in the process reads the property again. That includes the platform JMX agent's per-client RMIConnectionImpl (-Dcom.sun.management.jmxremote.port) and the exports of an embedding application. With the JMX handler on 127.0.0.1 and no operator value, remote jconsole clients of the platform agent get stubs for 127.0.0.1. A JDK 26 probe confirmed this, and disabling the handler does not undo it. With two JMX handlers on different specific addresses, the handler that started last wins, and every later client of the other one is refused. The JDK has no API to set the host for one export only, so this is the cost of the approach. It belongs in the listen-address description of the JMX handler or in the release notes, together with the fact that -Djava.rmi.server.hostname overrides it.
…n the HTTP connection handler and the JMX RMI connector The HTTP connection handler bound a single Grizzly listener to the wildcard address whatever listen-address said, and the RMI connector of the JMX connection handler, on rmi-port, did the same (DirectoryRMIServerSocketFactory bound 0.0.0.0 with SSL, the default RMI factory without it). Both accepted connections on every interface of the host while the configuration and the monitor showed only the configured address. HTTP: one NetworkListener per listen address, on the addresses and the port that getListeners() reports. Each listener gets its own name, since HttpServer keys its listeners by name, and its own transport settings, since each listener owns its transport. When HttpServer.start() fails on one address, the listeners already started are shut down: the caller only forgets about the server, so their ports would stay bound for good. JMX: the RMI connector listens on listen-address, as the RMI registry already does, with and without SSL, and getListeners() reports that address instead of 0.0.0.0.
…of the JMX RMI connector, and move the RMI registry with it The stub that a JMX client gets from the RMI registry advertises java.rmi.server.hostname, or else the address of the local host, whatever address the connector listens on. With the connector bound to its listen address, a client was sent to an address that refuses it whenever the two differ: listen-address 127.0.0.1 on a host whose name resolves to 127.0.1.1 or to a LAN address, or one address of a multi-homed host. RmiConnector now sets the property to the listen address before it exports the connector, keeps a value that the operator set, and clears its own value when the connector goes back to the wildcard address. A change of listen-address restarts the RMI registry as well as the connector, so the registry, the connector and getListeners() stay on the same address. The HTTP connection handler reports the addresses and the port its server starts with: a configuration change replaces initConfig, and a handler that had given up starting starts on the new values. Tests: JmxListenAddressTestCase opens a JMX session through the registry for both use-ssl values, on the address that is not the local host's, checks that the other address is reachable before it counts a refusal there, and covers an rmi-port change, a listen-address change and an operator's java.rmi.server.hostname. HTTPConnectionHandlerTestCase completes a TLS handshake on both listen addresses of an HTTPS handler, and checks the reported address after a change that lets the handler start.
…dler restarts, and advertise the local host on the wildcard address listen-address of the JMX connection handler requires a server restart. The previous round restarted the RMI registry and the connector on a change of it instead, which moved a running handler to an address the host may not have (a typo left JMX down), and to the wildcard address with stubs that still advertised the loopback address it left. The handler now keeps the address it was initialized with: the registry, a connector restarted by another change and getListeners() stay on it until the handler restarts. A handler restarted on the wildcard address, after one on a specific address in the same JVM, sets java.rmi.server.hostname to the local host when that is not a loopback address, instead of clearing it: cleared, the property leaves the JDK advertising the host it last read. The description of listen-address states that a specific address sets java.rmi.server.hostname for the whole JVM unless it is already set. Tests: keepsItsListenAddressUntilItRestarts replaces movesToANewListenAddress; stopsAdvertisingTheLoopbackAddressOnTheWildcardAddress looks up the stubs of a wildcard handler started after a loopback one; the operator's value is "localhost", which the server never records itself; the HTTP case applies its change again until it holds, since the handler thread disables the handler just after it sends the alert.
29e308a to
0585602
Compare
|
Round 2 is in 0585602, on top of master 61d6274 (the branch was rebased first, with no conflict). The first two items: the wildcard address after 127.0.0.1, and a listen address the host does not have. I took the other road you named: a change of The wildcard branch still needed your fix. Disabling and enabling the handler creates a new instance with the current configuration, so 127.0.0.1 followed by 0.0.0.0 still happens without a server restart, and so does a boot with a specific-address handler before a wildcard one. Pins:
Third item: the HTTP race. Fixed as suggested. The change is applied again inside the wait until it holds. The wait for the alert stays, so the first apply does not land before the handler gives up. Fourth item: the operator's value. Fixed, with Suggestion: document the global property. Done in the description of the JMX handler's Runs (macOS, JDK 26):
|
maximthomas
left a comment
There was a problem hiding this comment.
praise: Round 2's two JMX items are closed at the source, not worked around.
- A change of
listen-addressnow waits for the restart that<adm:server-restart/>announces.listenAddressis set only at initialization (JmxConnectionHandler.java:314), andgetListenAddress(), the listeners and a connector restarted by another change all read it. A typo no longer takes JMX down, and the registry and the connector can no longer end up on different addresses. - The wildcard arm advertises the local host, which is the JDK's own default, instead of clearing back to a stale loopback host. It keeps the clear when the local host is a loopback address (
RmiConnector.java:468-485). - The HTTP case applies its change again inside the wait (
HTTPConnectionHandlerTestCase.java:212), so the race with the handler thread cannot undo it.
Fixes #1112.
Problem
The HTTP connection handler bound a single Grizzly listener to
0.0.0.0whateverlisten-addresssaid, and the RMI connector of the JMX connection handler, onrmi-port, did the same:DirectoryRMIServerSocketFactorybound0.0.0.0withuse-ssl: true, and without SSL there was no server socket factory at all, so RMI used its default one. Both accepted connections on every interface of the host, while the configuration and the monitor showed only the configured address. The RMI registry of the JMX handler, onlisten-port, already listened onlisten-address.The LDAP connection handlers and the administration connector are not affected. The SNMP connection handler has a read-only
listen-addressfixed at0.0.0.0. With the defaultlisten-address(0.0.0.0), which the packaging does not change, the behaviour stays the same.Changes
HTTP (
HTTPConnectionHandler):NetworkListenerper listen address, on the addresses and the port ofinitConfig(as before for the port).createHttpServeralso sets whatgetListeners()reports from them, since a configuration change replacesinitConfigand a handler that had given up starting starts on the new values; the list is volatile and unmodifiable, since the monitor reads it from other threads;OpenDJ-HTTP <address>):HttpServerkeys its listeners by name, so with the old single name only one listener would be kept;createHttpServerapplied to the single one (configureTransport): eachNetworkListenerowns its transport, and sharing one would not work, sinceNetworkListener.start()sets the transport's processor andshutdownNow()shuts the transport down. With several listen addresses, the number of selector threads is multiplied by the number of addresses;HttpServer.start()fails,startHttpServershuts the server down before rethrowing.HttpServer.start()stops at the first listener that cannot bind and leaves the listeners started before it bound, whilerun()only forgets about the server: without this, their ports would stay bound until the JVM stops.JMX:
DirectoryRMIServerSocketFactorytakes the listen address, and binds it instead of0.0.0.0; the address takes part inequals/hashCode, which RMI uses to share endpoints;RmiConnectorpassesOpendsRmiServerSocketFactory(listenAddress), the factory of the registry, instead ofnull;JmxConnectionHandler.getListeners()reports the listen address instead ofHostPort.allAddresses(...)(the same value for0.0.0.0);RmiConnectorsetsjava.rmi.server.hostnameto the listen address. The stub that a client gets from the registry advertises that property, or else the address of the local host, whatever address the connector listens on: without it,listen-address: 127.0.0.1on a host whose name resolves to 127.0.1.1 or to a LAN address sent clients to an address that refuses them. A value that the server did not set is the operator's, and is kept. On the wildcard address, the server's own value becomes the local host when that is not a loopback address, and is cleared otherwise: cleared, the property would leave the JDK advertising the address a previous handler listened on. The property applies to the whole JVM, which the description oflisten-addressnow states;listen-addressrequires a restart: the registry, a connector restarted by another change (rmi-port, SSL) andgetListeners()stay on it until the handler restarts, as the LDAP and HTTP handlers do.Several listen addresses that overlap, such as
0.0.0.0together with127.0.0.1, make the HTTP handler fail to start on Linux, where a listening wildcard socket blocks a specific bind on the same port: the LDAP connection handler already binds each address separately and fails on the same set.{0.0.0.0, ::}fails on any OS, since Java binds both as[::]on a dual-stack host.Tests
HTTPConnectionHandlerTestCase:listensOnlyOnTheConfiguredListenAddress(127.0.0.1accepts, a non-loopback IPv4 address of the host refuses),listensOnEveryConfiguredListenAddress(127.0.0.1and the non-loopback address both accept),releasesTheBoundListenAddressesWhenAnotherCannotBeBound(127.0.0.1and192.0.2.1, from TEST-NET-1: the port of127.0.0.1is released once the handler gives up). The listener of127.0.0.1starts before the one of192.0.2.1:HttpServerstarts its listeners in the order of a hash map of their names, and this order was checked with the mutant below.HTTPConnectionHandlerTestCasealso: ause-ssl: truerow oflistensOnEveryConfiguredListenAddress, which completes a TLS handshake on both addresses, andreportsTheListenAddressItStartsWithAfterAChange(a handler on192.0.2.1gives up, thenlisten-address: 127.0.0.1is applied: it listens there andgetListeners()reports it). That case waits for the consecutive-failures alert, then applies the change again until it holds: a change applied while the second start attempt is still running, or just after the alert, is undone when the handler thread disables the handler, a race older than this PR.JmxListenAddressTestCase(new): the listen address is whichever of127.0.0.1and the non-loopback address differs fromInetAddress.getLocalHost(), and the other one must accept a connection to a wildcard socket before a refusal there counts.listensOnlyOnTheConfiguredListenAddress, withuse-sslfalse and true: the registry and the RMI connector answer on the listen address only,getListeners()reports it, and a JMX session opens through the registry and callsgetMBeanCount();reportsTheListenAddressAfterAChangeOfTheRmiPort;keepsItsListenAddressUntilItRestarts(a change of the address together withrmi-portleaves the registry, the connector, the listeners and the session on the original address);stopsAdvertisingTheLoopbackAddressOnTheWildcardAddress(the stubs of a wildcard handler started after a loopback one do not name 127.0.0.1 or 0.0.0.0; skipped when the local host is a loopback address);keepsTheOperatorsRmiServerHostname(withlocalhost, a name the server never records itself).getLocalHost()is exactly127.0.0.1, the JDK learns its host from the connector's own binding in the registry, and a stub that does not advertise the listen address goes unnoticed: the session check fails on hosts whose name resolves elsewhere, as the Linux CI cells do, and was checked locally with-Djdk.net.hosts.filemapping the host name to 127.0.1.1.TestCaseUtils.getNonLoopbackAddress()/isAcceptingConnections(): the tests are skipped on a host without a non-loopback IPv4 address.Local runs (macOS, JDK 11):
HTTPConnectionHandlerTestCase4,JmxListenAddressTestCase2);listensOnlyOnTheConfiguredListenAddressandreleasesTheBoundListenAddressesWhenAnotherCannotBeBoundfail for HTTP, both JMX cases fail on the RMI connector (the RMI connector answers on <non-loopback address>, which is not the listen address);shutdownNow()on a failed start, the HTTP listener back onDEFAULT_NETWORK_HOST, no server socket factory without SSL,0.0.0.0inDirectoryRMIServerSocketFactory,HostPort.allAddressesingetListeners();org/opends/server/protocols/jmx/**,org/opends/server/protocols/http/**andDsconfigOptionsTestCasepass (two classes hit anAddress already in useon the test server's own ports at startup, a local port collision with another run, and passed when run again);packagewith javadoc passes foropendj-server-legacy.Review round 1 (macOS, JDK 26, on master 5ced345):
HTTPConnectionHandlerTestCase6/6,JmxListenAddressTestCase5/5, and 5/5 again with the host name mapped to 127.0.1.1;java.rmi.server.hostname(with the host name mapped to 127.0.1.1:Connection refused to host: 127.0.1.1), an operator's value overwritten, the registry left on the old address,HostPort.allAddressesafter a change, a plain factory in the SSL arm, SSL on the first HTTP listener only, SSL on no HTTP listener,getListeners()not refreshed when the HTTP server starts;org/opends/server/protocols/jmx/**andorg/opends/server/protocols/http/**: 57 tests, no failure;packagewith javadoc passes.Review round 2 (macOS, JDK 26, on master 61d6274):
HTTPConnectionHandlerTestCase6/6;JmxListenAddressTestCase6/6 with the host name mapped to the LAN address;getListenAddress()reading the current configuration, an operator's value overwritten,HostPort.allAddressesafter a change, a plain factory in the SSL arm, SSL on the first HTTP listener only, SSL on no HTTP listener,listenersnot refreshed increateHttpServer;org/opends/server/protocols/jmx/**andorg/opends/server/protocols/http/**: 58 tests, no failure, the wildcard case skipped;packagewith javadoc passes.Related
createSSLContext,applyConfigurationChange), notcreateHttpServer; the branch is rebased on top of them.