package org.mobicents.slee.services.sip.proxy;
import gov.nist.javax.sip.address.SipUri;
import java.text.ParseException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.management.ObjectName;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sip.ClientTransaction;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipProvider;
import javax.sip.TimeoutEvent;
import javax.sip.TransactionState;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.HeaderFactory;
import javax.sip.header.MaxForwardsHeader;
import javax.sip.header.RecordRouteHeader;
import javax.sip.header.RouteHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import javax.slee.ActivityContextInterface;
import javax.slee.ActivityEndEvent;
import javax.slee.ChildRelation;
import javax.slee.CreateException;
import javax.slee.InitialEventSelector;
import javax.slee.RolledBackContext;
import javax.slee.SLEEException;
import javax.slee.Sbb;
import javax.slee.SbbContext;
import javax.slee.SbbLocalObject;
import javax.slee.TransactionRequiredLocalException;
import javax.slee.UnrecognizedActivityException;
import javax.slee.serviceactivity.ServiceActivity;
import javax.slee.serviceactivity.ServiceActivityFactory;
import net.java.slee.resource.sip.CancelRequestEvent;
import net.java.slee.resource.sip.SipActivityContextInterfaceFactory;
import net.java.slee.resource.sip.SleeSipProvider;
import org.apache.log4j.Logger;
import org.mobicents.slee.container.SleeContainer;
import org.mobicents.slee.services.sip.common.MessageHandlerInterface;
import org.mobicents.slee.services.sip.common.MessageUtils;
import org.mobicents.slee.services.sip.common.ProxyConfiguration;
import org.mobicents.slee.services.sip.common.SipLoopDetectedException;
import org.mobicents.slee.services.sip.common.SipSendErrorResponseException;
import org.mobicents.slee.services.sip.location.LocationSbbLocalObject;
import org.mobicents.slee.services.sip.location.LocationService;
import org.mobicents.slee.services.sip.location.LocationServiceException;
import org.mobicents.slee.services.sip.location.RegistrationBinding;
import org.mobicents.slee.services.sip.proxy.mbean.ProxyConfigurator;
public abstract class ProxySbb implements Sbb {
private static Logger logger = Logger.getLogger(ProxySbb.class);
/**
* Proxy Configuration MBean
*/
private final static ProxyConfigurator proxyConfigurator = new ProxyConfigurator();
private SbbContext sbbContext;
private Context myEnv;
private SleeSipProvider provider=null;
private AddressFactory addressFactory;
private HeaderFactory headerFactory;
private MessageFactory messageFactory;
private SipActivityContextInterfaceFactory acif;
/**
* Generate a custom convergence name so that events with the same call
* identifier will go to the same root SBB entity.
*/
public InitialEventSelector callIDSelect(InitialEventSelector ies) {
Object event = ies.getEvent();
String callId = null;
if (event instanceof ResponseEvent) {
ies.setInitialEvent(false);
return ies;
} else if (event instanceof RequestEvent) {
// If request event, the convergence name to callId
Request request = ((RequestEvent) event).getRequest();
if (!request.getMethod().equals(Request.ACK)) {
callId = ((ViaHeader) request.getHeaders(ViaHeader.NAME).next())
.getBranch();
} else {
callId = ((ViaHeader) request.getHeaders(ViaHeader.NAME).next())
.getBranch()
+ "_ACK";
}
}
// Set the convergence name
if (logger.isDebugEnabled()) {
logger.debug( "Setting convergence name to: " + callId);
}
ies.setCustomName(callId);
return ies;
}
// ****************************** ABSTRACT PARTS ********************
// **** SLEE Children
public abstract ChildRelation getRegistrarSbbChildRelation();
public abstract ChildRelation getLocationSbbChildRelation();
public LocationSbbLocalObject getLocationSbb() throws TransactionRequiredLocalException, SLEEException, CreateException {
final ChildRelation childRelation = getLocationSbbChildRelation();
if (childRelation.isEmpty()) {
return (LocationSbbLocalObject) childRelation.create();
}
else {
return (LocationSbbLocalObject) childRelation.iterator().next();
}
}
// ***** Custom ACI
public abstract ProxySbbActivityContextInterface asSbbActivityContextInterface(
ActivityContextInterface ac);
// ***** CMPs
public abstract void setConfiguration(ProxyConfigurator pc);
public abstract ProxyConfigurator getConfiguration();
/**
* This flag tells the SBB that the transaction has been terminated, and
* that any further messages on this transaction (eg. ACKs after the proxy
* returns 404 NOT_FOUND) should be ignored and not forwarded.
*/
public abstract boolean getServerTransactionTerminated();
public abstract void setServerTransactionTerminated(
boolean transactionTerminated);
// This is required for cancels and acks, those need the same via as
// invites?
public abstract void setForwardedInviteViaHeader(ViaHeader via);
public abstract ViaHeader getForwardedInviteViaHeader();
// ***************************** SERVICE (DE)ACTIVATION
public void onServiceStarted(
javax.slee.serviceactivity.ServiceStartedEvent serviceEvent,
ActivityContextInterface aci) {
try {
// check if it's my service that is starting
ServiceActivity sa = ((ServiceActivityFactory) myEnv
.lookup("java:comp/env/slee/serviceactivity/factory")).getActivity();
if (sa.equals(aci.getActivity())) {
startMBeanConfigurator();
}
else {
aci.detach(this.sbbContext.getSbbLocalObject());
}
} catch (Exception e) {
logger.error(e);
}
}
private void startMBeanConfigurator() {
proxyConfigurator.setSipHostName(provider.getListeningPoints()[0].getIPAddress());
proxyConfigurator.setSipPort(provider.getListeningPoints()[0].getPort());
proxyConfigurator.setSipTransports(new String[]{provider.getListeningPoints()[0].getTransport()});
String confValue = null;
Context myEnv = null;
try {
logger.info("Building Configuration from ENV Entries");
myEnv = (Context) new InitialContext().lookup("java:comp/env");
} catch (NamingException ne) {
logger.warn("Could not set SBB context:" + ne.getMessage());
return;
}
// env-entries
try {
confValue = (String) myEnv.lookup("configuration-URI-SCHEMES");
} catch (NamingException e) {
logger.error(e);
}
if (confValue == null) {
proxyConfigurator.addSupportedURIScheme("sip");
proxyConfigurator.addSupportedURIScheme("sips");
} else {
String[] tmp = confValue.split(";");
for (int i = 0; i < tmp.length; i++)
proxyConfigurator.addSupportedURIScheme(tmp[i]);
}
confValue = null;
try {
confValue = (String) myEnv.lookup("configuration-LOCAL-DOMAINS");
} catch (NamingException e) {
logger.error(e);
}
if (confValue == null) {
// Domains should be present in conf file, it shouldnt do much harm
// to add those
proxyConfigurator.addLocalDomain("nist.gov");
proxyConfigurator.addLocalDomain("mobicents.org");
} else {
String[] tmp = confValue.split(";");
for (int i = 0; i < tmp.length; i++)
proxyConfigurator.addLocalDomain(tmp[i]);
}
String configurationName=null;
try {
configurationName = (String) myEnv
.lookup("configuration-MBEAN");
} catch (NamingException e) {
logger.error(e);
}
if(configurationName!=null)
proxyConfigurator.setName(configurationName);
// GO ;] start service
proxyConfigurator.startService();
}
public void onActivityEndEvent(ActivityEndEvent event,
ActivityContextInterface aci) {
try {
if (aci.getActivity() instanceof ServiceActivity) {
if (logger.isDebugEnabled()) {
logger.debug("Service aci ending, removing mbean");
}
// lets remove our mbean
SleeContainer.lookupFromJndi().getMBeanServer()
.unregisterMBean(
new ObjectName(
ProxyConfiguration.MBEAN_NAME_PREFIX
+ proxyConfigurator.getName()));
}
} catch (Exception e) {
logger.error(e);
}
}
// ****************** EVENT HANLDERS
public void onRegisterEvent(RequestEvent event, ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received REGISTER request, class="+ event.getClass());
// TODO: IF NOT LOCAL DOMAIN THEN PROXY REQUEST
// ELSE this is local domain
// attach child to this activity
try {
ac.attach(getRegistrarSbbChildRelation().create());
} catch (Exception e) {
// failed to attach the register, send error back
logger.error(e);
// TODO send 500 back
}
// detach myself
ac.detach(sbbContext.getSbbLocalObject());
}
public void onInviteEvent(RequestEvent event, ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received INVITE request");
processRequest(event.getServerTransaction(), event.getRequest(), ac);
}
public void onByeEvent(RequestEvent event, ActivityContextInterface ac) {
// getDefaultSbbUsageParameterSet().incrementNumberOfBye(1);
if (logger.isDebugEnabled())
logger.debug("Received BYE request");
processRequest(event.getServerTransaction(), event.getRequest(), ac);
}
public void onCancelEvent(CancelRequestEvent event, ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received CANCEL request");
final ServerTransaction serverTransaction = event.getServerTransaction();
try {
// CANCELs are hop-by-hop, so here we respond immediately on the
// server txn, if the RA didn't do it
if ((serverTransaction.getState() != TransactionState.TERMINATED)
&& (serverTransaction.getState() != TransactionState.COMPLETED)
&& (serverTransaction.getState() != TransactionState.CONFIRMED)) {
serverTransaction.sendResponse(messageFactory.createResponse(
Response.OK, event.getRequest()));
}
} catch (Exception e) {
logger.warn( "Failed to reply to CANCEL", e);
}
// This will generate a new CANCEL request that originates from the
// proxy
processRequest(serverTransaction, event.getRequest(), ac);
}
public void onAckEvent(RequestEvent event, ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received ACK request");
processRequest(event.getServerTransaction(), event.getRequest(), ac);
}
public void onMessageEvent(RequestEvent event, ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received MESSAGE request");
processRequest(event.getServerTransaction(), event.getRequest(), ac);
}
public void onOptionsEvent(RequestEvent event, ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received OPTIONS request");
try {
Request request = event.getRequest();
ServerTransaction serverTransaction = event.getServerTransaction();
ProxyConfiguration proxyConfiguration = (ProxyConfiguration) getConfiguration();
// check if it's for me, in that case reply 501
SipUri localNodeURI = new SipUri();
localNodeURI.setHost(proxyConfiguration.getSipHostname());
localNodeURI.setPort(proxyConfiguration.getSipPort());
if (request.getRequestURI().equals(localNodeURI)) {
if (request.getHeader(MaxForwardsHeader.NAME) == null) {
request
.addHeader(headerFactory
.createMaxForwardsHeader(69));
}
serverTransaction.sendResponse(messageFactory.createResponse(
Response.NOT_IMPLEMENTED, request));
} else {
processRequest(serverTransaction, request, ac);
}
} catch (Exception e) {
logger.warn( "Exception during onOptionsEvent", e);
}
}
public void onInfoRespEvent(ResponseEvent event, ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received 1xx (FINER) response");
processResponse(event.getClientTransaction(), event.getResponse(), ac);
}
public void onSuccessRespEvent(ResponseEvent event,
ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received 2xx (SUCCESS) response");
processResponse(event.getClientTransaction(), event.getResponse(), ac);
}
public void onRedirRespEvent(ResponseEvent event,
ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received 3xx (REDIRECT) response");
processResponse(event.getClientTransaction(), event.getResponse(), ac);
}
public void onClientErrorRespEvent(ResponseEvent event,
ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received 4xx (CLIENT ERROR) response");
processResponse(event.getClientTransaction(), event.getResponse(), ac);
}
public void onServerErrorRespEvent(ResponseEvent event,
ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received 5xx (SERVER ERROR) response");
processResponse(event.getClientTransaction(), event.getResponse(), ac);
}
public void onGlobalFailureRespEvent(ResponseEvent event,
ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received 6xx (GLOBAL FAILURE) response");
processResponse(event.getClientTransaction(), event.getResponse(), ac);
}
/*
* Timeouts
*/
public void onTransactionTimeoutEvent(TimeoutEvent event,
ActivityContextInterface ac) {
if (logger.isDebugEnabled())
logger.debug("Received transaction timeout event, tid="
+ event.getClientTransaction());
ServerTransaction serverTransaction = event.getServerTransaction();
if (serverTransaction != null) {
try {
serverTransaction.sendResponse(messageFactory.createResponse(
Response.REQUEST_TIMEOUT, serverTransaction
.getRequest()));
setServerTransactionTerminated(true);
} catch (Exception e) {
logger.error(e);
}
}
}
public ServerTransaction getServerTransaction(
ClientTransaction clientTransaction) {
ActivityContextInterface myacis[] = sbbContext.getActivities();
for (int i = 0; i < myacis.length; i++) {
Object activity = myacis[i].getActivity();
if (activity instanceof ServerTransaction) {
ServerTransaction stx = (ServerTransaction) activity;
Request req = stx.getRequest();
if (!req.getMethod().equals(Request.CANCEL)
&& req.getMethod().equals(clientTransaction.getRequest().getMethod()))
return stx;
}
}
return null;
}
// ***** SENDER METHODS
public ClientTransaction sendRequest(Request request, boolean attach)
throws SipException {
if (request.getHeader(MaxForwardsHeader.NAME) == null) {
try {
request.addHeader(headerFactory.createMaxForwardsHeader(69));
} catch (Exception e) {
logger.error(e);
}
}
ClientTransaction ct = provider.getNewClientTransaction(request);
if (attach) {
try {
ActivityContextInterface aci = acif.getActivityContextInterface(ct);
aci.attach(sbbContext.getSbbLocalObject());
} catch (UnrecognizedActivityException e) {
logger.warn("unable to attach to client transaction", e);
}
}
ct.sendRequest();
return ct;
}
public void sendStatelessRequest(Request request) throws SipException {
provider.sendRequest(request);
}
public void sendStatelessResponse(Response response) throws SipException {
provider.sendResponse(response);
}
// ** PROXY MESSAGE
private void processRequest(ServerTransaction serverTransaction,
Request request, ActivityContextInterface ac) {
ac.detach(sbbContext.getSbbLocalObject());
if (logger.isInfoEnabled())
logger.info("processing request: method = \n"
+ request.getMethod().toString());
// log.error("===> REQUEST METHOD["+request.getMethod()+"]
// CALLID["+((CallID)request.getHeader(CallID.NAME)).getCallId()+"]
// TO["+((ToHeader)request.getHeader(ToHeader.NAME)).getAddress()+"]
// BRANCH["+serverTransaction.getBranchId()+"]");
try {
if (getServerTransactionTerminated()) {
if (logger.isDebugEnabled())
logger.debug("[PROXY MACHINE] txTERM \n" + request);
return;
}
// if (getServerTX() == null)
// setServerTX(serverTransaction);
// Go - if it is invite here, serverTransaction can be CANCEL
// transaction!!!! so we dont want to overwrite it above
new ProxyMachine(getProxyConfigurator(), getLocationSbb(),
this.addressFactory, this.headerFactory,
this.messageFactory, this.provider)
.processRequest(serverTransaction, request);
} catch (Exception e) {
// Send error response so client can deal with it
logger.warn( "Exception during processRequest", e);
try {
serverTransaction.sendResponse(messageFactory.createResponse(
Response.SERVER_INTERNAL_ERROR, request));
} catch (Exception ex) {
logger.warn( "Exception during processRequest", e);
}
}
}
private void processResponse(ClientTransaction clientTransaction,
Response response, ActivityContextInterface ac) {
ac.detach(sbbContext.getSbbLocalObject());
if (logger.isInfoEnabled())
logger.info("processing response: status = \n"
+ response.getStatusCode());
// log.error("===> RESPONSE CODE["+response.getStatusCode()+"]
// METHOD["+((CSeq)response.getHeader(CSeq.NAME)).getMethod()+"]
// CALLID["+((CallID)response.getHeader(CallID.NAME)).getCallId()+"]
// TO["+((ToHeader)response.getHeader(ToHeader.NAME)).getAddress()+"]
// BRANCH["+clientTransaction.getBranchId()+"]");
try {
if (getServerTransactionTerminated()) {
return;
}
// Go
ServerTransaction serverTransaction = getServerTransaction(clientTransaction);
if (serverTransaction != null) {
new ProxyMachine(getProxyConfigurator(), getLocationSbb(), this.addressFactory,
this.headerFactory, this.messageFactory, this.provider)
.processResponse(serverTransaction,
clientTransaction, response);
} else {
logger.warn("Weird got null tx for[" + response + "]");
}
} catch (Exception e) {
// Send error response so client can deal with it
logger.warn( "Exception during processResponse", e);
}
}
private ProxyConfigurator getProxyConfigurator() {
ProxyConfigurator configurator = getConfiguration();
if (configurator == null) {
configurator = (ProxyConfigurator)proxyConfigurator.clone();
setConfiguration(configurator);
}
return configurator;
}
// ** STRICT RFC 3261 Proxy part
// *********** SBB SLEE METHODS
public void sbbActivate() {}
public void sbbCreate() throws CreateException {}
public void sbbExceptionThrown(Exception arg0, Object arg1,
ActivityContextInterface arg2) {}
public void sbbLoad() {}
public void sbbPassivate() {}
public void sbbPostCreate() throws CreateException {}
public void sbbRemove() {}
public void sbbRolledBack(RolledBackContext arg0) {}
public void sbbStore() {}
public void setSbbContext(SbbContext context) {
this.sbbContext = context;
try {
myEnv = new InitialContext();
provider = (SleeSipProvider) myEnv.lookup("java:comp/env/slee/resources/jainsip/1.2/provider");
messageFactory = provider.getMessageFactory();
headerFactory = provider.getHeaderFactory();
addressFactory = provider.getAddressFactory();
acif = (SipActivityContextInterfaceFactory) myEnv.lookup("java:comp/env/slee/resources/jainsip/1.2/acifactory");
} catch (NamingException ne) {
logger.error("Could not set SBB context: ", ne);
}
}
public void unsetSbbContext() { this.sbbContext = null; }
// Inner class - this is pojo, but it needs access to some SLEE stuff, its
// more conveniant to do this like this, since otherwise we would have
// to either pass whole sbb or interface
class ProxyMachine extends MessageUtils implements MessageHandlerInterface {
protected final Logger log = Logger.getLogger("ProxyMachine.class");
// We can use those variables from top level class, but let us have our
// own.
protected LocationService reg = null;
protected AddressFactory af = null;
protected HeaderFactory hf = null;
protected MessageFactory mf = null;
protected SipProvider provider = null;
protected HashSet<URI> localMachineInterfaces = new HashSet<URI>();
protected ProxyConfiguration pc = null;
protected ProxyConfiguration config = null;
public ProxyMachine(ProxyConfiguration config,
LocationService registrarAccess, AddressFactory af,
HeaderFactory hf, MessageFactory mf, SipProvider prov)
throws ParseException {
super(config);
reg = registrarAccess;
this.mf = mf;
this.af = af;
this.hf = hf;
this.provider = prov;
this.config = config;
SipUri localMachineURI = new SipUri();
localMachineURI.setHost(this.config.getSipHostname());
localMachineURI.setPort(this.config.getSipPort());
this.localMachineInterfaces.add(localMachineURI);
}
public void processRequest(ServerTransaction stx, Request req) {
if (log.isDebugEnabled()) {
log.debug("processRequest");
}
try {
Request tmpNewRequest = (Request) req.clone();
// 16.3 Request Validation
validateRequest(stx, tmpNewRequest);
// 16.4 Route Information Preprocessing
routePreProcess(tmpNewRequest);
// logger.debug("Server transaction " + stx);
// 16.5 Determining Request Targets
List targets = determineRequestTargets(tmpNewRequest);
Iterator it = targets.iterator();
while (it.hasNext()) {
Request newRequest = (Request) tmpNewRequest.clone();
URI target = (URI) it.next();
// Part of loop detection, here we will stop initial reqeust
// that makes loop in local stack
if (isLocalMachine(target)) {
continue;
}
// logger.fine("SIP Proxy Forwarding: "
// + req.getMethod() + " to URI target: " + target);
// 16.6 Request Forwarding
// 1. Copy request
// 2. Request-URI
if (target.isSipURI() && !((SipUri) target).hasLrParam())
newRequest.setRequestURI(target);
// *NEW* CANCEL processing
// CANCELs are hop-by-hop, so here must remove any existing
// Via
// headers,
// Record-Route headers. We insert Via header below so we
// will
// get response.
if (newRequest.getMethod().equals(Request.CANCEL)) {
newRequest.removeHeader(ViaHeader.NAME);
newRequest.removeHeader(RecordRouteHeader.NAME);
} else {
// 3. Max-Forwards
decrementMaxForwards(newRequest);
// 4. Record-Route
addRecordRouteHeader(newRequest);
}
// 5. Add Additional Header Fields
// TBD
// 6. Postprocess routing information
// TBD
// 7. Determine Next-Hop Address, Port and Transport
// TBD
// 8. Add a Via header field value
addViaHeader(newRequest);
// 9. Add a Content-Leangth header field if necessary
// TBD
// 10. Forward Request
ClientTransaction ctx = forwardRequest(stx, newRequest);
// 11. Set timer C
}
} catch (SipSendErrorResponseException se) {
se.printStackTrace();
int statusCode = se.getStatusCode();
sendErrorResponse(stx, req, statusCode);
} catch (SipLoopDetectedException slde) {
log.warn("Loop detected, droping message.");
slde.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public void processResponse(ServerTransaction stx,
ClientTransaction ctx, Response resp) {
// Now check if we really want to send it right away
// log.info(this.getClass().getName(), "processResponse");
try {
Response newResponse = (Response) resp.clone();
// 16.7 Response Processing
// 1. Find appropriate response context
// 2. Update timer C for provisional responses
// 3. Remove topmost via
Iterator viaHeaderIt = newResponse.getHeaders(ViaHeader.NAME);
viaHeaderIt.next();
viaHeaderIt.remove();
if (!viaHeaderIt.hasNext())
return; // response was meant for this proxy
// 4. Add the response to the response context
// 5. Check to see if this response should be forwarded
// immediately
if (newResponse.getStatusCode() == Response.TRYING) {
return;
}
// 6. When necessary, choose the best final response from the
// 7. Aggregate authorization header fields if necessary
// 8. Optionally rewrite Record-Route header field values
// 9. Forward the response
forwardResponse(stx, newResponse);
// 10. Generate any necessary CANCEL requests
} catch (Exception e) {
e.printStackTrace();
}
}
public ClientTransaction forwardRequest(
ServerTransaction serverTransaction, Request request) {
ClientTransaction toReturn = null;
if (log.isDebugEnabled())
log.debug("Forwarding request " + request.getMethod()
+ " of server tx " + serverTransaction.getBranchId());
// ProxySbb.log.error("===> REQUEST FWD
// METHOD["+request.getMethod()+"]
// CALLID["+((CallID)request.getHeader(CallID.NAME)).getCallId()+"]
// BRANCH["+serverTransaction.getBranchId()+"]");
// log.info("PRXY forwardReqeust\n"+request);
try {
if (request.getMethod().equals(Request.ACK)) {
sendStatelessRequest(request);
} else if (request.getMethod().equals(Request.CANCEL)) {
sendRequest(request, false);
} else {
ClientTransaction clientTransaction = sendRequest(request,
true);
return clientTransaction;
}
} catch (Exception e) {
// log.info( "Exception during forwardRequest-->"+ e);
e.printStackTrace();
if (!serverTransaction.getRequest().getMethod().endsWith(
Request.CANCEL)) {
// send back error if it's nt a cancel, because that one
// already got a 200 ok
sendErrorResponse(serverTransaction, serverTransaction
.getRequest(), Response.SERVER_INTERNAL_ERROR);
}
}
return toReturn;
}
public void sendErrorResponse(ServerTransaction txn, Request request,
int statusCode) {
try {
// sipaci.setTransactionTerminated(true);
setServerTransactionTerminated(true);
Response response = mf.createResponse(statusCode, request);
if (response.getHeader(MaxForwardsHeader.NAME) == null) {
response.addHeader(hf.createMaxForwardsHeader(69));
}
txn.sendResponse(response);
} catch (Exception e) {
// trace(Level.WARNING, "Exception during sendErrorResponse",
// e);
e.printStackTrace();
}
}
public void forwardResponse(ServerTransaction txn, Response response) {
if (log.isDebugEnabled())
log.debug("Forwarding response " + response.getStatusCode()
+ " of server tx " + txn.getBranchId());
// log.info("PRXY forwardResponse\n"+response);
// try{
// ProxySbb.log.error("===> RESPONSE FWD
// CODE["+response.getStatusCode()+"]
// METHOD["+((CSeq)response.getHeader(CSeq.NAME)).getMethod()+"]
// CALLID["+((CallID)response.getHeader(CallID.NAME)).getCallId()+"]
// BRANCH["+txn==null?"GO TNULL":txn.getBranchId()+"]");
// }catch(Exception e)
// {
// e.printStackTrace();
// }
try {
// trace(Level.FINEST, "Forwarding response:\n" + response);
if (txn != null) {
txn.sendResponse(response);
} else {
// forward statelessly anyway
sendStatelessResponse(response);
}
} catch (Exception e) {
log.error("Exception during forwardResponse[\n" + response
+ "\n] TXBRANCH[" + txn.getBranchId() + "] TXR[\n"
+ txn.getRequest() + "\n]:" + e);
}
}
/**
* Performs request validation as per RFC 3261 section 16.3. If a
* request fails validation, throw exception to cause appropriate error
* response to client.
*
* @param txn
* the server transaction of the request.
* @param request
* the SIP request to be validated.
* @throws SipLoopDetectedException -
* error that is beeing thrown when local stack goes into
* loop
* @throws SipSendErrorResponseException -
* thrown uri of passed message is not supported
*/
public void validateRequest(ServerTransaction txn, Request request)
throws SipSendErrorResponseException, SipLoopDetectedException {
// 1. Reasonable syntax
// 2. URI scheme
URI requestURI = null;
requestURI = request.getRequestURI();
boolean supportedURIScheme = false;
supportedURIScheme = isSupportedURIScheme(requestURI);
if (!supportedURIScheme) {
throw new SipSendErrorResponseException(
"Unsupported URI scheme",
Response.UNSUPPORTED_URI_SCHEME);
}
// 3. Max-Forwards
checkMaxForwards(txn, request);
// 4. Loop Detection - TBD
detectLoop(request);
// 5. Proxy-Require - TBD
// 6. Proxy-Authorization - TBD
}
public void detectLoop(Request request) throws SipLoopDetectedException {
URI requestURI = null;
requestURI = request.getRequestURI();
// , now its simple, so we wont spin requests
// to local node
SipUri localNodeURI = new SipUri();
try {
localNodeURI.setHost(this.config.getSipHostname());
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
localNodeURI.setPort(this.config.getSipPort());
// This only works if UAC conforms to rfc 3261
if (requestURI.equals(localNodeURI)) {
// throw new SipSendErrorResponseException("Possible local
// looping on node",Response.LOOP_DETECTED);
// throw new SipLoopDetectedException(
// "Possible loop detected on LOCAL["+localNodeURI+"]
// MSG["+requestURI+"] message:n" + request
// + "\n====================================");
// this can be a loop, we will only warn as this is uncertain at
// this point
// if You know more, please patch :]
if (log.isDebugEnabled()) {
log.debug("Possible loop detected on LOCAL[" + localNodeURI
+ "] MSG[" + requestURI + "] message:n" + request
+ "\n====================================");
}
}
// do more:
ListIterator lit = request.getHeaders(ViaHeader.NAME);
if (lit != null && lit.hasNext()) {
int found = 0;
do {
ViaHeader vh = (ViaHeader) lit.next();
if (vh.getHost().equals(localNodeURI.getHost())
&& vh.getPort() == localNodeURI.getPort()) {
found++;
}
if (found >= 2) {
throw new SipLoopDetectedException(
"Possible loop detected[mutliple via headers] on message:n"
+ request
+ "\n====================================");
}
} while (lit.hasNext());
}
}
/**
* Validate the max-forwards header throw a user error exception (too
* many hops) if max-forwards reaches 0.
*
* @param txn
* @param request
* @throws SipSendErrorResponseException
*/
public void checkMaxForwards(ServerTransaction txn, Request request)
throws SipSendErrorResponseException {
MaxForwardsHeader mfh = (MaxForwardsHeader) request
.getHeader(MaxForwardsHeader.NAME);
if (mfh == null)
return;
int maxForwards = 0;
maxForwards = ((MaxForwardsHeader) request
.getHeader(MaxForwardsHeader.NAME)).getMaxForwards();
if (maxForwards > 0) {
return;
} else {
// MAY respond to OPTIONS, otherwise return 483 Too Many Hops
throw new SipSendErrorResponseException("Too many hops",
Response.TOO_MANY_HOPS);
}
}
/**
* Attempts to find a locally registered contact address for the given
* URI, using the location service interface.
*/
public LinkedList<URI> findLocalTarget(URI uri)
throws SipSendErrorResponseException {
String addressOfRecord = uri.toString();
Map<String, RegistrationBinding> bindings = null;
LinkedList<URI> listOfTargets = new LinkedList<URI>();
try {
bindings = reg.getBindings(addressOfRecord);
} catch (LocationServiceException e) {
e.printStackTrace();
return listOfTargets;
}
if (bindings == null) {
throw new SipSendErrorResponseException("User not found",
Response.NOT_FOUND);
}
if (bindings.isEmpty()) {
throw new SipSendErrorResponseException(
"User temporarily unavailable",
Response.TEMPORARILY_UNAVAILABLE);
}
Iterator it = bindings.values().iterator();
URI target = null;
while (it.hasNext()) {
String contactAddress = ((RegistrationBinding)it.next()).getContactAddress();
try {
listOfTargets.add(af.createURI(contactAddress));
} catch (ParseException e) {
log.warn("Ignoring contact address "+contactAddress+" due to parse error",e);
}
}
if (listOfTargets.size() == 0) {
// logger.fine("findLocalTarget: No contacts for "
// + addressOfRecord + " found.");
throw new SipSendErrorResponseException(
"User temporarily unavailable",
Response.TEMPORARILY_UNAVAILABLE);
}
return listOfTargets;
}
/**
* Adds a default Via header to the request. Override to provide a
* different Via header.
*/
public void addViaHeader(Request request)
throws SipSendErrorResponseException {
ViaHeader via = null;
try {
// ViaHeader via = hf.createViaHeader(config.getSipHostname(),
// config.getSipPort(), config.getSipTransport(), null);
// if (request.getMethod().equals(Request.CANCEL)
// || request.getMethod().equals(Request.ACK)) {
// For now we cant do rfc 3261 ch 17.1.1.3
if (request.getMethod().equals(Request.CANCEL)) {
via = getForwardedInviteViaHeader();
if (via == null) {
throw new SipSendErrorResponseException(
"Couldnt add via [" + via + "] to msg[\n"
+ request + "\n], didnt find forwarded via!!!", Response.BAD_REQUEST);
}
} else {
via = hf.createViaHeader(config.getSipHostname(), config
.getSipPort(), config.getSipTransports()[0],
"z9hG4bK" + System.currentTimeMillis() + "_"
+ Math.random() + "_"
+ System.currentTimeMillis());
if (request.getMethod().equals(Request.INVITE)) {
setForwardedInviteViaHeader(via);
}
}
// THIS: config.getSipTransports()[0] // has to be changed!!!
log.debug("[&&&] addViaHeader\n" + via + "");
// via.setParameter("ID",
// ""+System.currentTimeMillis()+"_"+Math.random()+"_"+config.getSipHostname()+":"+config.getSipPort());
request.addHeader(via);
} catch (Exception e) {
throw new SipSendErrorResponseException("Couldnt add via ["
+ via + "] to msg[\n" + request + "\n]",
Response.SERVER_INTERNAL_ERROR,e);
}
}
/**
* Adds a default Record-Route header to the request. Override to
* provide a different Record-Route header.
*/
public void addRecordRouteHeader(Request request) {
try {
// Add our record-route header to list...
SipURI myURI = af.createSipURI(null, config.getSipHostname());
myURI.setPort(config.getSipPort());
myURI.setMAddrParam(config.getSipHostname());
myURI.setTransportParam(config.getSipTransports()[0]);
myURI.setParameter("cluster", "mobi-cents");
myURI.setParameter("lr", "");
Address myName = af.createAddress(myURI);
RecordRouteHeader myHeader = hf.createRecordRouteHeader(myName);
request.addFirst(myHeader);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Decrement the value of max-forwards. If no max-forwards header
* present, create a max-forwards header with the RFC3261 recommended
* default value
*
* @param request
* @throws SipSendErrorResponseException
*/
public void decrementMaxForwards(Request request)
throws SipSendErrorResponseException {
MaxForwardsHeader max = (MaxForwardsHeader) request
.getHeader(MaxForwardsHeader.NAME);
try {
if (max == null) {
// add max-forwards with default 70 hops
max = hf.createMaxForwardsHeader(70);
request.setHeader(max);
} else {
// decrement max-forwards
int maxForwards = max.getMaxForwards();
maxForwards--;
max.setMaxForwards(maxForwards);
request.setHeader(max);
}
} catch (Exception e) {
throw new SipSendErrorResponseException(
"Error updating max-forwards",
Response.SERVER_INTERNAL_ERROR);
}
}
/**
* Check for strict-routing style route headers and swap with
* Request-URI if applicable.
*/
public void routePreProcess(Request request)
throws SipSendErrorResponseException {
URI requestURI = null;
requestURI = request.getRequestURI();
// TODO: add check on multiple names!!!!!
if ((requestURI.isSipURI())
&& (((SipURI) requestURI).getUser() == null)
&& (((SipURI) requestURI).getHost().equalsIgnoreCase(config
.getSipHostname()))) {
// client is a strict router, replace request-URI with last
// value in Route header field...
try {
ListIterator it = request.getHeaders(RouteHeader.NAME);
LinkedList l = new LinkedList();
// need last value in list
while (it.hasNext()) {
RouteHeader r = (RouteHeader) it.next();
l.add(r);
}
if (l.size() == 0)
return;
RouteHeader route = (RouteHeader) l.getLast();
l.removeLast(); // Remove the last route header from the
// list,
// possibly leaving an empty list
request.removeHeader(RouteHeader.NAME); // Remove all route
// headers from the
// message
// Re-add the remaining headers to the message
for (int i = 0; i < l.size(); i++) {
RouteHeader routeHeader = (RouteHeader) l.get(i);
request.addHeader(routeHeader);
}
URI newURI = route.getAddress().getURI();
request.setRequestURI(newURI);
} catch (Exception e) {
e.printStackTrace();
throw new SipSendErrorResponseException(
"Error updating route headers",
Response.SERVER_INTERNAL_ERROR);
}
}
// From RFC3261 16.4:
// If the first value in the Route header field indicates this
// proxy,
// the proxy MUST remove that value from the request.
Iterator routeHeaders = request.getHeaders(RouteHeader.NAME);
if (routeHeaders.hasNext()) {
RouteHeader r = (RouteHeader) routeHeaders.next();
// is this route header for our hostname & port?
URI uri = r.getAddress().getURI();
if (uri.isSipURI()) {
SipURI sipURI = (SipURI) uri;
int uriPort = sipURI.getPort();
if (uriPort <= 0)
uriPort = 5060; // WARNING: Assumes stack impl returns
// <= 0
// if port not specified in URI
String cluster = sipURI.getParameter("cluster");
boolean isMobicents = (cluster != null && cluster
.equals("mobi-cents"));
// JAIN SIP does not specify but NIST SIP returns -1
if (((sipURI.getHost().equalsIgnoreCase(config
.getSipHostname())) && (uriPort == config
.getSipPort()))
|| isMobicents) {
// logger.fine("Cluster = " + cluster);
// remove this route header
routeHeaders.remove();
// if this was the last one, remove the header entirely
// (why
// isn't this automatic?)
if (!routeHeaders.hasNext())
request.removeHeader(RouteHeader.NAME);
}
}
}
}
/**
* Determines target SIP URI(s) for request, using location service or
* other criteria.
*
* TODO: Forking (return more than one target)
*
* @param request
* the SIP request being forwarded
* @return a list of URIs
*/
public List determineRequestTargets(Request request)
throws SipSendErrorResponseException {
LinkedList targets = null;
URI requestURI = null;
URI target = null;
boolean localDomain = false;
requestURI = request.getRequestURI();
localDomain = isLocalDomain(requestURI);
if (request.getMethod().equals(Request.ACK)
|| request.getMethod().equals(Request.BYE)) {
RouteHeader rh = (RouteHeader) request
.getHeader(RouteHeader.NAME);
if (rh != null) {
target = rh.getAddress().getURI();
} else
target = request.getRequestURI();
targets = new LinkedList();
targets.add(target);
} else if (localDomain) {
// determine local SIP target(s) using location service etc
targets = findLocalTarget(requestURI);
// This is done in findLocalTarget
// if (target == null) { // not found (or not currently
// registered)
// throw new SipSendErrorResponseException("User not
// registered",
// Response.TEMPORARILY_UNAVAILABLE);
// }
} else {
// destination addr is outside our domain
target = requestURI;
targets = new LinkedList();
targets.add(target);
}
return targets;
}
public boolean isLocalMachine(URI hostURI) {
return this.localMachineInterfaces.contains(hostURI);
}
}
}