Work in progress on the implementation of parallel DNS queries.

cusax-fix
Emil Ivov 15 years ago
parent a5613d06de
commit 6438dd780e

@ -9,6 +9,8 @@
import java.net.*;
import java.util.*;
import net.java.sip.communicator.util.dns.*;
import org.xbill.DNS.*;
import java.text.*;
@ -70,12 +72,37 @@ public class NetworkUtils
*/
public static final int MIN_PORT_NUMBER = 1024;
/**
* The random port number generator that we use in getRandomPortNumer()
*/
private static Random portNumberGenerator = new Random();
/**
* The name of the system property that users may use to override the
* address of our backup DNS resolver.
*/
public static final String PNAME_BACKUP_RESOLVER
= "net.java.sip.communicator.util.dns.BACKUP_RESOLVER";
/**
* The name of the system property that users may use to override the port
* of our backup DNS resolver.
*/
public static final String PNAME_BACKUP_RESOLVER_PORT
= "net.java.sip.communicator.util.dns.BACKUP_RESOLVER_PORT";
/**
* The address of the backup resolver we would use by default.
*/
public static final String DEFAULT_BACKUP_RESOLVER
= "backup-resolver.jitsi.net";
/**
* The DNSjava resolver that we use with SRV and NAPTR queries in order to
* try and smooth the problem of DNS servers that silently drop them.
*/
private static Resolver parallelResolver = null;
/**
* Determines whether the address is the result of windows auto configuration.
* (i.e. One that is in the 169.254.0.0 network)
@ -521,12 +548,12 @@ public static InetSocketAddress[] getSRVRecords(String domain)
Record[] records = null;
try
{
Lookup lookup = new Lookup(domain, Type.SRV);
Lookup lookup = createLookup(domain, Type.SRV);
records = lookup.run();
}
catch (TextParseException tpe)
{
logger.error("Failed to parse domain="+domain, tpe);
logger.error("Failed to parse domain=" + domain, tpe);
throw new ParseException(tpe.getMessage(), 0);
}
if (records == null)
@ -644,14 +671,14 @@ public static InetSocketAddress[] getSRVRecords(String service,
}
/**
* Makes a NAPTR query.
* @param domain the name of the domain we'd like to resolve.
* @return an array with the values or null if no records found.
* The returned records are an array of
* [Order, Service(Transport) and Replacement
* Makes a NAPTR query and returns the result. The returned records are an
* array of [Order, Service(Transport) and Replacement
* (the srv to query for servers and ports)] this all for supplied
* <tt>domain</tt>.
*
* @param domain the name of the domain we'd like to resolve.
* @return an array with the values or null if no records found.
*
* @throws ParseException if <tt>domain</tt> is not a valid domain name.
*/
public static String[][] getNAPTRRecords(String domain)
@ -660,7 +687,7 @@ public static String[][] getNAPTRRecords(String domain)
Record[] records = null;
try
{
Lookup lookup = new Lookup(domain, Type.NAPTR);
Lookup lookup = createLookup(domain, Type.NAPTR);
records = lookup.run();
}
catch (TextParseException tpe)
@ -707,6 +734,7 @@ public int compare(String array1[], String array2[])
/**
* Returns the mapping from rfc3263 between service and the protocols.
*
* @param service the service from NAPTR record.
* @return the protocol TCP, UDP or TLS.
*/
@ -790,13 +818,12 @@ public static InetAddress getInetAddress(String hostAddress)
}
/**
* 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).
* Returns array of hosts from the A record of the specified domain.
* The records are ordered against the A record priority
* @param domain the name of the domain we'd like to resolve.
* @param port the port number of the returned <tt>InetSocketAddress</tt>
* @return an array of InetSocketAddress containing records returned by the DNS
* server - address and port .
* @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 getARecord(String domain, int port)
@ -805,6 +832,10 @@ public static InetSocketAddress getARecord(String domain, int port)
Record[] records = null;
try
{
//note that we intentionally do not use our parallel resolver here.
//for starters we'd like to make sure that it works well enough
//with SRV and NAPTR queries. We may then also adopt it for As
//and AAAAs once it proves to be reliable (posted on: 2010-11-24)
Lookup lookup = new Lookup(domain, Type.A);
records = lookup.run();
}
@ -827,13 +858,12 @@ public static InetSocketAddress getARecord(String domain, int port)
}
/**
* 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).
* Returns array of hosts from the AAAA record of the specified domain.
* The records are ordered against the AAAA record priority
* @param domain the name of the domain we'd like to resolve.
* @param port the port number of the returned <tt>InetSocketAddress</tt>
* @return an array of InetSocketAddress containing records returned by the DNS
* server - address and port .
* @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 getAAAARecord(String domain, int port)
@ -842,6 +872,10 @@ public static InetSocketAddress getAAAARecord(String domain, int port)
Record[] records = null;
try
{
//note that we intentionally do not use our parallel resolver here.
//for starters we'd like to make sure that it works well enough
//with SRV and NAPTR queries. We may then also adopt it for As
//and AAAAs once it proves to be reliable (posted on: 2010-11-24)
Lookup lookup = new Lookup(domain, Type.AAAA);
records = lookup.run();
}
@ -970,4 +1004,77 @@ private static boolean isMappedIPv4Addr(byte[] address)
return false;
}
/**
* Creates a new {@link Lookup} instance using our own {@link
* ParallelResolver}.
*
* @param domain the domain we will be resolving
* @param type the type of the record we will be trying to obtain.
*
* @return the newly created {@link Lookup} instance.
*
* @throws TextParseException if <tt>domain</tt> is not a valid domain name.
*/
private static Lookup createLookup(String domain, int type)
throws TextParseException
{
Lookup lookup = new Lookup(domain, Type.SRV);
//initiate our global parallel resolver if this is our first ever
//DNS query.
if(parallelResolver == null)
{
try
{
String customRslvrAddr
= System.getProperty(PNAME_BACKUP_RESOLVER);
String rslvrAddrStr = DEFAULT_BACKUP_RESOLVER;
if(! StringUtils.isNullOrEmpty( customRslvrAddr ))
rslvrAddrStr = customRslvrAddr;
InetAddress resolverAddress
= getInetAddress(rslvrAddrStr);
int rslvrPort = SimpleResolver.DEFAULT_PORT;
//jsut in case someone said sth else ... and we want no errs
try
{
rslvrPort = Integer.getInteger(
PNAME_BACKUP_RESOLVER_PORT);
}
catch(Throwable t)
{
logger.info("Ignoring invalid resolver port.");
}
InetSocketAddress resolverSockAddr
= new InetSocketAddress(resolverAddress, rslvrPort);
parallelResolver = new ParallelResolver(
new InetSocketAddress[]{resolverSockAddr});
}
catch(Throwable t)
{
//We don't want to a problem with our parallel resolver to
//make our entire DNS resolution to fail so in case something
//goes wrong during initialization so we default to the
//dns java default resolver
logger.info("failed to initialize parallel resolver. we will "
+"be using dnsjava's default one instead");
if(logger.isDebugEnabled())
logger.debug("exception was: ", t);
parallelResolver = Lookup.getDefaultResolver();
}
}
lookup.setResolver( parallelResolver );
return lookup;
}
}

@ -10,6 +10,8 @@
import java.net.*;
import java.util.*;
import net.java.sip.communicator.util.*;
import org.xbill.DNS.*;
/**
@ -17,14 +19,14 @@
* in networks where DNS servers would ignore SRV and NAPTR queries (i.e.
* without even sending an error response).
* <p>
* We achieve this by entering a panic mode whenever we detect an abnormal delay
* while waiting for a DNS answer. Once we enter panic mode, we start
* duplicating all queries and sending them to both our primary and backup
* resolvers (in case we have any). We then always return the first response we
* get.
* We achieve this by entering a redundant mode whenever we detect an abnormal
* delay (longer than <tt>DNS_PATIENCE</tt>) while waiting for a DNS answer.
* Once we enter redundant mode, we start duplicating all queries and sending
* them to both our primary and backup resolvers (in case we have any). We then
* always return the first response we get.
* <p>
* We exit panic mode after receiving three consecutive timely responses from
* our primary resolver.
* We exit redundant mode after receiving <tt>DNS_REDEMPTION</tt> consecutive
* timely responses from our primary resolver.
* <p>
* Note that this class does not attempt to fix everything that may be wrong
* with local DNS servers. For example, some DNS servers would return
@ -48,6 +50,73 @@
*/
public class ParallelResolver implements Resolver
{
/**
* The <tt>Logger</tt> used by the <tt>ParallelResolver</tt>
* class and its instances for logging output.
*/
private static final Logger logger = Logger
.getLogger(ParallelResolver.class.getName());
/**
* Indicates whether we are currently in a mode where all DNS queries are
* sent to both the primary and the backup DNS servers.
*/
private static boolean redundantMode = false;
/**
* The default number of milliseconds it takes us to get into redundant
* mode while waiting for a DNS query response.
*/
public static final long DNS_PATIENCE = 1500;
/**
* The name of the System property that allows us to override the default
* <tt>DNS_PATIENCE</tt> value.
*/
public static final String PNAME_DNS_PATIENCE
= "net.java.sip.communicator.util.dns.DNS_PATIENCE";
/**
* The currently configured number of milliseconds that we need to wait
* before entering redundant mode.
*/
private static long currentDnsPatience = DNS_PATIENCE;
/**
* The default number of times that the primary DNS would have to provide a
* faster response than the backup resolver before we consider it safe
* enough to exit redundant mode.
*/
public static final int DNS_REDEMPTION = 7;
/**
* The name of the System property that allows us to override the default
* <tt>DNS_REDEMPTION</tt> value.
*/
public static final String PNAME_DNS_REDEMPTION
= "net.java.sip.communicator.util.dns.DNS_REDEMPTION";
/**
* The currently configured number of times that the primary DNS would have
* to provide a faster response than the backup resolver before we consider
* it safe enough to exit redundant mode.
*/
public static int currentDnsRedemption = DNS_REDEMPTION;
/**
* The number of fast responses that we need to get from the primary
* resolver before we exit redundant mode. <tt>0</tt> indicates that we are
* no longer in redundant mode
*/
private static int redemptionStatus = 0;
/**
* A lock that we use while determining whether we've completed redemption
* and can exit redundant mode.
*/
private final static Object redemptionLock = new Object();
/**
* The default resolver that we use if everything works properly.
*/
@ -64,6 +133,27 @@ public class ParallelResolver implements Resolver
//should never happen
throw new RuntimeException("Failed to initialize resolver");
}
initProperties();
}
/**
* Default resolver property initialisation
*/
private static void initProperties()
{
try
{
currentDnsPatience = Long.getLong(PNAME_DNS_PATIENCE);
currentDnsRedemption = Integer.getInteger(PNAME_DNS_REDEMPTION);
}
catch(Throwable t)
{
//we don't want messed up properties to screw up DNS resolution
//so we just log.
logger.info("Failed to initialize DNS resolver properties", t);
}
}
/**
@ -118,27 +208,55 @@ public Message send(Message query)
ParallelResolution resolution = new ParallelResolution(query);
resolution.sendFirstQuery();
/*
//if we are not in panic mode we should wait a bit and see how this
//make a copy of the redundant mode variable in case we are currently
//completed a redemption that started earlier.
boolean redundantModeCopy;
synchronized(redemptionLock)
{
redundantModeCopy = redundantMode;
}
//if we are not in redundant mode we should wait a bit and see how this
//goes. if we get a reply we could return bravely.
if(!panicMode)
if(!redundantModeCopy)
{
if(resolution.waitForResponse(patience))
if(resolution.waitForResponse(currentDnsPatience))
{
//we are done.
resolution.returnResponse();
return resolution.returnResponseOrThrowUp();
}
else
{
synchronized(redemptionLock)
{
redundantMode = true;
redemptionStatus = DNS_REDEMPTION;
}
}
if (response != null)
return response;
}
//panic mode
//we are definitely in redundant mode now
resolution.sendBackupQueries();
panicMode = true;
resolution.waitForResponse(0);
*/
return null;
//check if it is time to end redundant mode.
synchronized(redemptionLock)
{
if(resolution.primaryResolverRespondedFirst
&& redemptionStatus > 0)
{
redemptionStatus --;
//yup, it's now time to end DNS redundant mode;
if(currentDnsPatience == 0)
redundantMode = false;
}
}
return resolution.returnResponseOrThrowUp();
}
/**
@ -266,7 +384,7 @@ private class ParallelResolution extends Thread
* The field where we would store the first incoming response to our
* query.
*/
private Message response;
public Message response;
/**
* The field where we would store the first error we receive from a DNS
@ -282,7 +400,7 @@ private class ParallelResolution extends Thread
/**
* Indicates that a response was received from the primary resolver.
*/
private boolean primaryResolverResponded = true;
private boolean primaryResolverRespondedFirst = true;
/**
* Creates a {@link ParallelResolution} for the specified <tt>query</tt>
@ -309,9 +427,10 @@ public void sendFirstQuery()
*/
public void run()
{
Message localResponse = null;
try
{
response = defaultResolver.send(query);
localResponse = defaultResolver.send(query);
}
catch (Throwable exc)
{
@ -319,7 +438,15 @@ public void run()
}
synchronized(this)
{
//if the backup resolvers had already replied we ignore the
//reply of the primary one whatever it was.
if(done)
return;
response = localResponse;
done = true;
notify();
}
}
@ -329,12 +456,15 @@ public void run()
*/
public void sendBackupQueries()
{
for (Resolver resolver : backupResolvers)
synchronized(this)
{
if (done)
return;
for (Resolver resolver : backupResolvers)
{
if (done)
return;
resolver.sendAsync(query, this);
resolver.sendAsync(query, this);
}
}
}
@ -376,10 +506,15 @@ public boolean waitForResponse(long waitFor)
*
* @return the response {@link Message} we received from the DNS.
*
* @throws Throwable if this resolution ended badly ;)
* @throws IOException if this resolution ended badly because of a
* network IO error
* @throws RuntimeException if something unexpected happened
* during resolution.
* @throws IllegalArgumentException if something unexpected happened
* during resolution.
*/
public Message returnResponse()
throws Throwable
public Message returnResponseOrThrowUp()
throws IOException, RuntimeException, IllegalArgumentException
{
if(!done)
waitForResponse(0);
@ -412,7 +547,7 @@ public void receiveMessage(Object id, Message message)
return;
this.response = message;
this.primaryResolverResponded = false;
this.primaryResolverRespondedFirst = false;
done = true;
}

Loading…
Cancel
Save