/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.ericsson.ssa.sip.dns;
import com.ericsson.ssa.config.annotations.Configuration;
import com.ericsson.ssa.config.annotations.UsagePolicy;
import com.ericsson.ssa.container.TransportNotSupportedException;
import com.ericsson.ssa.container.reporter.ReporterResolver;
import com.ericsson.ssa.container.reporter.Reporter;
import org.jvnet.glassfish.comms.deployment.backend.SessionCase;
import com.ericsson.ssa.sip.Header;
import com.ericsson.ssa.sip.Layer;
import com.ericsson.ssa.sip.LayerHelper;
import com.ericsson.ssa.sip.RecordRouteResolver;
import com.ericsson.ssa.sip.SipServletRequestImpl;
import com.ericsson.ssa.sip.SipServletResponseImpl;
import com.ericsson.ssa.sip.URIImpl;
import java.util.ListIterator;
import java.util.logging.Level;
// inserted by hockey (automatic)
import java.util.logging.Logger;
import org.jvnet.glassfish.comms.util.LogUtil;
import javax.servlet.sip.Address;
import javax.servlet.sip.ServletParseException;
/**
* @author ekrigro TODO To change the template for this generated type comment
* go to Window - Preferences - Java - Code Style - Code Templates
*
* @reviewed epetstr 2007-feb-20
*
*/
public class ResolverManager implements Layer {
private static final int TIMEOUT_BASED_QUARANTINE_TIME = 0;
private static final int DEFAULT_QUARANTINE_TIME = 1;
private static DnsResolver myDnsResolver = DnsResolver.getInstance();
private static ResolverManager myResolverManager = new ResolverManager();
private static TargetResolver myTargetResolver = TargetResolver.getInstance();
private Layer _nextLayer;
private Logger logger = LogUtil.SIP_LOGGER.getLogger();
private final int UNDEFINED_SIZE = -1; // messageLength for MTU_SIZE handling
private boolean defaultTCPTransport;
// Determines if the functionality described in RFC3263 4.3 Details of RFC
// 2782 Process is disabled.
private boolean _eas_503_disabled = false;
private Reporter _reporter;
private int _defaultQuarantineTime = DEFAULT_QUARANTINE_TIME;
private int _timeoutBasedQuarantineTime = TIMEOUT_BASED_QUARANTINE_TIME;
private ResolverManager() {
}
public void setReporters(String reporters) {
_reporter = ReporterResolver.getInstance().getReporter(reporters);
}
public Reporter getReporter() {
return _reporter;
}
public static ResolverManager getInstance() {
return myResolverManager;
}
@Configuration(key="defaultQuarantineTime", node = "/SipContainer")
public void setDefaultQuarantineTime(final Integer defaultQuarantineTime) {
if (logger.isLoggable(Level.INFO)) {
logger.log(Level.INFO, "default quarantine time value will be set to "
+ defaultQuarantineTime.intValue());
}
if (defaultQuarantineTime == null) {
_defaultQuarantineTime = DEFAULT_QUARANTINE_TIME;
}
_defaultQuarantineTime = defaultQuarantineTime.intValue();
}
public int getDefaultQuarantineTime() {
return _defaultQuarantineTime;
}
@Configuration(key="timeoutBasedQuarantineTime", node = "/SipContainer")
public void setTimeoutBasedQuarantineTime(final Integer timeoutBasedQuarantineTime) {
if (logger.isLoggable(Level.INFO)) {
logger.log(Level.INFO, "timeout based quarantine time value will be set to "
+ timeoutBasedQuarantineTime.intValue());
}
if (timeoutBasedQuarantineTime == null) {
_timeoutBasedQuarantineTime = TIMEOUT_BASED_QUARANTINE_TIME;
}
_timeoutBasedQuarantineTime = timeoutBasedQuarantineTime.intValue();
}
public int getTimeoutBasedQuarantineTime() {
return _timeoutBasedQuarantineTime;
}
/**
* Configurable parameter.
*
* @return true only if the {@link #setDefaultTCPTransport(boolean)} has been
* called with value true.
*/
public synchronized boolean isDefaultTCPTransport() {
return defaultTCPTransport;
}
/**
* Called via java reflection from
* <code> SSAContainerListener.initializeServer()</code>
*
* @param defaultTCPTransport
* true
*/
@Configuration(key = "DefaultTcpTransport", node = "/SipService/SipProtocol")
public synchronized void setDefaultTCPTransport(Boolean defaultTCPTransport) {
this.defaultTCPTransport = defaultTCPTransport;
}
/*
* (non-Javadoc)
*
* @see com.ericsson.ssa.sip.Layer#next(com.ericsson.ssa.sip.SipServletRequestImpl)
*/
public void next(SipServletRequestImpl req) {
// receiving a request...
checkRouteHeader(req, true);
req.pushTransactionDispatcher(this);
req.pushApplicationDispatcher(this);
LayerHelper.next(req, this, _nextLayer);
}
/*
* (non-Javadoc)
*
* @see com.ericsson.ssa.sip.Layer#next(com.ericsson.ssa.sip.SipServletResponseImpl)
*/
public void next(SipServletResponseImpl resp) {
boolean shouldForwardResponseToApplication = true;
// if (resp.getStatus() == 503) // TODO check if we should use defined
// identifiers (SC_SERVICE_UNAVAILABLE)
// we do the dns failover for 503, no matter whether it was received from external or generated internally
// but for timeout (408) we only do failover if it was generated by ourselves, not if received from the network.
// Of course, if the dns failover is disabled (_eas_503_disabled) we skip the failover functionality
if (!_eas_503_disabled
&& (resp.getStatus() == 503
|| (resp.getStatus() == 408 && resp.isInternalTransportFailure()))) {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "handling DNS failover for : " + resp.getStatus());
}
int expireTime;
// init the expireTime to a default which is overridden if we received
// a retry-after in the response
if (resp.getStatus() == 408) {
// for timeouts use a different (typically longer) timer
// the reason is that if we failed to reach an instance for 64*T1
// we except that it might be more than a hiccup
expireTime = getTimeoutBasedQuarantineTime();
} else {
expireTime = getDefaultQuarantineTime();
}
// check if the Retry-After header field is set&valid
String retryAfterHeaderStr = resp.getHeader(Header.RETRY_AFTER);
String retryAfter = getRetryAfterValue(retryAfterHeaderStr);
if (retryAfter != null) {
try {
expireTime = Integer.parseInt(retryAfter);
} catch (NumberFormatException ignored) {
}
}
try {
TargetResolver resolver = TargetResolver.getInstance();
SipServletRequestImpl req = (SipServletRequestImpl) resp.getRequest();
// put the target in quarantine for the requested time
if (expireTime > 0) {
resolver.setTargetFailed(req.getRemote(), expireTime);
}
// but for the current request we also mark it as failed
// so regardless of the quarantine time it will not be tried twice
// for the same request
req.addFailedTarget(req.getRemote());
// shortcut to let the dns resolver consider the failed targets
// for this request by setting them via threadlocal
DnsResolver.getInstance().setThreadLocalFailedTargets(req.getFailedTargets());
TargetTuple newDestination = resolver.resolveRequest(req,
req.getMessageSize());
if (newDestination == null) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"List of available targets is exhausted");
}
shouldForwardResponseToApplication = true; // nothing more to
// do.....
resp.setInternalTransportFailure(false);
if (resp.getStatus() == 408) {
// keep the status
} else {
resp.setStatus(500);
}
} else {
req.setRemote(newDestination);
// send in new transaction....
req.restoreRetransmissionApplicationStack(
ResolverManager.getInstance());
if("CANCEL".equals(req.getMethod())
|| "ACK".equals(req.getMethod())) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"For CANCEL & ACK DONT POP VIA");
}
} else {
req.popTopVia();
}
req.popDispatcher().dispatch(req);
shouldForwardResponseToApplication = false; // Do not pass this
// response up
}
} catch (Exception e) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"failed during response processing:", e);
}
}
finally {
// good pratice to unset the threadlocal in a finally block
DnsResolver.getInstance().clearThreadLocalFailedTargets();
}
}
resp.setInternalTransportFailure(false); // also when dns failover is disabled
if (shouldForwardResponseToApplication) {
LayerHelper.next(resp, this, _nextLayer);
}
}
/*
* (non-Javadoc)
*
* @see com.ericsson.ssa.sip.Layer#registerNext(com.ericsson.ssa.sip.Layer)
*/
public void registerNext(Layer layer) {
_nextLayer = layer;
}
/*
* (non-Javadoc)
*
* @see com.ericsson.ssa.sip.Dispatcher#dispatch(com.ericsson.ssa.sip.SipServletRequestImpl)
*/
public void dispatch(SipServletRequestImpl req) {
// sending a request...
//
try {
if (doResolve(req, req.getMessageSize())) {
req.popDispatcher().dispatch(req);
}
} catch (TransportNotSupportedException noSuppExc) {
/*
* The size of a message must be checked after the transaction layer
* has added the via header Therefore this RuntimeException is thrown
* by network layer when messageSize > MAX_MTU_SIZE this means we tried
* tcp instead of udp however remote host does not support tcp....
*/
if (doResolve(req, UNDEFINED_SIZE)) {
req.popDispatcher().dispatch(req);
}
}
}
private boolean doResolve(SipServletRequestImpl req, int msgSize) {
TargetTuple destination = null;
try {
destination = myTargetResolver.resolveRequest(req, msgSize);
} catch (Throwable t) {
// log but ignore exception, destination will be null anyway...
logger.log(Level.WARNING, "failed during request processing:", t);
}
if (destination == null) {
handleResolvError(req);
return false;
}
// hh29713, lets remove this loop detection since it causes problem when
// spiral occurs.
// jsr 289 will solve this.
// Old correction removed: HG69331 Detect loop if ip-address points to us
// and no more Route exist
/*
* if
* (destination.getIP().equals(NetworkManager.getInstance().getPreferredIpAddress()) &&
* (destination.getPort() == NetworkManager.getInstance().getDefaultPort() ||
* destination.getPort() ==
* NetworkManager.getInstance().getSubsequentPort()) &&
* ((req.getHeader(Header.ROUTE)) == null)) { SipServletResponseImpl
* loopResp = null; if (req.getMethod().equals("SUBSCRIBE") ||
* req.getMethod().equals("NOTIFY")) { // RFC 3265: "489 Bad Event"
* response should be returned to indicate that the // specified
* event/event class is not understood by any application on this server
* loopResp = req.createTerminatingResponse(489); } else { // return 404
* Not Found loopResp = req.createTerminatingResponse(404); } if
* (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "loop detected, send " +
* loopResp.getStatus() + " response"); }
* loopResp.popDispatcher().dispatch(loopResp); return false; }
*/
req.setRemote(destination);
req.handleAssertedIdentity();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "resolving req:\n" + destination.toString());
}
return true;
}
private void handleResolvError(SipServletRequestImpl req) {
SipServletResponseImpl failureResp = getRequestTimeout(req);
// TR HH52078
if (failureResp == null) {
return;
}
// incoming req (with via) --> send 408 to client
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "failed to resolve req, send 408 response");
}
failureResp.popDispatcher().dispatch(failureResp);
}
private SipServletResponseImpl getRequestTimeout(SipServletRequestImpl in) {
SipServletResponseImpl resp = in.createTerminatingResponse(408);
// set the transaction request, since UA would have already
// copied it... B2bUA will fail otherwise.
SipServletRequestImpl req = in.getTransactionRequest();
if ((req != null) && (resp != null)) {
resp.setRequest(req);
resp.setSession(req.getSessionImpl());
}
return resp;
}
/*
* (non-Javadoc)
*
* @see com.ericsson.ssa.sip.Dispatcher#dispatch(com.ericsson.ssa.sip.SipServletResponseImpl)
*/
public void dispatch(SipServletResponseImpl resp) {
// sending a response...
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "sending response");
}
RecordRouteResolver.resolveTransport(resp);
/*try {
TargetTuple remote = null;
remote = myTargetResolver.resolveResponse(resp);
if (remote == null) {
if (logger.isLoggable(Level.INFO)) {
logger.log(Level.INFO,
"Ignoring response. Cannot resolve target for:" + resp);
return;
}
}
resp.setRemote(remote);
} catch (Exception e) {
// The only thing to do here is to ignore the response....
logger.log(Level.INFO, "Exception when resolving via header:", e);
return;
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "resolving via:\n" + remote.toString());
}*/
resp.popDispatcher().dispatch(resp);
}
public void checkRouteHeader(SipServletRequestImpl req, boolean remove) {
// loose route support: remove first route header which should refer to
// this sipstack
// TODO implement support for strict route
Header r = req.getRawHeader(Header.ROUTE);
if (r == null) {
req.setFragmentId(((URIImpl) req.getRequestURI()).getFragmentId());
// Session Case handling
req.setSessionCase(SessionCase.EXTERNAL);
// end Session Case handling
// Populate Parameters from Request URI if initial request
if (req.isInitial()) {
req.setRequestParams((URIImpl) req.getRequestURI());
}
} else {
r.setReadOnly(false);
ListIterator<Address> it_a = null;
try {
it_a = r.getAddressValues();
} catch (ServletParseException e) {
// TR HH52078
SipServletResponseImpl failureResp = req.createTerminatingResponse(400);
if (failureResp == null) {
return;
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"failed to parse top route , send 400 response");
}
failureResp.popDispatcher().dispatch(failureResp);
return;
}
Address a = it_a.next();
URIImpl uri = (URIImpl) a.getURI();
// Session Case handling
// TODO move constants
final String CALL_PARAM = "call";
final String CALL_PARAM_ORIG = "orig";
final String CALL_PARAM_TERMINATING = "term_registered";
final String CALL_PARAM_TERMINATING_UNREG = "term_unregistered";
String callParam = uri.getParameter(CALL_PARAM);
// if(callParam == null) callParam = uri.getParameter(CALL_PARAM);
if (callParam == null) {
req.setSessionCase(SessionCase.EXTERNAL);
} else {
if (callParam.equals(CALL_PARAM_ORIG)) {
req.setSessionCase(SessionCase.ORIGINATING);
} else if (callParam.equals(CALL_PARAM_TERMINATING)) {
req.setSessionCase(SessionCase.TERMINATING);
} else if (callParam.equals(CALL_PARAM_TERMINATING_UNREG)) {
req.setSessionCase(SessionCase.TERMINATING_UNREGISTERED);
} else {
req.setSessionCase(SessionCase.EXTERNAL);
logger.log(Level.WARNING,
"Assigned SessionCase external due to unknown value of " +
CALL_PARAM + " :" + callParam);
}
}
// end Session Case handling
if (uri.getLrParam()) {
// Before removing the Route header extract the fragment-id.
// This code needs BASIC TESTING in an extended
// spiraling scenario, such as
//
// UAC/EAS --> proxy/external --> proxy/EAS --> UAS/external
//
// logger.log(Level.FINE, "+++ Route header with ;lr seen in resolver");
// logger.log(Level.FINE, "+++ try setting frag info from the Route header
// URI");
// logger.log(Level.FINE, "+++ URI: "+(SipURIImpl)a.getURI());
// logger.log(Level.FINE, "+++ and the id is:
// "+((SipURIImpl)a.getURI()).getFragmentId());
// logger.log(Level.FINE, "+++ let's set it in the request then");
req.setFragmentId(((URIImpl) a.getURI()).getFragmentId());
if (remove) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Removing route = " + a);
}
it_a.remove();
// support of popped route added.
req.setInitialPoppedRoute(a);
req.setRecordRouteURI(uri);
}
} else {
throw new IllegalAccessError(
"we do not support strict routes yet");
}
r.setReadOnly(true);
}
}
private String getRetryAfterValue(String retry_after) {
if (retry_after == null) {
return null;
}
int stopIndex = 0;
int length = retry_after.length();
// Fixed defect HG89704 by checking length before doing charAt.
while ((stopIndex < length) &&
Character.isDigit(retry_after.charAt(stopIndex)))
stopIndex++;
return retry_after.substring(0, stopIndex);
}
@Configuration(key = "enum.top.domain", usage=UsagePolicy.IGNORE)
public void setEnumTopDomain(String topDomain) {
myDnsResolver.setEnumTopDomain(topDomain);
}
public String getEnumTopDomain() {
return myDnsResolver.getEnumTopDomain();
}
/**
* Returns whether the functionality described in RFC3263 4.3 Details of RFC
* 2782 Process is disabled.
*
* @return
*/
public Boolean isEas503Disabled() {
return _eas_503_disabled;
}
/**
* Sets whether the functionality described in RFC3263 4.3 Details of RFC
* 2782 Process should be disabled.
*
* @param responseEnabled
*/
@Configuration(key = "Eas503Disabled", node = "/SipService/SipProtocol", usage=UsagePolicy.IGNORE)
public void setEas503Disabled(Boolean disabled) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"Eas503Disabled property is set to " + disabled);
}
_eas_503_disabled = disabled;
}
public synchronized void start() {
/*defaultTCPTransport = Boolean.getBoolean(
"sip.resolver.defaultTCPTransport");*/
if (defaultTCPTransport) {
logger.warning(
"TCP default instead of UDP as in rfc3261, this option is for jsr116 TCK compatibility only.");
}
}
/* Sets DNS cache size
* Read from jvm-options like other dns properties
*/
@Configuration(key = "dns.cache.size")
public void setDnsCacheSize(int newSize)
{
myDnsResolver.setDnsCacheSize(newSize);
}
}