/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.common.network;
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;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.unit.TimeValue;
/**
*
*/
public class NetworkService extends AbstractComponent {
/** By default, we bind to loopback interfaces */
public static final String DEFAULT_NETWORK_HOST = "_local_";
private static final String GLOBAL_NETWORK_HOST_SETTING = "network.host";
private static final String GLOBAL_NETWORK_BINDHOST_SETTING = "network.bind_host";
private static final String GLOBAL_NETWORK_PUBLISHHOST_SETTING = "network.publish_host";
public static final class TcpSettings {
public static final String TCP_NO_DELAY = "network.tcp.no_delay";
public static final String TCP_KEEP_ALIVE = "network.tcp.keep_alive";
public static final String TCP_REUSE_ADDRESS = "network.tcp.reuse_address";
public static final String TCP_SEND_BUFFER_SIZE = "network.tcp.send_buffer_size";
public static final String TCP_RECEIVE_BUFFER_SIZE = "network.tcp.receive_buffer_size";
public static final String TCP_BLOCKING = "network.tcp.blocking";
public static final String TCP_BLOCKING_SERVER = "network.tcp.blocking_server";
public static final String TCP_BLOCKING_CLIENT = "network.tcp.blocking_client";
public static final String TCP_CONNECT_TIMEOUT = "network.tcp.connect_timeout";
public static final ByteSizeValue TCP_DEFAULT_SEND_BUFFER_SIZE = null;
public static final ByteSizeValue TCP_DEFAULT_RECEIVE_BUFFER_SIZE = null;
public static final TimeValue TCP_DEFAULT_CONNECT_TIMEOUT = new TimeValue(30, TimeUnit.SECONDS);
}
/**
* A custom name resolver can support custom lookup keys (my_net_key:ipv4) and also change
* the default inet address used in case no settings is provided.
*/
public static interface CustomNameResolver {
/**
* Resolves the default value if possible. If not, return <tt>null</tt>.
*/
InetAddress[] resolveDefault();
/**
* Resolves a custom value handling, return <tt>null</tt> if can't handle it.
*/
InetAddress[] resolveIfPossible(String value) throws IOException;
}
private final List<CustomNameResolver> customNameResolvers = new CopyOnWriteArrayList<>();
@Inject
public NetworkService(Settings settings) {
super(settings);
IfConfig.logIfNecessary();
}
/**
* Add a custom name resolver.
*/
public void addCustomNameResolver(CustomNameResolver customNameResolver) {
customNameResolvers.add(customNameResolver);
}
/**
* 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 (bindHosts == null) {
bindHosts = settings.getAsArray(GLOBAL_NETWORK_BINDHOST_SETTING, settings.getAsArray(GLOBAL_NETWORK_HOST_SETTING, null));
}
// next check any registered custom resolvers
if (bindHosts == null) {
for (CustomNameResolver customNameResolver : customNameResolvers) {
InetAddress addresses[] = customNameResolver.resolveDefault();
if (addresses != null) {
return addresses;
}
}
}
// finally, fill with our default
if (bindHosts == null) {
bindHosts = new String[] { DEFAULT_NETWORK_HOST };
}
InetAddress addresses[] = resolveInetAddresses(bindHosts);
// try to deal with some (mis)configuration
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>
* @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 resolvePublishHostAddresses(String publishHosts[]) throws IOException {
// first check settings
if (publishHosts == null) {
publishHosts = settings.getAsArray(GLOBAL_NETWORK_PUBLISHHOST_SETTING, settings.getAsArray(GLOBAL_NETWORK_HOST_SETTING, null));
}
// next check any registered custom resolvers
if (publishHosts == null) {
for (CustomNameResolver customNameResolver : customNameResolvers) {
InetAddress addresses[] = customNameResolver.resolveDefault();
if (addresses != null) {
return addresses[0];
}
}
}
// finally, fill with our default
if (publishHosts == null) {
publishHosts = new String[] { DEFAULT_NETWORK_HOST };
}
InetAddress addresses[] = resolveInetAddresses(publishHosts);
// TODO: allow publishing multiple addresses
// for now... the hack begins
// 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");
}
// 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()) {
throw new IllegalArgumentException("publish address: {" + NetworkAddress.format(address) + "} is wildcard, but multiple addresses specified: this makes no sense");
}
}
// 3. if we end out with multiple publish addresses, select by preference.
// don't warn the user, or they will get confused by bind_host vs publish_host etc.
if (addresses.length > 1) {
List<InetAddress> sorted = new ArrayList<>(Arrays.asList(addresses));
NetworkUtils.sortAddresses(sorted);
addresses = new InetAddress[] { sorted.get(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("_"))) {
host = host.substring(1, host.length() - 1);
// allow custom resolvers to have special names
for (CustomNameResolver customNameResolver : customNameResolvers) {
InetAddress addresses[] = customNameResolver.resolveIfPossible(host);
if (addresses != null) {
return addresses;
}
}
switch (host) {
case "local":
return NetworkUtils.getLoopbackAddresses();
case "local:ipv4":
return NetworkUtils.filterIPV4(NetworkUtils.getLoopbackAddresses());
case "local:ipv6":
return NetworkUtils.filterIPV6(NetworkUtils.getLoopbackAddresses());
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());
case "non_loopback":
logger.warn("_non_loopback_ is deprecated as it picks an arbitrary interface. specify explicit scope(s), interface(s), address(es), or hostname(s) instead");
return NetworkUtils.getFirstNonLoopbackAddresses();
case "non_loopback:ipv4":
logger.warn("_non_loopback_ is deprecated as it picks an arbitrary interface. specify explicit scope(s), interface(s), address(es), or hostname(s) instead");
return NetworkUtils.filterIPV4(NetworkUtils.getFirstNonLoopbackAddresses());
case "non_loopback:ipv6":
logger.warn("_non_loopback_ is deprecated as it picks an arbitrary interface. specify explicit scope(s), interface(s), address(es), or hostname(s) instead");
return NetworkUtils.filterIPV6(NetworkUtils.getFirstNonLoopbackAddresses());
default:
/* an interface specification */
if (host.endsWith(":ipv4")) {
host = host.substring(0, host.length() - 5);
return NetworkUtils.filterIPV4(NetworkUtils.getAddressesForInterface(host));
} else if (host.endsWith(":ipv6")) {
host = host.substring(0, host.length() - 5);
return NetworkUtils.filterIPV6(NetworkUtils.getAddressesForInterface(host));
} else {
return NetworkUtils.getAddressesForInterface(host);
}
}
}
// if host is a string representation of InetAddress, nothing to resolve...
try {
return new InetAddress[] { com.google.common.net.InetAddresses.forString(host) };
} catch (IllegalArgumentException e) {
}
return InetAddress.getAllByName(host);
}
}