Only bind loopback addresses when binding to local

* Only bind loopback addresses when binding to local

Today when binding to local (the default) we bind to any address that is
a loopback address, or any address on an interface that declares itself
as a loopback interface. Yet, not all addresses on loopback interfaces
are loopback addresses. This arises on macOS where there is a link-local
address assigned to the loopback interface (fe80::1%lo0) and in Docker
services where virtual IPs of the service are assigned to the loopback
interface (docker/libnetwork#1877). These situations cause problems:
 - because we do not handle the scope ID of a link-local address, we end
   up bound to an address for which publishing of that address does not
   allow that address to be reached (since we drop the scope)
 - the virtual IPs in the Docker situation are not loopback addresses,
   they are not link-local addresses, so we end up bound to interfaces
   that cause the bootstrap checks to be enforced even though the
   instance is only bound to local

We address this by only binding to actual loopback addresses, and skip
binding to any address on a loopback interface that is not a loopback
address. This lets us simplify some code where in the bootstrap checks
we were skipping link-local addresses, and in writing the ports file
where we had to skip link-local addresses because again the formatting
of them does not allow them to be connected to by another node (to be
clear, they could be connected to via the scope-qualified address, but
that information is not written out).

Relates #28029
This commit is contained in:
Jason Tedor 2018-01-02 07:04:09 -05:00 committed by GitHub
parent 1877139e89
commit a91da9a9af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 17 additions and 25 deletions

View File

@ -132,7 +132,7 @@ final class BootstrapChecks {
} }
if (enforceLimits) { if (enforceLimits) {
logger.info("bound or publishing to a non-loopback or non-link-local address, enforcing bootstrap checks"); logger.info("bound or publishing to a non-loopback address, enforcing bootstrap checks");
} else if (enforceBootstrapChecks) { } else if (enforceBootstrapChecks) {
logger.info("explicitly enforcing bootstrap checks"); logger.info("explicitly enforcing bootstrap checks");
} }
@ -176,11 +176,10 @@ final class BootstrapChecks {
* @return {@code true} if the checks should be enforced * @return {@code true} if the checks should be enforced
*/ */
static boolean enforceLimits(final BoundTransportAddress boundTransportAddress, final String discoveryType) { static boolean enforceLimits(final BoundTransportAddress boundTransportAddress, final String discoveryType) {
final Predicate<TransportAddress> isLoopbackOrLinkLocalAddress = final Predicate<TransportAddress> isLoopbackAddress = t -> t.address().getAddress().isLoopbackAddress();
t -> t.address().getAddress().isLinkLocalAddress() || t.address().getAddress().isLoopbackAddress();
final boolean bound = final boolean bound =
!(Arrays.stream(boundTransportAddress.boundAddresses()).allMatch(isLoopbackOrLinkLocalAddress) && !(Arrays.stream(boundTransportAddress.boundAddresses()).allMatch(isLoopbackAddress) &&
isLoopbackOrLinkLocalAddress.test(boundTransportAddress.publishAddress())); isLoopbackAddress.test(boundTransportAddress.publishAddress()));
return bound && !"single-node".equals(discoveryType); return bound && !"single-node".equals(discoveryType);
} }

View File

@ -155,11 +155,8 @@ public abstract class NetworkUtils {
List<InetAddress> list = new ArrayList<>(); List<InetAddress> list = new ArrayList<>();
for (NetworkInterface intf : getInterfaces()) { for (NetworkInterface intf : getInterfaces()) {
if (intf.isUp()) { if (intf.isUp()) {
// NOTE: some operating systems (e.g. BSD stack) assign a link local address to the loopback interface
// while technically not a loopback address, some of these treat them as one (e.g. OS X "localhost") so we must too,
// otherwise things just won't work out of box. So we include all addresses from loopback interfaces.
for (InetAddress address : Collections.list(intf.getInetAddresses())) { for (InetAddress address : Collections.list(intf.getInetAddresses())) {
if (intf.isLoopback() || address.isLoopbackAddress()) { if (address.isLoopbackAddress()) {
list.add(address); list.add(address);
} }
} }

View File

@ -858,10 +858,6 @@ public class Node implements Closeable {
try (BufferedWriter writer = Files.newBufferedWriter(tmpPortsFile, Charset.forName("UTF-8"))) { try (BufferedWriter writer = Files.newBufferedWriter(tmpPortsFile, Charset.forName("UTF-8"))) {
for (TransportAddress address : boundAddress.boundAddresses()) { for (TransportAddress address : boundAddress.boundAddresses()) {
InetAddress inetAddress = InetAddress.getByName(address.getAddress()); InetAddress inetAddress = InetAddress.getByName(address.getAddress());
if (inetAddress instanceof Inet6Address && inetAddress.isLinkLocalAddress()) {
// no link local, just causes problems
continue;
}
writer.write(NetworkAddress.format(new InetSocketAddress(inetAddress, address.getPort())) + "\n"); writer.write(NetworkAddress.format(new InetSocketAddress(inetAddress, address.getPort())) + "\n");
} }
} catch (IOException e) { } catch (IOException e) {

View File

@ -78,7 +78,7 @@ public class BootstrapChecksTests extends ESTestCase {
public void testLogMessageInProductionMode() throws NodeValidationException { public void testLogMessageInProductionMode() throws NodeValidationException {
final Logger logger = mock(Logger.class); final Logger logger = mock(Logger.class);
BootstrapChecks.check(defaultContext, true, Collections.emptyList(), logger); BootstrapChecks.check(defaultContext, true, Collections.emptyList(), logger);
verify(logger).info("bound or publishing to a non-loopback or non-link-local address, enforcing bootstrap checks"); verify(logger).info("bound or publishing to a non-loopback address, enforcing bootstrap checks");
verifyNoMoreInteractions(logger); verifyNoMoreInteractions(logger);
} }

View File

@ -23,16 +23,16 @@ documented individually.
[float] [float]
=== Development vs. production mode === Development vs. production mode
By default, Elasticsearch binds to `localhost` for <<modules-http,HTTP>> and By default, Elasticsearch binds to loopback addresses for <<modules-http,HTTP>>
<<modules-transport,transport (internal)>> communication. This is fine for and <<modules-transport,transport (internal)>> communication. This is fine for
downloading and playing with Elasticsearch as well as everyday development, but it's downloading and playing with Elasticsearch as well as everyday development, but
useless for production systems. To join a cluster, an Elasticsearch node must be it's useless for production systems. To join a cluster, an Elasticsearch node
reachable via transport communication. To join a cluster over an external must be reachable via transport communication. To join a cluster via a
network interface, a node must bind transport to an external interface and not non-loopback address, a node must bind transport to a non-loopback address and
be using <<single-node-discovery,single-node discovery>>. Thus, we consider an not be using <<single-node-discovery,single-node discovery>>. Thus, we consider
Elasticsearch node to be in development mode if it can not form a cluster with an Elasticsearch node to be in development mode if it can not form a cluster
another machine over an external network interface, and is otherwise in with another machine via a non-loopback address, and is otherwise in production
production mode if it can join a cluster over an external interface. mode if it can join a cluster via non-loopback addresses.
Note that HTTP and transport can be configured independently via Note that HTTP and transport can be configured independently via
<<modules-http,`http.host`>> and <<modules-transport,`transport.host`>>; this <<modules-http,`http.host`>> and <<modules-transport,`transport.host`>>; this