/*
* Copyright 2004 - 2008 Christian Sprajc. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* PowerFolder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.util.net;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import de.dal33t.powerfolder.ConfigurationEntry;
import de.dal33t.powerfolder.Controller;
import de.dal33t.powerfolder.util.Reject;
import de.dal33t.powerfolder.util.StringUtils;
/**
* Utility class for all low level networking stuff.
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc</a>
* @version $Revision: 1.4 $
*/
public class NetworkUtil {
private final static Logger LOG = Logger.getLogger(NetworkUtil.class
.getName());
/**
* Network interfaces cache.
*/
private static final long CACHE_TIMEOUT = 30 * 1000;
private static long LAST_CHACHE_UPDATE = 0;
private static Map<InterfaceAddress, NetworkInterface> LOCAL_NETWORK_ADDRESSES_CACHE;
private static InetAddress NULL_IP;
static {
try {
NULL_IP = InetAddress.getByAddress("0.0.0.0",
new byte[]{0, 0, 0, 0});
} catch (UnknownHostException e) {
NULL_IP = null;
e.printStackTrace();
}
}
private NetworkUtil() {
// No instance allowed
}
/**
* Sets a socket up for use with PowerFolder
*
* @param socket
* the Socket to setup
* @throws SocketException
*/
public static void setupSocket(Socket socket, Controller controller)
throws SocketException
{
Reject.ifNull(socket, "Socket is null");
Reject.ifNull(controller, "Controller is null");
boolean onLan = isOnLanOrLoopback(socket.getInetAddress());
// socket.setSoTimeout(Constants.SOCKET_CONNECT_TIMEOUT);
// socket.setSoLinger(true, 4000);
// socket.setKeepAlive(true);
if (onLan) {
int bufferSize = ConfigurationEntry.NET_SOCKET_LAN_BUFFER_SIZE
.getValueInt(controller);
if (bufferSize > 0) {
socket.setReceiveBufferSize(bufferSize);
socket.setSendBufferSize(bufferSize);
}
} else {
int bufferSize = ConfigurationEntry.NET_SOCKET_INTERNET_BUFFER_SIZE
.getValueInt(controller);
if (bufferSize > 0) {
socket.setReceiveBufferSize(bufferSize);
socket.setSendBufferSize(bufferSize);
}
}
// socket.setTcpNoDelay(true);
LOG.finer("Socket setup: (" + socket.getSendBufferSize() + "/"
+ socket.getReceiveBufferSize() + "/" + socket.getSoLinger()
+ "ms) " + socket);
}
/**
* Sets a socket up for use with PowerFolder
* <p>
* FIXME: Is broken. Does not consider LAN-IP list or computers discovered
* by network broadcast. Recommended new API: setupSocket(UDTSocket socket,
* boolean onLAN). Let the caller decide to setup with LAN optimized values.
*
* @param socket
* the Socket to setup
* @param inetSocketAddress
* the remote address
* @throws IOException
*/
public static void setupSocket(UDTSocket socket,
InetSocketAddress inetSocketAddress, Controller controller)
throws IOException
{
Reject.ifNull(socket, "Socket is null");
Reject.ifNull(controller, "Controller is null");
boolean onLan = (inetSocketAddress != null && inetSocketAddress
.getAddress() != null) ? isOnLanOrLoopback(inetSocketAddress
.getAddress()) : false;
if (onLan) {
int bufferSize = ConfigurationEntry.NET_SOCKET_LAN_BUFFER_SIZE
.getValueInt(controller);
if (bufferSize > 0) {
socket.setSoUDPReceiverBufferSize(bufferSize);
socket.setSoUDPSenderBufferSize(bufferSize);
}
int bufferLimit = ConfigurationEntry.NET_SOCKET_LAN_BUFFER_LIMIT
.getValueInt(controller);
if (bufferLimit > 0) {
socket.setSoSenderBufferLimit(bufferLimit);
socket.setSoReceiverBufferLimit(bufferLimit);
}
} else {
int bufferSize = ConfigurationEntry.NET_SOCKET_INTERNET_BUFFER_SIZE
.getValueInt(controller);
if (bufferSize > 0) {
socket.setSoUDPReceiverBufferSize(bufferSize);
socket.setSoUDPSenderBufferSize(bufferSize);
}
int bufferLimit = ConfigurationEntry.NET_SOCKET_INTERNET_BUFFER_LIMIT
.getValueInt(controller);
if (bufferLimit > 0) {
socket.setSoSenderBufferLimit(bufferLimit);
socket.setSoReceiverBufferLimit(bufferLimit);
}
}
LOG.finer("Socket setup: (" + socket.getSoUDPSenderBufferSize() + "/"
+ socket.getSoUDPReceiverBufferSize() + " " + socket);
}
/**
* @param addr
* the address to check
* @return if the address is on lan or on loopback device
*/
public static boolean isOnLanOrLoopback(InetAddress addr) {
Reject.ifNull(addr, "Address is null");
if (!(addr instanceof Inet4Address)) {
LOG.warning("Inet6 not supported yet: " + addr);
}
try {
boolean local = addr.isLoopbackAddress()
|| isFromThisComputer(addr);
if (local) {
return true;
}
// Try harder. Test all interface IP networks
for (InterfaceAddress ia : getAllLocalNetworkAddressesCached()
.keySet())
{
if (!(ia.getAddress() instanceof Inet4Address)) {
continue;
}
if (isOnInterfaceSubnet(ia, addr)) {
return true;
}
}
return false;
} catch (Exception e) {
LOG.warning("Unable to check network setup: " + e);
return false;
}
}
/**
* Returns a Map with all detected local IP-addresses as keys and the
* associated NetworkInterface as values.
*
* @return all local network addresses
* @throws SocketException
*/
public static final Map<InterfaceAddress, NetworkInterface> getAllLocalNetworkAddresses()
throws SocketException
{
Map<InterfaceAddress, NetworkInterface> res = new HashMap<InterfaceAddress, NetworkInterface>();
NetworkInterface ni = null;
InterfaceAddress ia = null;
try {
for (Enumeration<NetworkInterface> eni = NetworkInterface
.getNetworkInterfaces(); eni.hasMoreElements();)
{
ni = eni.nextElement();
for (InterfaceAddress ia0 : ni.getInterfaceAddresses()) {
try {
ia = ia0;
if (ia != null) {
res.put(ia, ni);
}
} catch (Throwable e) {
LOG.warning("Unable to get network interface configuration of "
+ ni + " address: " + ia + ": " + e);
}
}
}
} catch (Error e) {
LOG.warning("Unable to get network interface configuration of "
+ ni + " address: " + ia + ": " + e);
}
return res;
}
public static boolean isOnInterfaceSubnet(InterfaceAddress ia,
InetAddress addr)
{
Reject.ifNull(addr, "Address");
Reject.ifNull(ia, "InterfaceAddress");
try {
if (!(ia.getAddress() instanceof Inet4Address)
|| !(addr instanceof Inet4Address))
{
// TODO How?
return false;
}
byte[] bAddr = ia.getAddress().getAddress();
int iAddr = (bAddr[0] << 24) + (bAddr[1] << 16) + (bAddr[2] << 8)
+ ((int) bAddr[3] & 0xFF);
int iMask = 0;
int nplen = ia.getNetworkPrefixLength();
if (nplen > 32) {
if (ia.getAddress().isSiteLocalAddress()) {
// UGLY HACK because of:
// http://bugs.sun.com/view_bug.do?bug_id=6707289
// Simply assume a C-class network on site local addresses.
nplen = 24;
} else if (ia.getAddress().isLinkLocalAddress()) {
// UGLY HACK because of:
// http://bugs.sun.com/view_bug.do?bug_id=6707289
// Simply assume a B-class network on link local addresses.
// http://en.wikipedia.org/wiki/Link-local_address
nplen = 16;
} else if (ia.getAddress().isLoopbackAddress()) {
// UGLY HACK because of:
// http://bugs.sun.com/view_bug.do?bug_id=6707289
// Simply assume a A-class network on local addresses
// (127.0.0.1)
nplen = 8;
} else {
// Cannot handle
return false;
}
} else if (nplen <= 0) {
if (ia.getAddress().isLoopbackAddress()) {
// UGLY HACK because of:
// http://bugs.sun.com/view_bug.do?bug_id=6707289
// Simply assume a A-class network on local addresses
// (127.0.0.1)
nplen = 8;
} else {
// Cannot handle
return false;
}
}
for (int i = 0; i < nplen; i++) {
int mod = 1 << (31 - i);
iMask += mod;
}
int subnetAddr = iAddr & iMask;
byte[] btAddress = addr.getAddress();
int itAddr = (btAddress[0] << 24) + (btAddress[1] << 16)
+ (btAddress[2] << 8) + ((int) btAddress[3] & 0xFF);
int tsubnetAddr = itAddr & iMask;
// On same subnet!
return tsubnetAddr == subnetAddr;
} catch (Exception e) {
LOG.severe("Prolbem while checking subnet mask. " + e);
return false;
}
}
public static boolean isFromThisComputer(InetAddress addr)
throws SocketException
{
try {
if (addr == null) {
return false;
}
if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
return true;
}
for (InterfaceAddress ia : getAllLocalNetworkAddressesCached()
.keySet())
{
if (ia == null || ia.getAddress() == null) {
continue;
}
if (ia.getAddress().equals(addr)) {
return true;
}
}
} catch (Exception e) {
LOG.severe("Unable to get network setup. " + e);
}
return false;
}
/**
* Returns a Map with all detected local IP-addresses as keys and the
* associated NetworkInterface as values. Caches the result for a certain
* amount of time.
*
* @return the cached list al all network addresses
* @throws SocketException
*/
public static final Map<InterfaceAddress, NetworkInterface> getAllLocalNetworkAddressesCached()
throws SocketException
{
boolean cacheInvalid = LOCAL_NETWORK_ADDRESSES_CACHE == null
|| (System.currentTimeMillis() - CACHE_TIMEOUT > LAST_CHACHE_UPDATE);
if (cacheInvalid) {
LOCAL_NETWORK_ADDRESSES_CACHE = getAllLocalNetworkAddresses();
LAST_CHACHE_UPDATE = System.currentTimeMillis();
}
return LOCAL_NETWORK_ADDRESSES_CACHE;
}
/**
* @return true if this system has support for UDT based connections.
*/
public static final boolean isUDTSupported() {
return UDTSocket.isSupported();
}
/**
* @param address
* @return true if the address is 0.0.0.0
*/
public static final boolean isNullIP(InetAddress address) {
Reject.ifNull(address, "Address is null");
boolean nullIP = false;
if (NULL_IP != null) {
// Using advanced check
nullIP = Boolean.valueOf(NULL_IP.equals(address));
} else {
// Fallback, this works
byte[] addr = address.getAddress();
nullIP = Boolean.valueOf((addr[0] & 0xff) == 0
&& (addr[1] & 0xff) == 0 && (addr[2] & 0xff) == 0
&& (addr[3] & 0xff) == 0);
}
return nullIP;
}
/**
* Tries to retrieve the hostname of the address if available, but returns
* the IP only if not available. Does NOT perform a reverse lookup.
*
* @param addr
* @return the hostname or IP of the address.
*/
public static final String getHostAddressNoResolve(InetAddress addr) {
Reject.ifNull(addr, "Address is null");
try {
String[] str = addr.toString().split("/");
if (StringUtils.isNotBlank(str[0])) {
return str[0];
} else if (StringUtils.isNotBlank(str[1])) {
return str[1];
}
} catch (Exception e) {
LOG.warning("Unable to resolve hostname/ip from " + addr + ". " + e);
}
// Fallback
return addr.getHostAddress();
}
}