/*
* Copyright (c) 2008-2012, Hazel Bilisim Ltd. All Rights Reserved.
*
* Licensed 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 com.hazelcast.impl;
import com.hazelcast.config.Config;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;
import com.hazelcast.nio.Address;
import com.hazelcast.util.AddressUtil;
import java.net.*;
import java.nio.channels.ServerSocketChannel;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
public class AddressPicker {
final Node node;
final ServerSocketChannel serverSocketChannel;
final ILogger logger;
public AddressPicker(Node node, ServerSocketChannel serverSocketChannel) {
this.node = node;
this.logger = Logger.getLogger(AddressPicker.class.getName());
this.serverSocketChannel = serverSocketChannel;
}
public static boolean matchAddress(final String address, final Collection<String> interfaces) {
return AddressUtil.matchAnyInterface(address, interfaces);
}
public Address pickAddress() throws Exception {
InetAddress currentInetAddress = null;
try {
final Config config = node.getConfig();
final String localAddress = System.getProperty("hazelcast.local.localAddress");
if (localAddress != null) {
currentInetAddress = InetAddress.getByName(localAddress.trim());
}
if (currentInetAddress == null) {
final Set<String> interfaces = new HashSet<String>();
if (config.getNetworkConfig().getJoin().getTcpIpConfig().isEnabled()) {
final Collection<Address> possibleAddresses = new TcpIpJoiner(node)
.getPossibleAddresses(config, null, logger);
for (Address possibleAddress : possibleAddresses) {
interfaces.add(possibleAddress.getHost());
}
}
if (config.getNetworkConfig().getInterfaces().isEnabled()) {
interfaces.addAll(config.getNetworkConfig().getInterfaces().getInterfaces());
}
if (interfaces.contains("127.0.0.1") || interfaces.contains("localhost")) {
currentInetAddress = InetAddress.getByName("127.0.0.1");
} else {
if (interfaces.size() > 0) {
currentInetAddress = pickInetAddress(interfaces);
}
if (currentInetAddress == null) {
if (config.getNetworkConfig().getInterfaces().isEnabled()) {
String msg = "Hazelcast CANNOT start on this node. No matching network interface found. ";
msg += "\nInterface matching must be either disabled or updated in the hazelcast.xml config file.";
logger.log(Level.SEVERE, msg);
throw new RuntimeException(msg);
} else {
currentInetAddress = pickInetAddress(null);
}
}
}
}
if (currentInetAddress != null && currentInetAddress instanceof Inet6Address) {
// check if scope id correctly set
if (currentInetAddress.isLinkLocalAddress() || currentInetAddress.isSiteLocalAddress()) {
NetworkInterface ni = NetworkInterface.getByInetAddress(currentInetAddress);
currentInetAddress = Inet6Address.getByAddress(null, currentInetAddress.getAddress(), ni);
}
}
if (currentInetAddress == null) {
currentInetAddress = InetAddress.getByName("127.0.0.1");
}
final InetAddress inetAddress = currentInetAddress;
final boolean reuseAddress = config.isReuseAddress();
ServerSocket serverSocket = serverSocketChannel.socket();
/**
* why setReuseAddress(true)?
* when the member is shutdown,
* the serversocket port will be in TIME_WAIT state for the next
* 2 minutes or so. If you start the member right after shutting it down
* you may not able able to bind to the same port because it is in TIME_WAIT
* state. if you set reuseaddress=true then TIME_WAIT will be ignored and
* you will be able to bind to the same port again.
*
* this will cause problem on windows
* see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6421091
* http://www.hsc.fr/ressources/articles/win_net_srv/multiple_bindings.html
*
* By default if the OS is Windows then reuseaddress will be false.
*/
logger.log(Level.FINEST, "inet reuseAddress:" + reuseAddress);
serverSocket.setReuseAddress(reuseAddress);
serverSocket.setSoTimeout(1000);
InetSocketAddress isa;
int port = config.getPort();
for (int i = 0; i < 100; i++) {
try {
boolean bindAny = node.getGroupProperties().SOCKET_BIND_ANY.getBoolean();
if (bindAny) {
isa = new InetSocketAddress(port);
} else {
isa = new InetSocketAddress(inetAddress, port);
}
logger.log(Level.FINEST, "inet socket address:" + isa);
serverSocket.bind(isa, 100);
break;
} catch (final Exception e) {
if (config.isPortAutoIncrement()) {
serverSocket = serverSocketChannel.socket();
serverSocket.setReuseAddress(reuseAddress);
port++;
} else {
String msg = "Port [" + port + "] is already in use and auto-increment is " +
"disabled. Hazelcast cannot start.";
logger.log(Level.SEVERE, msg, e);
throw e;
}
}
}
serverSocketChannel.configureBlocking(false);
return new Address(inetAddress, port);
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
throw e;
}
}
private InetAddress pickInetAddress(final Set<String> interfaces) throws SocketException {
final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
while (networkInterfaces.hasMoreElements()) {
final NetworkInterface ni = networkInterfaces.nextElement();
final Enumeration<InetAddress> e = ni.getInetAddresses();
while (e.hasMoreElements()) {
final InetAddress inetAddress = e.nextElement();
if (interfaces != null && !interfaces.isEmpty()) {
final String address = inetAddress.getHostAddress();
if (matchAddress(address, interfaces)) {
return inetAddress;
}
} else if (!inetAddress.isLoopbackAddress()) {
return inetAddress;
}
}
}
return null;
}
}