Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow binding to multiple addresses. #13954

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -90,13 +93,21 @@ public void addCustomNameResolver(CustomNameResolver 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
if (bindHost == null) {
bindHost = settings.get(GLOBAL_NETWORK_BINDHOST_SETTING, settings.get(GLOBAL_NETWORK_HOST_SETTING));
if (bindHosts == null) {
bindHosts = settings.getAsArray(GLOBAL_NETWORK_BINDHOST_SETTING, settings.getAsArray(GLOBAL_NETWORK_HOST_SETTING, null));
}
// next check any registered custom resolvers
if (bindHost == null) {
if (bindHosts == null) {
for (CustomNameResolver customNameResolver : customNameResolvers) {
InetAddress addresses[] = customNameResolver.resolveDefault();
if (addresses != null) {
Expand All @@ -105,31 +116,44 @@ public InetAddress[] resolveBindHostAddress(String bindHost) throws IOException
}
}
// finally, fill with our default
if (bindHost == null) {
bindHost = DEFAULT_NETWORK_HOST;
if (bindHosts == null) {
bindHosts = new String[] { DEFAULT_NETWORK_HOST };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it become DEFAULT_NETWORK_HOSTS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our default is a single host specification: _local_. One point of this change is that you can use multiple: e.g. _local_,12.1.4.2,foo.bar.com

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah - I suppose that is fine reasoning.

}
InetAddress addresses[] = resolveInetAddress(bindHost);
InetAddress addresses[] = resolveInetAddresses(bindHosts);

// try to deal with some (mis)configuration
if (addresses != null) {
for (InetAddress address : addresses) {
// check if its multicast: flat out mistake
if (address.isMulticastAddress()) {
throw new IllegalArgumentException("bind address: {" + NetworkAddress.format(address) + "} is invalid: multicast address");
}
for (InetAddress address : addresses) {
// check if its multicast: flat out mistake
if (address.isMulticastAddress()) {
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;
}

/**
* 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[]
public InetAddress resolvePublishHostAddress(String publishHost) throws IOException {
public InetAddress resolvePublishHostAddresses(String publishHosts[]) throws IOException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It still returns a single address so its probably wrong to rename it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is only for now. I plan to solve the "multiple publish addresses" in a followup... but the api must be correct!

This method takes as input, an array of addresses. and lots of places were passing null to it, so its important for type safety to rename!

// first check settings
if (publishHost == null) {
publishHost = settings.get(GLOBAL_NETWORK_PUBLISHHOST_SETTING, settings.get(GLOBAL_NETWORK_HOST_SETTING));
if (publishHosts == null) {
publishHosts = settings.getAsArray(GLOBAL_NETWORK_PUBLISHHOST_SETTING, settings.getAsArray(GLOBAL_NETWORK_HOST_SETTING, null));
}
// next check any registered custom resolvers
if (publishHost == null) {
if (publishHosts == null) {
for (CustomNameResolver customNameResolver : customNameResolvers) {
InetAddress addresses[] = customNameResolver.resolveDefault();
if (addresses != null) {
Expand All @@ -138,30 +162,59 @@ public InetAddress resolvePublishHostAddress(String publishHost) throws IOExcept
}
}
// finally, fill with our default
if (publishHost == null) {
publishHost = DEFAULT_NETWORK_HOST;
if (publishHosts == null) {
publishHosts = new String[] { DEFAULT_NETWORK_HOST };
}
InetAddress addresses[] = resolveInetAddresses(publishHosts);
// TODO: allow publishing multiple addresses
InetAddress address = resolveInetAddress(publishHost)[0];
// for now... the hack begins

// try to deal with some (mis)configuration
if (address != null) {
// 1. single wildcard address, probably set by network.host: expand to all interface addresses.
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
if (address.isMulticastAddress()) {
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()) {
InetAddress old = address;
address = NetworkUtils.getFirstNonLoopbackAddresses()[0];
logger.warn("publish address: {{}} is a wildcard address, falling back to first non-loopback: {{}}",
NetworkAddress.format(old), NetworkAddress.format(address));
throw new IllegalArgumentException("publish address: {" + NetworkAddress.format(address) + "} is wildcard, but multiple addresses specified: this makes no sense");
}
}
return address;

// 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()]);
}

private InetAddress[] resolveInetAddress(String host) throws IOException {
/** resolves a single host specification */
private InetAddress[] resolveInternal(String host) throws IOException {
if ((host.startsWith("#") && host.endsWith("#")) || (host.startsWith("_") && host.endsWith("_"))) {
host = host.substring(1, host.length() - 1);
// allow custom resolvers to have special names
Expand All @@ -178,12 +231,18 @@ private InetAddress[] resolveInetAddress(String host) throws IOException {
return NetworkUtils.filterIPV4(NetworkUtils.getLoopbackAddresses());
case "local:ipv6":
return NetworkUtils.filterIPV6(NetworkUtils.getLoopbackAddresses());
case "non_loopback":
return NetworkUtils.getFirstNonLoopbackAddresses();
case "non_loopback:ipv4":
return NetworkUtils.filterIPV4(NetworkUtils.getFirstNonLoopbackAddresses());
case "non_loopback:ipv6":
return NetworkUtils.filterIPV6(NetworkUtils.getFirstNonLoopbackAddresses());
case "site":
return NetworkUtils.getSiteLocalAddresses();
case "site:ipv4":
return NetworkUtils.filterIPV4(NetworkUtils.getSiteLocalAddresses());
case "site:ipv6":
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:
/* an interface specification */
if (host.endsWith(":ipv4")) {
Expand All @@ -197,6 +256,6 @@ private InetAddress[] resolveInetAddress(String host) throws IOException {
}
}
}
return NetworkUtils.getAllByName(host);
return InetAddress.getAllByName(host);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;

/**
Expand Down Expand Up @@ -109,7 +107,8 @@ static int sortKey(InetAddress address, boolean prefer_v6) {
* @deprecated remove this when multihoming is really correct
*/
@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>() {
@Override
public int compare(InetAddress left, InetAddress right) {
Expand Down Expand Up @@ -150,34 +149,79 @@ public static boolean defaultReuseAddress() {
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 {
List<InetAddress> list = new ArrayList<>();
for (NetworkInterface intf : getInterfaces()) {
if (intf.isLoopback() && intf.isUp()) {
list.addAll(Collections.list(intf.getInetAddresses()));
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())) {
if (intf.isLoopback() || address.isLoopbackAddress()) {
list.add(address);
}
}
}
}
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()]);
}

/** Returns addresses for the first non-loopback interface that is up. */
static InetAddress[] getFirstNonLoopbackAddresses() throws SocketException {
/** Returns all site-local scope (private) addresses for interfaces that are up. */
static InetAddress[] getSiteLocalAddresses() throws SocketException {
List<InetAddress> list = new ArrayList<>();
for (NetworkInterface intf : getInterfaces()) {
if (intf.isLoopback() == false && intf.isUp()) {
list.addAll(Collections.list(intf.getInetAddresses()));
break;
if (intf.isUp()) {
for (InetAddress address : Collections.list(intf.getInetAddresses())) {
if (address.isSiteLocalAddress()) {
list.add(address);
}
}
}
}
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()]);
}

Expand All @@ -194,20 +238,9 @@ static InetAddress[] getAddressesForInterface(String name) throws SocketExceptio
if (list.isEmpty()) {
throw new IllegalArgumentException("Interface '" + name + "' has no internet addresses");
}
sortAddresses(list);
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} */
static InetAddress[] filterIPV4(InetAddress addresses[]) {
List<InetAddress> list = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
Expand Down Expand Up @@ -105,9 +106,9 @@ public class NettyHttpServerTransport extends AbstractLifecycleComponent<HttpSer

protected final String port;

protected final String bindHost;
protected final String bindHosts[];

protected final String publishHost;
protected final String publishHosts[];

protected final boolean detailedErrorsEnabled;

Expand Down Expand Up @@ -157,8 +158,8 @@ public NettyHttpServerTransport(Settings settings, NetworkService networkService
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.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.publishHost = settings.get("http.netty.publish_host", settings.get("http.publish_host", settings.get("http.host")));
this.bindHosts = settings.getAsArray("http.netty.bind_host", settings.getAsArray("http.bind_host", settings.getAsArray("http.host", null)));
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.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"));
Expand Down Expand Up @@ -246,9 +247,9 @@ protected void doStart() {
// Bind and start to accept incoming connections.
InetAddress hostAddresses[];
try {
hostAddresses = networkService.resolveBindHostAddress(bindHost);
hostAddresses = networkService.resolveBindHostAddresses(bindHosts);
} 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);
Expand All @@ -262,7 +263,7 @@ protected void doStart() {
publishPort = boundAddress.getPort();
}
try {
publishAddress = new InetSocketAddress(networkService.resolvePublishHostAddress(publishHost), publishPort);
publishAddress = new InetSocketAddress(networkService.resolvePublishHostAddresses(publishHosts), publishPort);
} catch (Exception e) {
throw new BindTransportException("Failed to resolve publish address", e);
}
Expand Down
Loading