Allow binding to multiple addresses

* Allow for multiple host specifications (e.g. _en0_,192.168.1.2,_site_).
* Add _site_ and _global_ scopes as counterparts to _local_.
* Warn on heuristic selection of publish address.
* Remove the arbitrary _non_loopback_ setting.

Closes #13954
This commit is contained in:
Robert Muir 2015-10-23 23:43:37 -04:00
parent c7f5911d1b
commit 6c8e290322
13 changed files with 229 additions and 126 deletions

View File

@ -27,6 +27,9 @@ import org.elasticsearch.common.unit.TimeValue;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -90,13 +93,21 @@ public class NetworkService extends AbstractComponent {
customNameResolvers.add(customNameResolver); customNameResolvers.add(customNameResolver);
} }
public InetAddress[] resolveBindHostAddress(String bindHost) throws IOException { /**
* Resolves {@code bindHosts} to a list of internet addresses. The list will
* not contain duplicate addresses.
* @param bindHosts list of hosts to bind to. this may contain special pseudo-hostnames
* such as _local_ (see the documentation). if it is null, it will be populated
* based on global default settings.
* @return unique set of internet addresses
*/
public InetAddress[] resolveBindHostAddresses(String bindHosts[]) throws IOException {
// first check settings // first check settings
if (bindHost == null) { if (bindHosts == null) {
bindHost = settings.get(GLOBAL_NETWORK_BINDHOST_SETTING, settings.get(GLOBAL_NETWORK_HOST_SETTING)); bindHosts = settings.getAsArray(GLOBAL_NETWORK_BINDHOST_SETTING, settings.getAsArray(GLOBAL_NETWORK_HOST_SETTING, null));
} }
// next check any registered custom resolvers // next check any registered custom resolvers
if (bindHost == null) { if (bindHosts == null) {
for (CustomNameResolver customNameResolver : customNameResolvers) { for (CustomNameResolver customNameResolver : customNameResolvers) {
InetAddress addresses[] = customNameResolver.resolveDefault(); InetAddress addresses[] = customNameResolver.resolveDefault();
if (addresses != null) { if (addresses != null) {
@ -105,31 +116,44 @@ public class NetworkService extends AbstractComponent {
} }
} }
// finally, fill with our default // finally, fill with our default
if (bindHost == null) { if (bindHosts == null) {
bindHost = DEFAULT_NETWORK_HOST; bindHosts = new String[] { DEFAULT_NETWORK_HOST };
} }
InetAddress addresses[] = resolveInetAddress(bindHost); InetAddress addresses[] = resolveInetAddresses(bindHosts);
// try to deal with some (mis)configuration // try to deal with some (mis)configuration
if (addresses != null) {
for (InetAddress address : addresses) { for (InetAddress address : addresses) {
// check if its multicast: flat out mistake // check if its multicast: flat out mistake
if (address.isMulticastAddress()) { if (address.isMulticastAddress()) {
throw new IllegalArgumentException("bind address: {" + NetworkAddress.format(address) + "} is invalid: multicast address"); throw new IllegalArgumentException("bind address: {" + NetworkAddress.format(address) + "} is invalid: multicast address");
} }
// check if its a wildcard address: this is only ok if its the only address!
if (address.isAnyLocalAddress() && addresses.length > 1) {
throw new IllegalArgumentException("bind address: {" + NetworkAddress.format(address) + "} is wildcard, but multiple addresses specified: this makes no sense");
} }
} }
return addresses; return addresses;
} }
/**
* Resolves {@code publishHosts} to a single publish address. The fact that it returns
* only one address is just a current limitation.
* <p>
* If {@code publishHosts} resolves to more than one address, <b>then one is selected with magic</b>,
* and the user is warned (they can always just be more specific).
* @param publishHosts list of hosts to publish as. this may contain special pseudo-hostnames
* such as _local_ (see the documentation). if it is null, it will be populated
* based on global default settings.
* @return single internet address
*/
// TODO: needs to be InetAddress[] // TODO: needs to be InetAddress[]
public InetAddress resolvePublishHostAddress(String publishHost) throws IOException { public InetAddress resolvePublishHostAddresses(String publishHosts[]) throws IOException {
// first check settings // first check settings
if (publishHost == null) { if (publishHosts == null) {
publishHost = settings.get(GLOBAL_NETWORK_PUBLISHHOST_SETTING, settings.get(GLOBAL_NETWORK_HOST_SETTING)); publishHosts = settings.getAsArray(GLOBAL_NETWORK_PUBLISHHOST_SETTING, settings.getAsArray(GLOBAL_NETWORK_HOST_SETTING, null));
} }
// next check any registered custom resolvers // next check any registered custom resolvers
if (publishHost == null) { if (publishHosts == null) {
for (CustomNameResolver customNameResolver : customNameResolvers) { for (CustomNameResolver customNameResolver : customNameResolvers) {
InetAddress addresses[] = customNameResolver.resolveDefault(); InetAddress addresses[] = customNameResolver.resolveDefault();
if (addresses != null) { if (addresses != null) {
@ -138,30 +162,59 @@ public class NetworkService extends AbstractComponent {
} }
} }
// finally, fill with our default // finally, fill with our default
if (publishHost == null) { if (publishHosts == null) {
publishHost = DEFAULT_NETWORK_HOST; publishHosts = new String[] { DEFAULT_NETWORK_HOST };
} }
InetAddress addresses[] = resolveInetAddresses(publishHosts);
// TODO: allow publishing multiple addresses // TODO: allow publishing multiple addresses
InetAddress address = resolveInetAddress(publishHost)[0]; // for now... the hack begins
// try to deal with some (mis)configuration // 1. single wildcard address, probably set by network.host: expand to all interface addresses.
if (address != null) { if (addresses.length == 1 && addresses[0].isAnyLocalAddress()) {
HashSet<InetAddress> all = new HashSet<>(Arrays.asList(NetworkUtils.getAllAddresses()));
addresses = all.toArray(new InetAddress[all.size()]);
}
// 2. try to deal with some (mis)configuration
for (InetAddress address : addresses) {
// check if its multicast: flat out mistake // check if its multicast: flat out mistake
if (address.isMulticastAddress()) { if (address.isMulticastAddress()) {
throw new IllegalArgumentException("publish address: {" + NetworkAddress.format(address) + "} is invalid: multicast address"); throw new IllegalArgumentException("publish address: {" + NetworkAddress.format(address) + "} is invalid: multicast address");
} }
// wildcard address, probably set by network.host // check if its a wildcard address: this is only ok if its the only address!
// (if it was a single wildcard address, it was replaced by step 1 above)
if (address.isAnyLocalAddress()) { if (address.isAnyLocalAddress()) {
InetAddress old = address; throw new IllegalArgumentException("publish address: {" + NetworkAddress.format(address) + "} is wildcard, but multiple addresses specified: this makes no sense");
address = NetworkUtils.getFirstNonLoopbackAddresses()[0];
logger.warn("publish address: {{}} is a wildcard address, falling back to first non-loopback: {{}}",
NetworkAddress.format(old), NetworkAddress.format(address));
} }
} }
return address;
}
private InetAddress[] resolveInetAddress(String host) throws IOException { // 3. warn user if we end out with multiple publish addresses
if (addresses.length > 1) {
List<InetAddress> sorted = new ArrayList<>(Arrays.asList(addresses));
NetworkUtils.sortAddresses(sorted);
addresses = new InetAddress[] { sorted.get(0) };
logger.warn("publish host: {} resolves to multiple addresses, auto-selecting {{}} as single publish address",
Arrays.toString(publishHosts), NetworkAddress.format(addresses[0]));
}
return addresses[0];
}
/** resolves (and deduplicates) host specification */
private InetAddress[] resolveInetAddresses(String hosts[]) throws IOException {
if (hosts.length == 0) {
throw new IllegalArgumentException("empty host specification");
}
// deduplicate, in case of resolver misconfiguration
// stuff like https://bugzilla.redhat.com/show_bug.cgi?id=496300
HashSet<InetAddress> set = new HashSet<>();
for (String host : hosts) {
set.addAll(Arrays.asList(resolveInternal(host)));
}
return set.toArray(new InetAddress[set.size()]);
}
/** resolves a single host specification */
private InetAddress[] resolveInternal(String host) throws IOException {
if ((host.startsWith("#") && host.endsWith("#")) || (host.startsWith("_") && host.endsWith("_"))) { if ((host.startsWith("#") && host.endsWith("#")) || (host.startsWith("_") && host.endsWith("_"))) {
host = host.substring(1, host.length() - 1); host = host.substring(1, host.length() - 1);
// allow custom resolvers to have special names // allow custom resolvers to have special names
@ -178,12 +231,18 @@ public class NetworkService extends AbstractComponent {
return NetworkUtils.filterIPV4(NetworkUtils.getLoopbackAddresses()); return NetworkUtils.filterIPV4(NetworkUtils.getLoopbackAddresses());
case "local:ipv6": case "local:ipv6":
return NetworkUtils.filterIPV6(NetworkUtils.getLoopbackAddresses()); return NetworkUtils.filterIPV6(NetworkUtils.getLoopbackAddresses());
case "non_loopback": case "site":
return NetworkUtils.getFirstNonLoopbackAddresses(); return NetworkUtils.getSiteLocalAddresses();
case "non_loopback:ipv4": case "site:ipv4":
return NetworkUtils.filterIPV4(NetworkUtils.getFirstNonLoopbackAddresses()); return NetworkUtils.filterIPV4(NetworkUtils.getSiteLocalAddresses());
case "non_loopback:ipv6": case "site:ipv6":
return NetworkUtils.filterIPV6(NetworkUtils.getFirstNonLoopbackAddresses()); return NetworkUtils.filterIPV6(NetworkUtils.getSiteLocalAddresses());
case "global":
return NetworkUtils.getGlobalAddresses();
case "global:ipv4":
return NetworkUtils.filterIPV4(NetworkUtils.getGlobalAddresses());
case "global:ipv6":
return NetworkUtils.filterIPV6(NetworkUtils.getGlobalAddresses());
default: default:
/* an interface specification */ /* an interface specification */
if (host.endsWith(":ipv4")) { if (host.endsWith(":ipv4")) {
@ -197,6 +256,6 @@ public class NetworkService extends AbstractComponent {
} }
} }
} }
return NetworkUtils.getAllByName(host); return InetAddress.getAllByName(host);
} }
} }

View File

@ -27,12 +27,10 @@ import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet;
import java.util.List; import java.util.List;
/** /**
@ -109,7 +107,8 @@ public abstract class NetworkUtils {
* @deprecated remove this when multihoming is really correct * @deprecated remove this when multihoming is really correct
*/ */
@Deprecated @Deprecated
static void sortAddresses(List<InetAddress> list) { // only public because of silly multicast
public static void sortAddresses(List<InetAddress> list) {
Collections.sort(list, new Comparator<InetAddress>() { Collections.sort(list, new Comparator<InetAddress>() {
@Override @Override
public int compare(InetAddress left, InetAddress right) { public int compare(InetAddress left, InetAddress right) {
@ -150,34 +149,79 @@ public abstract class NetworkUtils {
return Constants.WINDOWS ? false : true; return Constants.WINDOWS ? false : true;
} }
/** Returns addresses for all loopback interfaces that are up. */ /** Returns all interface-local scope (loopback) addresses for interfaces that are up. */
static InetAddress[] getLoopbackAddresses() throws SocketException { static InetAddress[] getLoopbackAddresses() throws SocketException {
List<InetAddress> list = new ArrayList<>(); List<InetAddress> list = new ArrayList<>();
for (NetworkInterface intf : getInterfaces()) { for (NetworkInterface intf : getInterfaces()) {
if (intf.isLoopback() && intf.isUp()) { if (intf.isUp()) {
list.addAll(Collections.list(intf.getInetAddresses())); // 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())) {
if (intf.isLoopback() || address.isLoopbackAddress()) {
list.add(address);
}
}
} }
} }
if (list.isEmpty()) { if (list.isEmpty()) {
throw new IllegalArgumentException("No up-and-running loopback interfaces found, got " + getInterfaces()); throw new IllegalArgumentException("No up-and-running loopback addresses found, got " + getInterfaces());
} }
sortAddresses(list);
return list.toArray(new InetAddress[list.size()]); return list.toArray(new InetAddress[list.size()]);
} }
/** Returns addresses for the first non-loopback interface that is up. */ /** Returns all site-local scope (private) addresses for interfaces that are up. */
static InetAddress[] getFirstNonLoopbackAddresses() throws SocketException { static InetAddress[] getSiteLocalAddresses() throws SocketException {
List<InetAddress> list = new ArrayList<>(); List<InetAddress> list = new ArrayList<>();
for (NetworkInterface intf : getInterfaces()) { for (NetworkInterface intf : getInterfaces()) {
if (intf.isLoopback() == false && intf.isUp()) { if (intf.isUp()) {
list.addAll(Collections.list(intf.getInetAddresses())); for (InetAddress address : Collections.list(intf.getInetAddresses())) {
break; if (address.isSiteLocalAddress()) {
list.add(address);
}
}
} }
} }
if (list.isEmpty()) { if (list.isEmpty()) {
throw new IllegalArgumentException("No up-and-running non-loopback interfaces found, got " + getInterfaces()); throw new IllegalArgumentException("No up-and-running site-local (private) addresses found, got " + getInterfaces());
}
return list.toArray(new InetAddress[list.size()]);
}
/** Returns all global scope addresses for interfaces that are up. */
static InetAddress[] getGlobalAddresses() throws SocketException {
List<InetAddress> list = new ArrayList<>();
for (NetworkInterface intf : getInterfaces()) {
if (intf.isUp()) {
for (InetAddress address : Collections.list(intf.getInetAddresses())) {
if (address.isLoopbackAddress() == false &&
address.isSiteLocalAddress() == false &&
address.isLinkLocalAddress() == false) {
list.add(address);
}
}
}
}
if (list.isEmpty()) {
throw new IllegalArgumentException("No up-and-running global-scope (public) addresses found, got " + getInterfaces());
}
return list.toArray(new InetAddress[list.size()]);
}
/** Returns all addresses (any scope) for interfaces that are up.
* This is only used to pick a publish address, when the user set network.host to a wildcard */
static InetAddress[] getAllAddresses() throws SocketException {
List<InetAddress> list = new ArrayList<>();
for (NetworkInterface intf : getInterfaces()) {
if (intf.isUp()) {
for (InetAddress address : Collections.list(intf.getInetAddresses())) {
list.add(address);
}
}
}
if (list.isEmpty()) {
throw new IllegalArgumentException("No up-and-running addresses found, got " + getInterfaces());
} }
sortAddresses(list);
return list.toArray(new InetAddress[list.size()]); return list.toArray(new InetAddress[list.size()]);
} }
@ -194,20 +238,9 @@ public abstract class NetworkUtils {
if (list.isEmpty()) { if (list.isEmpty()) {
throw new IllegalArgumentException("Interface '" + name + "' has no internet addresses"); throw new IllegalArgumentException("Interface '" + name + "' has no internet addresses");
} }
sortAddresses(list);
return list.toArray(new InetAddress[list.size()]); return list.toArray(new InetAddress[list.size()]);
} }
/** Returns addresses for the given host, sorted by order of preference */
static InetAddress[] getAllByName(String host) throws UnknownHostException {
InetAddress addresses[] = InetAddress.getAllByName(host);
// deduplicate, in case of resolver misconfiguration
// stuff like https://bugzilla.redhat.com/show_bug.cgi?id=496300
List<InetAddress> unique = new ArrayList<>(new HashSet<>(Arrays.asList(addresses)));
sortAddresses(unique);
return unique.toArray(new InetAddress[unique.size()]);
}
/** Returns only the IPV4 addresses in {@code addresses} */ /** Returns only the IPV4 addresses in {@code addresses} */
static InetAddress[] filterIPV4(InetAddress addresses[]) { static InetAddress[] filterIPV4(InetAddress addresses[]) {
List<InetAddress> list = new ArrayList<>(); List<InetAddress> list = new ArrayList<>();

View File

@ -50,6 +50,7 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -105,9 +106,9 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
protected final String port; protected final String port;
protected final String bindHost; protected final String bindHosts[];
protected final String publishHost; protected final String publishHosts[];
protected final boolean detailedErrorsEnabled; protected final boolean detailedErrorsEnabled;
@ -157,8 +158,8 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
this.workerCount = settings.getAsInt("http.netty.worker_count", EsExecutors.boundedNumberOfProcessors(settings) * 2); this.workerCount = settings.getAsInt("http.netty.worker_count", EsExecutors.boundedNumberOfProcessors(settings) * 2);
this.blockingServer = settings.getAsBoolean("http.netty.http.blocking_server", settings.getAsBoolean(TCP_BLOCKING_SERVER, settings.getAsBoolean(TCP_BLOCKING, false))); this.blockingServer = settings.getAsBoolean("http.netty.http.blocking_server", settings.getAsBoolean(TCP_BLOCKING_SERVER, settings.getAsBoolean(TCP_BLOCKING, false)));
this.port = settings.get("http.netty.port", settings.get("http.port", "9200-9300")); this.port = settings.get("http.netty.port", settings.get("http.port", "9200-9300"));
this.bindHost = settings.get("http.netty.bind_host", settings.get("http.bind_host", settings.get("http.host"))); this.bindHosts = settings.getAsArray("http.netty.bind_host", settings.getAsArray("http.bind_host", settings.getAsArray("http.host", null)));
this.publishHost = settings.get("http.netty.publish_host", settings.get("http.publish_host", settings.get("http.host"))); this.publishHosts = settings.getAsArray("http.netty.publish_host", settings.getAsArray("http.publish_host", settings.getAsArray("http.host", null)));
this.publishPort = settings.getAsInt("http.netty.publish_port", settings.getAsInt("http.publish_port", 0)); this.publishPort = settings.getAsInt("http.netty.publish_port", settings.getAsInt("http.publish_port", 0));
this.tcpNoDelay = settings.get("http.netty.tcp_no_delay", settings.get(TCP_NO_DELAY, "true")); this.tcpNoDelay = settings.get("http.netty.tcp_no_delay", settings.get(TCP_NO_DELAY, "true"));
this.tcpKeepAlive = settings.get("http.netty.tcp_keep_alive", settings.get(TCP_KEEP_ALIVE, "true")); this.tcpKeepAlive = settings.get("http.netty.tcp_keep_alive", settings.get(TCP_KEEP_ALIVE, "true"));
@ -246,9 +247,9 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
// Bind and start to accept incoming connections. // Bind and start to accept incoming connections.
InetAddress hostAddresses[]; InetAddress hostAddresses[];
try { try {
hostAddresses = networkService.resolveBindHostAddress(bindHost); hostAddresses = networkService.resolveBindHostAddresses(bindHosts);
} catch (IOException e) { } catch (IOException e) {
throw new BindHttpException("Failed to resolve host [" + bindHost + "]", e); throw new BindHttpException("Failed to resolve host [" + Arrays.toString(bindHosts) + "]", e);
} }
List<InetSocketTransportAddress> boundAddresses = new ArrayList<>(hostAddresses.length); List<InetSocketTransportAddress> boundAddresses = new ArrayList<>(hostAddresses.length);
@ -262,7 +263,7 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer
publishPort = boundAddress.getPort(); publishPort = boundAddress.getPort();
} }
try { try {
publishAddress = new InetSocketAddress(networkService.resolvePublishHostAddress(publishHost), publishPort); publishAddress = new InetSocketAddress(networkService.resolvePublishHostAddresses(publishHosts), publishPort);
} catch (Exception e) { } catch (Exception e) {
throw new BindTransportException("Failed to resolve publish address", e); throw new BindTransportException("Failed to resolve publish address", e);
} }

View File

@ -343,9 +343,9 @@ public class NettyTransport extends AbstractLifecycleComponent<Transport> implem
return unmodifiableMap(new HashMap<>(profileBoundAddresses)); return unmodifiableMap(new HashMap<>(profileBoundAddresses));
} }
private InetSocketAddress createPublishAddress(String publishHost, int publishPort) { private InetSocketAddress createPublishAddress(String publishHosts[], int publishPort) {
try { try {
return new InetSocketAddress(networkService.resolvePublishHostAddress(publishHost), publishPort); return new InetSocketAddress(networkService.resolvePublishHostAddresses(publishHosts), publishPort);
} catch (Exception e) { } catch (Exception e) {
throw new BindTransportException("Failed to resolve publish address", e); throw new BindTransportException("Failed to resolve publish address", e);
} }
@ -436,11 +436,11 @@ public class NettyTransport extends AbstractLifecycleComponent<Transport> implem
private void bindServerBootstrap(final String name, final Settings settings) { private void bindServerBootstrap(final String name, final Settings settings) {
// Bind and start to accept incoming connections. // Bind and start to accept incoming connections.
InetAddress hostAddresses[]; InetAddress hostAddresses[];
String bindHost = settings.get("bind_host"); String bindHosts[] = settings.getAsArray("bind_host", null);
try { try {
hostAddresses = networkService.resolveBindHostAddress(bindHost); hostAddresses = networkService.resolveBindHostAddresses(bindHosts);
} catch (IOException e) { } catch (IOException e) {
throw new BindTransportException("Failed to resolve host [" + bindHost + "]", e); throw new BindTransportException("Failed to resolve host " + Arrays.toString(bindHosts) + "", e);
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
String[] addresses = new String[hostAddresses.length]; String[] addresses = new String[hostAddresses.length];
@ -493,8 +493,8 @@ public class NettyTransport extends AbstractLifecycleComponent<Transport> implem
if (boundTransportAddress == null) { if (boundTransportAddress == null) {
// no address is bound, so lets create one with the publish address information from the settings or the bound address as a fallback // no address is bound, so lets create one with the publish address information from the settings or the bound address as a fallback
int publishPort = profileSettings.getAsInt("publish_port", boundAddress.getPort()); int publishPort = profileSettings.getAsInt("publish_port", boundAddress.getPort());
String publishHost = profileSettings.get("publish_host", boundAddress.getHostString()); String publishHosts[] = profileSettings.getAsArray("publish_host", new String[] { boundAddress.getHostString() });
InetSocketAddress publishAddress = createPublishAddress(publishHost, publishPort); InetSocketAddress publishAddress = createPublishAddress(publishHosts, publishPort);
profileBoundAddresses.put(name, new BoundTransportAddress(new TransportAddress[]{new InetSocketTransportAddress(boundAddress)}, new InetSocketTransportAddress(publishAddress))); profileBoundAddresses.put(name, new BoundTransportAddress(new TransportAddress[]{new InetSocketTransportAddress(boundAddress)}, new InetSocketTransportAddress(publishAddress)));
} else { } else {
// TODO: support real multihoming with publishing. Today we update the bound addresses so only the prioritized address is published // TODO: support real multihoming with publishing. Today we update the bound addresses so only the prioritized address is published
@ -511,8 +511,8 @@ public class NettyTransport extends AbstractLifecycleComponent<Transport> implem
// these calls are different from the profile ones due to the way the settings for a profile are created. If we want to merge the code for the default profile and // these calls are different from the profile ones due to the way the settings for a profile are created. If we want to merge the code for the default profile and
// other profiles together, we need to change how the profileSettings are built for the default profile... // other profiles together, we need to change how the profileSettings are built for the default profile...
int publishPort = settings.getAsInt("transport.netty.publish_port", settings.getAsInt("transport.publish_port", boundAddress.getPort())); int publishPort = settings.getAsInt("transport.netty.publish_port", settings.getAsInt("transport.publish_port", boundAddress.getPort()));
String publishHost = settings.get("transport.netty.publish_host", settings.get("transport.publish_host", settings.get("transport.host"))); String publishHosts[] = settings.getAsArray("transport.netty.publish_host", settings.getAsArray("transport.publish_host", settings.getAsArray("transport.host", null)));
InetSocketAddress publishAddress = createPublishAddress(publishHost, publishPort); InetSocketAddress publishAddress = createPublishAddress(publishHosts, publishPort);
this.boundAddress = new BoundTransportAddress(new TransportAddress[]{new InetSocketTransportAddress(boundAddress)}, new InetSocketTransportAddress(publishAddress)); this.boundAddress = new BoundTransportAddress(new TransportAddress[]{new InetSocketTransportAddress(boundAddress)}, new InetSocketTransportAddress(publishAddress));
} else { } else {
// the default profile is already bound to one address and has the publish address, copy the existing bound addresses as is and append the new address. // the default profile is already bound to one address and has the publish address, copy the existing bound addresses as is and append the new address.

View File

@ -36,7 +36,7 @@ public class NetworkServiceTests extends ESTestCase {
public void testBindMulticastV4() throws Exception { public void testBindMulticastV4() throws Exception {
NetworkService service = new NetworkService(Settings.EMPTY); NetworkService service = new NetworkService(Settings.EMPTY);
try { try {
service.resolveBindHostAddress("239.1.1.1"); service.resolveBindHostAddresses(new String[] { "239.1.1.1" });
fail("should have hit exception"); fail("should have hit exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("invalid: multicast")); assertTrue(e.getMessage().contains("invalid: multicast"));
@ -49,7 +49,7 @@ public class NetworkServiceTests extends ESTestCase {
public void testBindMulticastV6() throws Exception { public void testBindMulticastV6() throws Exception {
NetworkService service = new NetworkService(Settings.EMPTY); NetworkService service = new NetworkService(Settings.EMPTY);
try { try {
service.resolveBindHostAddress("FF08::108"); service.resolveBindHostAddresses(new String[] { "FF08::108" });
fail("should have hit exception"); fail("should have hit exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("invalid: multicast")); assertTrue(e.getMessage().contains("invalid: multicast"));
@ -62,7 +62,7 @@ public class NetworkServiceTests extends ESTestCase {
public void testPublishMulticastV4() throws Exception { public void testPublishMulticastV4() throws Exception {
NetworkService service = new NetworkService(Settings.EMPTY); NetworkService service = new NetworkService(Settings.EMPTY);
try { try {
service.resolvePublishHostAddress("239.1.1.1"); service.resolvePublishHostAddresses(new String[] { "239.1.1.1" });
fail("should have hit exception"); fail("should have hit exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("invalid: multicast")); assertTrue(e.getMessage().contains("invalid: multicast"));
@ -75,7 +75,7 @@ public class NetworkServiceTests extends ESTestCase {
public void testPublishMulticastV6() throws Exception { public void testPublishMulticastV6() throws Exception {
NetworkService service = new NetworkService(Settings.EMPTY); NetworkService service = new NetworkService(Settings.EMPTY);
try { try {
service.resolvePublishHostAddress("FF08::108"); service.resolvePublishHostAddresses(new String[] { "FF08::108" });
fail("should have hit exception"); fail("should have hit exception");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("invalid: multicast")); assertTrue(e.getMessage().contains("invalid: multicast"));
@ -87,7 +87,7 @@ public class NetworkServiceTests extends ESTestCase {
*/ */
public void testBindAnyLocalV4() throws Exception { public void testBindAnyLocalV4() throws Exception {
NetworkService service = new NetworkService(Settings.EMPTY); NetworkService service = new NetworkService(Settings.EMPTY);
assertEquals(InetAddress.getByName("0.0.0.0"), service.resolveBindHostAddress("0.0.0.0")[0]); assertEquals(InetAddress.getByName("0.0.0.0"), service.resolveBindHostAddresses(new String[] { "0.0.0.0" })[0]);
} }
/** /**
@ -95,36 +95,24 @@ public class NetworkServiceTests extends ESTestCase {
*/ */
public void testBindAnyLocalV6() throws Exception { public void testBindAnyLocalV6() throws Exception {
NetworkService service = new NetworkService(Settings.EMPTY); NetworkService service = new NetworkService(Settings.EMPTY);
assertEquals(InetAddress.getByName("::"), service.resolveBindHostAddress("::")[0]); assertEquals(InetAddress.getByName("::"), service.resolveBindHostAddresses(new String[] { "::" })[0]);
} }
/** /**
* ensure specifying wildcard ipv4 address selects reasonable publish address * ensure specifying wildcard ipv4 address selects reasonable publish address
*/ */
public void testPublishAnyLocalV4() throws Exception { public void testPublishAnyLocalV4() throws Exception {
InetAddress expected = null;
try {
expected = NetworkUtils.getFirstNonLoopbackAddresses()[0];
} catch (Exception e) {
assumeNoException("test requires up-and-running non-loopback address", e);
}
NetworkService service = new NetworkService(Settings.EMPTY); NetworkService service = new NetworkService(Settings.EMPTY);
assertEquals(expected, service.resolvePublishHostAddress("0.0.0.0")); InetAddress address = service.resolvePublishHostAddresses(new String[] { "0.0.0.0" });
assertFalse(address.isAnyLocalAddress());
} }
/** /**
* ensure specifying wildcard ipv6 address selects reasonable publish address * ensure specifying wildcard ipv6 address selects reasonable publish address
*/ */
public void testPublishAnyLocalV6() throws Exception { public void testPublishAnyLocalV6() throws Exception {
InetAddress expected = null;
try {
expected = NetworkUtils.getFirstNonLoopbackAddresses()[0];
} catch (Exception e) {
assumeNoException("test requires up-and-running non-loopback address", e);
}
NetworkService service = new NetworkService(Settings.EMPTY); NetworkService service = new NetworkService(Settings.EMPTY);
assertEquals(expected, service.resolvePublishHostAddress("::")); InetAddress address = service.resolvePublishHostAddresses(new String[] { "::" });
assertFalse(address.isAnyLocalAddress());
} }
} }

View File

@ -329,3 +329,9 @@ to index a document only if it doesn't already exist.
Two cache concurrency level settings `indices.requests.cache.concurrency_level` and Two cache concurrency level settings `indices.requests.cache.concurrency_level` and
`indices.fielddata.cache.concurrency_level` because they no longer apply to the cache implementation used for the `indices.fielddata.cache.concurrency_level` because they no longer apply to the cache implementation used for the
request cache and the field data cache. request cache and the field data cache.
=== Remove bind option of `non_loopback`
This setting would arbitrarily pick the first interface not marked as loopback. Instead, specify by address
scope (e.g. `_local_,_site_` for all loopback and private network addresses) or by explicit interface names,
hostnames, or addresses.

View File

@ -8,6 +8,9 @@ configuration, for example, the
network settings allows to set common settings that will be shared among network settings allows to set common settings that will be shared among
all network based modules (unless explicitly overridden in each module). all network based modules (unless explicitly overridden in each module).
Be careful with host configuration! Never expose an unprotected instance
to the public internet.
The `network.bind_host` setting allows to control the host different network The `network.bind_host` setting allows to control the host different network
components will bind on. By default, the bind host will be `_local_` components will bind on. By default, the bind host will be `_local_`
(loopback addresses such as `127.0.0.1`, `::1`). (loopback addresses such as `127.0.0.1`, `::1`).
@ -16,14 +19,13 @@ The `network.publish_host` setting allows to control the host the node will
publish itself within the cluster so other nodes will be able to connect to it. publish itself within the cluster so other nodes will be able to connect to it.
Currently an elasticsearch node may be bound to multiple addresses, but only Currently an elasticsearch node may be bound to multiple addresses, but only
publishes one. If not specified, this defaults to the "best" address from publishes one. If not specified, this defaults to the "best" address from
`network.bind_host`. By default, IPv4 addresses are preferred to IPv6, and `network.bind_host`, sorted by IPv4/IPv6 stack preference, then by reachability.
ordinary addresses are preferred to site-local or link-local addresses.
The `network.host` setting is a simple setting to automatically set both The `network.host` setting is a simple setting to automatically set both
`network.bind_host` and `network.publish_host` to the same host value. `network.bind_host` and `network.publish_host` to the same host value.
Both settings allows to be configured with either explicit host address Both settings allows to be configured with either explicit host address(es)
or host name. The settings also accept logical setting values explained or host name(s). The settings also accept logical setting value(s) explained
in the following table: in the following table:
[cols="<,<",options="header",] [cols="<,<",options="header",]
@ -31,15 +33,21 @@ in the following table:
|Logical Host Setting Value |Description |Logical Host Setting Value |Description
|`_local_` |Will be resolved to loopback addresses |`_local_` |Will be resolved to loopback addresses
|`_local:ipv4_` |Will be resolved to loopback IPv4 addresses |`_local:ipv4_` |Will be resolved to loopback IPv4 addresses (e.g. 127.0.0.1)
|`_local:ipv6_` |Will be resolved to loopback IPv6 addresses |`_local:ipv6_` |Will be resolved to loopback IPv6 addresses (e.g. ::1)
|`_non_loopback_` |Addresses of the first non loopback interface |`_site_` |Will be resolved to site-local addresses ("private network")
|`_non_loopback:ipv4_` |IPv4 addresses of the first non loopback interface |`_site:ipv4_` |Will be resolved to site-local IPv4 addresses (e.g. 192.168.0.1)
|`_non_loopback:ipv6_` |IPv6 addresses of the first non loopback interface |`_site:ipv6_` |Will be resolved to site-local IPv6 addresses (e.g. fec0::1)
|`_global_` |Will be resolved to globally-scoped addresses ("publicly reachable")
|`_global:ipv4_` |Will be resolved to globally-scoped IPv4 addresses (e.g. 8.8.8.8)
|`_global:ipv6_` |Will be resolved to globally-scoped IPv6 addresses (e.g. 2001:4860:4860::8888)
|`_[networkInterface]_` |Resolves to the addresses of the provided |`_[networkInterface]_` |Resolves to the addresses of the provided
network interface. For example `_en0_`. network interface. For example `_en0_`.

View File

@ -177,7 +177,7 @@ public class AzureUnicastHostsProvider extends AbstractComponent implements Unic
InetAddress ipAddress = null; InetAddress ipAddress = null;
try { try {
ipAddress = networkService.resolvePublishHostAddress(null); ipAddress = networkService.resolvePublishHostAddresses(null);
logger.trace("ip of current node: [{}]", ipAddress); logger.trace("ip of current node: [{}]", ipAddress);
} catch (IOException e) { } catch (IOException e) {
// We can't find the publish host address... Hmmm. Too bad :-( // We can't find the publish host address... Hmmm. Too bad :-(

View File

@ -46,7 +46,7 @@ public class Ec2NetworkTests extends ESTestCase {
networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings)); networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
try { try {
networkService.resolveBindHostAddress(null); networkService.resolveBindHostAddresses(null);
} catch (IOException e) { } catch (IOException e) {
assertThat(e.getMessage(), containsString("local-ipv4")); assertThat(e.getMessage(), containsString("local-ipv4"));
} }
@ -64,7 +64,7 @@ public class Ec2NetworkTests extends ESTestCase {
networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings)); networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
try { try {
networkService.resolveBindHostAddress(null); networkService.resolveBindHostAddresses(null);
} catch (IOException e) { } catch (IOException e) {
assertThat(e.getMessage(), containsString("public-ipv4")); assertThat(e.getMessage(), containsString("public-ipv4"));
} }
@ -82,7 +82,7 @@ public class Ec2NetworkTests extends ESTestCase {
networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings)); networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
try { try {
networkService.resolveBindHostAddress(null); networkService.resolveBindHostAddresses(null);
} catch (IOException e) { } catch (IOException e) {
assertThat(e.getMessage(), containsString("local-ipv4")); assertThat(e.getMessage(), containsString("local-ipv4"));
} }
@ -100,7 +100,7 @@ public class Ec2NetworkTests extends ESTestCase {
networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings)); networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
try { try {
networkService.resolveBindHostAddress(null); networkService.resolveBindHostAddresses(null);
} catch (IOException e) { } catch (IOException e) {
assertThat(e.getMessage(), containsString("local-ipv4")); assertThat(e.getMessage(), containsString("local-ipv4"));
} }
@ -118,7 +118,7 @@ public class Ec2NetworkTests extends ESTestCase {
networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings)); networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
try { try {
networkService.resolveBindHostAddress(null); networkService.resolveBindHostAddresses(null);
} catch (IOException e) { } catch (IOException e) {
assertThat(e.getMessage(), containsString("local-hostname")); assertThat(e.getMessage(), containsString("local-hostname"));
} }
@ -136,7 +136,7 @@ public class Ec2NetworkTests extends ESTestCase {
networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings)); networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
try { try {
networkService.resolveBindHostAddress(null); networkService.resolveBindHostAddresses(null);
} catch (IOException e) { } catch (IOException e) {
assertThat(e.getMessage(), containsString("public-ipv4")); assertThat(e.getMessage(), containsString("public-ipv4"));
} }
@ -154,7 +154,7 @@ public class Ec2NetworkTests extends ESTestCase {
networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings)); networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
try { try {
networkService.resolveBindHostAddress(null); networkService.resolveBindHostAddresses(null);
} catch (IOException e) { } catch (IOException e) {
assertThat(e.getMessage(), containsString("public-hostname")); assertThat(e.getMessage(), containsString("public-hostname"));
} }
@ -171,7 +171,7 @@ public class Ec2NetworkTests extends ESTestCase {
NetworkService networkService = new NetworkService(nodeSettings); NetworkService networkService = new NetworkService(nodeSettings);
networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings)); networkService.addCustomNameResolver(new Ec2NameResolver(nodeSettings));
InetAddress[] addresses = networkService.resolveBindHostAddress(null); InetAddress[] addresses = networkService.resolveBindHostAddresses(null);
assertThat(addresses, arrayContaining(networkService.resolveBindHostAddress("_local_"))); assertThat(addresses, arrayContaining(networkService.resolveBindHostAddresses(new String[] { "_local_" })));
} }
} }

View File

@ -110,7 +110,7 @@ public class GceUnicastHostsProvider extends AbstractComponent implements Unicas
cachedDiscoNodes = new ArrayList<>(); cachedDiscoNodes = new ArrayList<>();
String ipAddress = null; String ipAddress = null;
try { try {
InetAddress inetAddress = networkService.resolvePublishHostAddress(null); InetAddress inetAddress = networkService.resolvePublishHostAddresses(null);
if (inetAddress != null) { if (inetAddress != null) {
ipAddress = NetworkAddress.formatAddress(inetAddress); ipAddress = NetworkAddress.formatAddress(inetAddress);
} }

View File

@ -78,8 +78,8 @@ public class GceNetworkTests extends ESTestCase {
* Test that we don't have any regression with network host core settings such as * Test that we don't have any regression with network host core settings such as
* network.host: _local_ * network.host: _local_
*/ */
public void testNetworkHostCoreLocal() throws IOException { public void networkHostCoreLocal() throws IOException {
resolveGce("_local_", new NetworkService(Settings.EMPTY).resolveBindHostAddress(NetworkService.DEFAULT_NETWORK_HOST)); resolveGce("_local_", new NetworkService(Settings.EMPTY).resolveBindHostAddresses(new String[] { NetworkService.DEFAULT_NETWORK_HOST }));
} }
/** /**
@ -107,7 +107,7 @@ public class GceNetworkTests extends ESTestCase {
GceComputeServiceMock mock = new GceComputeServiceMock(nodeSettings, networkService); GceComputeServiceMock mock = new GceComputeServiceMock(nodeSettings, networkService);
networkService.addCustomNameResolver(new GceNameResolver(nodeSettings, mock)); networkService.addCustomNameResolver(new GceNameResolver(nodeSettings, mock));
try { try {
InetAddress[] addresses = networkService.resolveBindHostAddress(null); InetAddress[] addresses = networkService.resolveBindHostAddresses(null);
if (expected == null) { if (expected == null) {
fail("We should get a IllegalArgumentException when setting network.host: _gce:doesnotexist_"); fail("We should get a IllegalArgumentException when setting network.host: _gce:doesnotexist_");
} }

View File

@ -19,6 +19,7 @@
<tests.jvms>1</tests.jvms> <tests.jvms>1</tests.jvms>
<tests.rest.suite>discovery_multicast</tests.rest.suite> <tests.rest.suite>discovery_multicast</tests.rest.suite>
<tests.rest.load_packaged>false</tests.rest.load_packaged> <tests.rest.load_packaged>false</tests.rest.load_packaged>
<xlint.options>-Xlint:-deprecation</xlint.options>
</properties> </properties>
<build> <build>

View File

@ -35,6 +35,7 @@ import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.network.NetworkUtils;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.AbstractRunnable;
@ -55,7 +56,10 @@ import org.elasticsearch.transport.TransportResponse;
import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportService;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Arrays;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -138,11 +142,14 @@ public class MulticastZenPing extends AbstractLifecycleComponent<ZenPing> implem
boolean shared = settings.getAsBoolean("discovery.zen.ping.multicast.shared", Constants.MAC_OS_X); boolean shared = settings.getAsBoolean("discovery.zen.ping.multicast.shared", Constants.MAC_OS_X);
// OSX does not correctly send multicasts FROM the right interface // OSX does not correctly send multicasts FROM the right interface
boolean deferToInterface = settings.getAsBoolean("discovery.zen.ping.multicast.defer_group_to_set_interface", Constants.MAC_OS_X); boolean deferToInterface = settings.getAsBoolean("discovery.zen.ping.multicast.defer_group_to_set_interface", Constants.MAC_OS_X);
multicastChannel = MulticastChannel.getChannel(nodeName(), shared,
new MulticastChannel.Config(port, group, bufferSize, ttl,
// don't use publish address, the use case for that is e.g. a firewall or proxy and // don't use publish address, the use case for that is e.g. a firewall or proxy and
// may not even be bound to an interface on this machine! use the first bound address. // may not even be bound to an interface on this machine! use the first bound address.
networkService.resolveBindHostAddress(address)[0], List<InetAddress> addresses = Arrays.asList(networkService.resolveBindHostAddresses(address == null ? null : new String[] { address }));
NetworkUtils.sortAddresses(addresses);
multicastChannel = MulticastChannel.getChannel(nodeName(), shared,
new MulticastChannel.Config(port, group, bufferSize, ttl,
addresses.get(0),
deferToInterface), deferToInterface),
new Receiver()); new Receiver());
} catch (Throwable t) { } catch (Throwable t) {