/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.util;
import java.net.*;
import java.util.*;
import org.xbill.DNS.*;
import java.text.*;
/**
* Utility methods and fields to use when working with network addresses.
*
* @author Emil Ivov
* @author Damian Minkov
* @author Vincent Lucas
* @author Alan Kelly
*/
public class NetworkUtils
{
private static final Logger logger
= Logger.getLogger(NetworkUtils.class);
/**
* A string containing the "any" local address.
*/
public static final String IN_ADDR_ANY = "0.0.0.0";
/**
* The maximum int value that could correspond to a port nubmer.
*/
public static final int MAX_PORT_NUMBER = 65535;
/**
* The minimum int value that could correspond to a port nubmer bindable
* by the SIP Communicator.
*/
public static final int MIN_PORT_NUMBER = 1024;
/**
* The random port number generator that we use in getRandomPortNumer()
*/
private static Random portNumberGenerator = new Random();
/**
* Determines whether the address is the result of windows auto configuration.
* (i.e. One that is in the 169.254.0.0 network)
* @param add the address to inspect
* @return true if the address is autoconfigured by windows, false otherwise.
*/
public static boolean isWindowsAutoConfiguredIPv4Address(InetAddress add)
{
return (add.getAddress()[0] & 0xFF) == 169
&& (add.getAddress()[1] & 0xFF) == 254;
}
/**
* Determines whether the address is an IPv4 link local address. IPv4 link
* local addresses are those in the following networks:
*
* 10.0.0.0 to 10.255.255.255
* 172.16.0.0 to 172.31.255.255
* 192.168.0.0 to 192.168.255.255
*
* @param add the address to inspect
* @return true if add is a link local ipv4 address and false if not.
*/
public static boolean isLinkLocalIPv4Address(InetAddress add)
{
if (add instanceof Inet4Address)
{
byte address[] = add.getAddress();
if ( (address[0] & 0xFF) == 10)
return true;
if ( (address[0] & 0xFF) == 172
&& (address[1] & 0xFF) >= 16 && address[1] <= 31)
return true;
if ( (address[0] & 0xFF) == 192
&& (address[1] & 0xFF) == 168)
return true;
return false;
}
return false;
}
/**
* Returns a random local port number that user applications could bind to.
* (i.e. above 1024).
* @return a random int located between 1024 and 65 535.
*/
public static int getRandomPortNumber()
{
return getRandomPortNumber(MIN_PORT_NUMBER, MAX_PORT_NUMBER);
}
/**
* Returns a random local port number, greater than min and lower than max.
*
* @param min the minimum allowed value for the returned port number.
* @param max the maximum allowed value for the returned port number.
*
* @return a random int located between greater than min and lower than max.
*/
public static int getRandomPortNumber(int min, int max)
{
return portNumberGenerator.nextInt(max - min) + min;
}
/**
* Returns a random local port number, greater than min and lower than max.
* If the pair flag is set to true, then the returned port number is
* guaranteed to be pair. This is useful for protocols that require this
* such as RTP
*
* @param min the minimum allowed value for the returned port number.
* @param max the maximum allowed value for the returned port number.
* @param pair specifies whether the caller would like the returned port to
* be pair.
*
* @return a random int located between greater than min and lower than max.
*/
public static int getRandomPortNumber(int min, int max, boolean pair)
{
if(pair)
{
int delta = max - min;
delta /= 2;
int port = getRandomPortNumber(min, min + delta);
return port * 2;
}
else
{
return getRandomPortNumber(min, max);
}
}
/**
* Verifies whether <tt>address</tt> could be an IPv4 address string.
*
* @param address the String that we'd like to determine as an IPv4 address.
*
* @return true if the address contained by <tt>address</tt> is an IPv4
* address and false otherwise.
*/
public static boolean isIPv4Address(String address)
{
return IPAddressUtil.isIPv4LiteralAddress(address);
}
/**
* Verifies whether <tt>address</tt> could be an IPv6 address string.
*
* @param address the String that we'd like to determine as an IPv6 address.
*
* @return true if the address contained by <tt>address</tt> is an IPv6
* address and false otherwise.
*/
public static boolean isIPv6Address(String address)
{
return IPAddressUtil.isIPv6LiteralAddress(address);
}
/**
* Checks whether <tt>address</tt> is a valid IP address string.
*
* @param address the address that we'd like to check
* @return true if address is an IPv4 or IPv6 address and false otherwise.
*/
public static boolean isValidIPAddress(String address)
{
// empty string
if (address == null || address.length() == 0)
{
return false;
}
// look for IPv6 brackets and remove brackets for parsing
boolean ipv6Expected = false;
if (address.charAt(0) == '[')
{
// This is supposed to be an IPv6 litteral
if (address.length() > 2
&& address.charAt(address.length() - 1) == ']')
{
// remove brackets from IPv6
address = address.substring(1, address.length() - 1);
ipv6Expected = true;
}
else
{
return false;
}
}
// look for IP addresses
if (Character.digit(address.charAt(0), 16) != -1
|| (address.charAt(0) == ':'))
{
byte[] addr = null;
// see if it is IPv4 address
addr = IPAddressUtil.textToNumericFormatV4(address);
// if not, see if it is IPv6 address
if (addr == null)
{
addr = IPAddressUtil.textToNumericFormatV6(address);
}
// if IPv4 is found when IPv6 is expected
else if (ipv6Expected)
{
// invalid address: IPv4 address surrounded with brackets!
return false;
}
// if an IPv4 or IPv6 address is found
if (addr != null)
{
// is an IP address
return true;
}
}
// no matches found
return false;
}
/**
* Returns array of hosts from the SRV record of the specified domain.
* The records are ordered against the SRV record priority
* @param domain the name of the domain we'd like to resolve (_proto._tcp
* included).
* @return an array of InetSocketAddress containing records returned by the DNS
* server - address and port .
* @throws ParseException if <tt>domain</tt> is not a valid domain name.
*/
public static InetSocketAddress[] getSRVRecords(String domain)
throws ParseException
{
Record[] records = null;
try
{
Lookup lookup = new Lookup(domain, Type.SRV);
records = lookup.run();
}
catch (TextParseException tpe)
{
logger.error("Failed to parse domain="+domain, tpe);
throw new ParseException(tpe.getMessage(), 0);
}
if (records == null)
{
return null;
}
String[][] pvhn = new String[records.length][4];
for (int i = 0; i < records.length; i++)
{
SRVRecord srvRecord = (SRVRecord) records[i];
pvhn[i][0] = "" + srvRecord.getPriority();
pvhn[i][1] = "" + srvRecord.getWeight();
pvhn[i][2] = "" + srvRecord.getPort();
pvhn[i][3] = srvRecord.getTarget().toString();
if (pvhn[i][3].endsWith("."))
{
pvhn[i][3] = pvhn[i][3].substring(0, pvhn[i][3].length() - 1);
}
}
/* sort the SRV RRs by RR value (lower is preferred) */
Arrays.sort(pvhn, new Comparator<String[]>()
{
public int compare(String array1[], String array2[])
{
return (Integer.parseInt( array1[0])
- Integer.parseInt( array2[0]));
}
});
/* put sorted host names in an array, get rid of any trailing '.' */
InetSocketAddress[] sortedHostNames = new InetSocketAddress[pvhn.length];
for (int i = 0; i < pvhn.length; i++)
{
sortedHostNames[i] =
new InetSocketAddress(pvhn[i][3], Integer.valueOf(pvhn[i][2]));
}
if (logger.isTraceEnabled())
{
logger.trace("DNS SRV query for domain " + domain + " returned:");
for (int i = 0; i < sortedHostNames.length; i++)
{
logger.trace(sortedHostNames[i]);
}
}
return sortedHostNames;
}
/**
* Returns an <tt>InetSocketAddress</tt> representing the first SRV
* record available for the specified domain or <tt>null</tt> if there are
* not SRV records for <tt>domain</tt>.
*
* @param domain the name of the domain we'd like to resolve.
* @param service the service that we are trying to get a record for.
* @param proto the protocol that we'd like <tt>service</tt> on.
*
* @return the first InetSocketAddress containing records returned by the
* DNS server - address and port .
* @throws ParseException if <tt>domain</tt> is not a valid domain name.
*/
public static InetSocketAddress getSRVRecord( String service,
String proto,
String domain)
throws ParseException
{
InetSocketAddress[] records = getSRVRecords("_" + service
+ "._" + proto
+ "." + domain);
if(records == null || records.length == 0)
return null;
return records[0];
}
/**
* Creates an InetAddress from the specified <tt>hostAddress</tt>. The point
* of using the method rather than creating the address by yourself is that
* it would first check whether the specified <tt>hostAddress</tt> is indeed
* a valid ip address. It this is the case, the method would create the
* <tt>InetAddress</tt> using the <tt>InetAddress.getByAddress()</tt>
* method so that no DNS resolution is attempted by the JRE. Otherwise
* it would simply use <tt>InetAddress.getByName()</tt> so that we would an
* <tt>InetAddress</tt> instance even at the cost of a potential DNS
* resolution.
*
* @param hostAddress the <tt>String</tt> representation of the address
* that we would like to create an <tt>InetAddress</tt> instance for.
*
* @return an <tt>InetAddress</tt> instance corresponding to the specified
* <tt>hostAddress</tt>.
*
* @throws UnknownHostException if any of the <tt>InetAddress</tt> methods
* we are using throw an exception.
*/
public static InetAddress getInetAddress(String hostAddress)
throws UnknownHostException
{
//is null
if (hostAddress == null || hostAddress.length() == 0)
{
throw new UnknownHostException(
hostAddress + " is not a valid host address");
}
//transform IPv6 literals into normal addresses
if (hostAddress.charAt(0) == '[')
{
// This is supposed to be an IPv6 literal
if (hostAddress.length() > 2
&& hostAddress.charAt(hostAddress.length()-1) == ']')
{
hostAddress = hostAddress.substring(1, hostAddress.length() -1);
}
else
{
// This was supposed to be a IPv6 address, but it's not!
throw new UnknownHostException(hostAddress);
}
}
if (NetworkUtils.isValidIPAddress(hostAddress))
{
byte[] addr = null;
// attempt parse as IPv4 address
addr = IPAddressUtil.textToNumericFormatV4(hostAddress);
// if not IPv4, parse as IPv6 address
if (addr == null)
{
addr = IPAddressUtil.textToNumericFormatV6(hostAddress);
}
return InetAddress.getByAddress(hostAddress, addr);
}
else
{
return InetAddress.getByName(hostAddress);
}
}
}