package org.mobicents.servlet.sip.core;
import gov.nist.core.net.AddressResolver;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.sip.ListeningPoint;
import javax.sip.address.Hop;
import org.apache.log4j.Logger;
import org.mobicents.servlet.sip.utils.Inet6Util;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.SRVRecord;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
/**
* The Address resolver to resolve proxy domain to a hop to the outbound proxy server
* by doing SRV lookup of the host of the Hop as mandated by rfc3263. <br/>
*
* some of the rfc3263 can hardly be implemented and NAPTR query can hardly be done
* since the stack populate port and transport automatically.
*
* @author M. Ranganathan
* @author J. Deruelle
*
*/
public class DNSAddressResolver implements AddressResolver {
private static Logger logger = Logger.getLogger(DNSAddressResolver.class);
//the sip factory implementation to be able
SipApplicationDispatcher sipApplicationDispatcher;
static ConcurrentHashMap<String, Map<String, String>> cachedLookup = new ConcurrentHashMap<String, Map<String, String>>();
/**
* @param sipApplicationDispatcherImpl
*/
public DNSAddressResolver(
SipApplicationDispatcher sipApplicationDispatcher) {
this.sipApplicationDispatcher = sipApplicationDispatcher;
}
/*
* (non-Javadoc)
* @see gov.nist.core.net.AddressResolver#resolveAddress(javax.sip.address.Hop)
*/
public Hop resolveAddress(Hop hop) {
String hopHost = hop.getHost();
int hopPort = hop.getPort();
String hopTransport = hop.getTransport();
if(logger.isDebugEnabled()) {
logger.debug("Resolving " + hopHost + " transport " + hopTransport);
}
// As per rfc3263 Section 4.2
// If TARGET is a numeric IP address, the client uses that address. If
// the URI also contains a port, it uses that port. If no port is
// specified, it uses the default port for the particular transport
// protocol.numeric IP address, no DNS lookup to be done
if(Inet6Util.isValidIP6Address(hopHost)
|| Inet6Util.isValidIPV4Address(hopHost)) {
if(logger.isDebugEnabled()) {
logger.debug("host " + hopHost + " is a numeric IP address, " +
"no DNS SRV lookup to be done, using the hop given in param");
}
return hop;
}
// if the host belong to the container, it tries to resolve the ip address
if(sipApplicationDispatcher.findHostNames().contains(hopHost)) {
try {
InetAddress ipAddress = InetAddress.getByName(hopHost);
return new HopImpl(ipAddress.getHostAddress(), hopPort, hopTransport);
} catch (UnknownHostException e) {
logger.warn(hopHost + " belonging to the container cannoit be resolved");
}
}
// As per rfc3263 Section 4.2
// If the TARGET was not a numeric IP address, and no port was present
// in the URI, the client performs an SRV query
return resolveHostByDnsSrvLookup(hop);
}
/**
* Resolve the Host by doing a SRV lookup on it
* @param host the host
* @param port the port
* @param transport the transport
* @return
*/
public static Hop resolveHostByDnsSrvLookup(Hop hop) {
String host = hop.getHost();
String transport = hop.getTransport();
if(transport==null) {
transport = ListeningPoint.UDP;
}
transport = transport.toLowerCase();
Record[] records = null;
try {
records = new Lookup("_sip._" + transport
+ "." + host, Type.SRV).run();
} catch (TextParseException e) {
logger.error("Impossible to parse the parameters for dns lookup", e);
}
if (records == null || records.length == 0) {
// SRV lookup failed, use the outbound proxy directly.
if(logger.isDebugEnabled()) {
logger
.debug("SRV lookup for host:transport " +
""+ host + "/" + transport + " returned nothing " +
"-- we are going to just use the domain name directly");
}
return hop;
} else {
Map<String, String> cachedEntry = foundCachedEntry(host, transport, (Record[]) records);
if(cachedEntry == null) {
SRVRecord record = (SRVRecord) records[0];
int recordPort = record.getPort();
String resolvedName = record.getTarget().toString();
try {
String hostAddress= InetAddress.getByName(resolvedName).getHostAddress();
if(logger.isDebugEnabled()) {
logger.debug("Did a successful DNS SRV lookup for host:transport " +
""+ host + "/" + transport +
" , Host Name = " + resolvedName +
" , Host IP Address = " + hostAddress +
", Host Port = " + recordPort);
}
Map<String, String> entry = new HashMap<String, String>();
entry.put("hostName", resolvedName);
entry.put("hostAddress", hostAddress);
entry.put("hostPort", ""+recordPort);
cachedLookup.putIfAbsent(host + transport, entry);
return new HopImpl(hostAddress, recordPort, transport);
} catch (UnknownHostException e) {
logger.error("Impossible to get the host address of the resolved name, " +
"we are going to just use the domain name directly" + resolvedName, e);
return hop;
}
} else {
String entryResolvedName = cachedEntry.get("hostName");
String hostAddress = cachedEntry.get("hostAddress");
String hostPort = cachedEntry.get("hostPort");
if(logger.isDebugEnabled()) {
logger.debug("Reusing a previous DNS SRV lookup for host:transport " +
""+ host + "/" + transport +
" , Host Name = " + entryResolvedName +
" , Host IP Address = " + hostAddress +
", Host Port = " + hostPort);
}
return new HopImpl(hostAddress, Integer.parseInt(hostPort), transport);
}
}
}
public static Map<String, String> foundCachedEntry(String host, String transport, Record[] records) {
Map<String, String> entry = cachedLookup.get(host+transport);
if(entry == null) {
return null;
}
String entryResolvedName = entry.get("hostName");
String hostAddress = entry.get("hostAddress");
String hostPort = entry.get("hostPort");
for (Record record : records) {
if(record instanceof SRVRecord) {
SRVRecord srvRecord = (SRVRecord) record;
String resolvedName = srvRecord.getTarget().toString();
String resolvedHostAddress;
try {
resolvedHostAddress = InetAddress.getByName(resolvedName).getHostAddress();
int recordPort = srvRecord.getPort();
if(entryResolvedName.equalsIgnoreCase(resolvedName)
&& hostAddress.equalsIgnoreCase(resolvedHostAddress)
&& hostPort.equalsIgnoreCase("" + recordPort)) {
return entry;
}
} catch (UnknownHostException e) {
logger.warn("Couldn't resolve address " + resolvedName);
}
}
}
return null;
}
}