/*
* JBoss, Home of Professional Open Source
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.mobicents.tools.sip.balancer;
import gov.nist.javax.sip.ServerTransactionExt;
import java.text.ParseException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sip.ClientTransaction;
import javax.sip.DialogTerminatedEvent;
import javax.sip.IOExceptionEvent;
import javax.sip.InvalidArgumentException;
import javax.sip.ListeningPoint;
import javax.sip.PeerUnavailableException;
import javax.sip.RequestEvent;
import javax.sip.ResponseEvent;
import javax.sip.ServerTransaction;
import javax.sip.SipException;
import javax.sip.SipFactory;
import javax.sip.SipListener;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import javax.sip.TimeoutEvent;
import javax.sip.Transaction;
import javax.sip.TransactionAlreadyExistsException;
import javax.sip.TransactionTerminatedEvent;
import javax.sip.TransactionUnavailableException;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.CSeqHeader;
import javax.sip.header.CallIdHeader;
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.Message;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
/**
* A transaction stateful UDP Forwarder that listens at a port and forwards to multiple
* outbound addresses. It keeps a timer thread around that pings the list of
* proxy servers and sends to the first proxy server.
*
* It uses double record routing to be able to listen on one transport and sends on another transport
* or allows support for multihoming.
*
* @author M. Ranganathan
* @author baranowb
* @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A>
*/
public class SIPBalancerForwarder implements SipListener {
private static final Logger logger = Logger.getLogger(SIPBalancerForwarder.class
.getCanonicalName());
protected static final HashSet<String> dialogCreationMethods=new HashSet<String>(2);
static{
dialogCreationMethods.add(Request.INVITE);
dialogCreationMethods.add(Request.SUBSCRIBE);
}
/*
* Those parameters is to indicate to the SIP Load Balancer, from which node comes from the request
* so that it can stick the Call Id to this node and correctly route the subsequent requests.
*/
public static final String ROUTE_PARAM_NODE_HOST = "node_host";
public static final String ROUTE_PARAM_NODE_PORT = "node_port";
protected SipProvider internalSipProvider;
protected SipProvider externalSipProvider;
protected String myHost;
protected int myPort;
protected int myExternalPort;
protected static AddressFactory addressFactory;
protected static HeaderFactory headerFactory;
protected static MessageFactory messageFactory;
protected SipStack sipStack;
protected NodeRegister register;
protected Properties properties;
protected RecordRouteHeader internalRecordRouteHeader;
protected RecordRouteHeader externalRecordRouteHeader;
protected AtomicLong requestsProcessed = new AtomicLong(0);
protected AtomicLong responsesProcessed = new AtomicLong(0);
public SIPBalancerForwarder(Properties properties, NodeRegister register) throws IllegalStateException{
super();
this.properties = properties;
this.register=register;
}
public void start() {
SipFactory sipFactory = null;
sipStack = null;
this.myHost = properties.getProperty("host");
this.myPort = Integer.parseInt(properties.getProperty("internalPort"));
this.myExternalPort = Integer.parseInt(properties.getProperty("externalPort"));
try {
// Create SipStack object
sipFactory = SipFactory.getInstance();
sipFactory.setPathName("gov.nist");
sipStack = sipFactory.createSipStack(properties);
} catch (PeerUnavailableException pue) {
// could not find
// gov.nist.jain.protocol.ip.sip.SipStackImpl
// in the classpath
throw new IllegalStateException("Cant create stack due to["+pue.getMessage()+"]", pue);
}
try {
headerFactory = sipFactory.createHeaderFactory();
addressFactory = sipFactory.createAddressFactory();
messageFactory = sipFactory.createMessageFactory();
ListeningPoint internalLp = sipStack.createListeningPoint(myHost, myPort, ListeningPoint.UDP);
internalSipProvider = sipStack.createSipProvider(internalLp);
internalSipProvider.addSipListener(this);
ListeningPoint externalLp = sipStack.createListeningPoint(myHost, myExternalPort, ListeningPoint.UDP);
externalSipProvider = sipStack.createSipProvider(externalLp);
externalSipProvider.addSipListener(this);
//Creating the Record Route headers on startup since they can't be changed at runtime and this will avoid the overhead of creating them
//for each request
// Record route the invite so the bye comes to me. FIXME: Add check, on reINVITE we wont add ourselvses twice
SipURI sipUri = addressFactory
.createSipURI(null, internalLp.getIPAddress());
sipUri.setPort(internalLp.getPort());
//See RFC 3261 19.1.1 for lr parameter
sipUri.setLrParam();
Address address = addressFactory.createAddress(sipUri);
address.setURI(sipUri);
internalRecordRouteHeader = headerFactory
.createRecordRouteHeader(address);
//We need to use double record (better option than record route rewriting) routing otherwise it is impossible :
//a) to forward BYE from the callee side to the caller
//b) to support different transports
SipURI sendingSipUri = addressFactory
.createSipURI(null, externalLp.getIPAddress());
sendingSipUri.setPort(externalLp.getPort());
//See RFC 3261 19.1.1 for lr parameter
sendingSipUri.setLrParam();
Address sendingAddress = addressFactory.createAddress(sendingSipUri);
sendingAddress.setURI(sendingSipUri);
if(logger.isLoggable(Level.FINEST)) {
logger.finest("adding Record Router Header :"+sendingAddress);
}
externalRecordRouteHeader = headerFactory
.createRecordRouteHeader(sendingAddress);
sipStack.start();
} catch (Exception ex) {
throw new IllegalStateException("Can't create sip objects and lps due to["+ex.getMessage()+"]", ex);
}
if(logger.isLoggable(Level.INFO)) {
logger.info("Sip Balancer started on address " + myHost + ", external port : " + myExternalPort + ", port : "+ myPort);
}
}
public void stop() {
Iterator<SipProvider> sipProviderIterator = sipStack.getSipProviders();
try{
while (sipProviderIterator.hasNext()) {
SipProvider sipProvider = sipProviderIterator.next();
ListeningPoint[] listeningPoints = sipProvider.getListeningPoints();
for (ListeningPoint listeningPoint : listeningPoints) {
if(logger.isLoggable(Level.INFO)) {
logger.info("Removing the following Listening Point " + listeningPoint);
}
sipProvider.removeListeningPoint(listeningPoint);
sipStack.deleteListeningPoint(listeningPoint);
}
if(logger.isLoggable(Level.INFO)) {
logger.info("Removing the sip provider");
}
sipProvider.removeSipListener(this);
sipStack.deleteSipProvider(sipProvider);
}
} catch (Exception e) {
throw new IllegalStateException("Cant remove the listening points or sip providers", e);
}
sipStack.stop();
sipStack = null;
if(logger.isLoggable(Level.INFO)) {
logger.info("Sip Balancer stopped");
}
}
public void processDialogTerminated(
DialogTerminatedEvent dialogTerminatedEvent) {
// We wont see those
register.unStickSessionFromNode(dialogTerminatedEvent.getDialog().getCallId().getCallId());
}
public void processIOException(IOExceptionEvent exceptionEvent) {
// Hopefully we wont see those either
}
/*
* (non-Javadoc)
* @see javax.sip.SipListener#processRequest(javax.sip.RequestEvent)
*/
public void processRequest(RequestEvent requestEvent) {
// This will be invoked only by external endpoint
SipProvider sipProvider = (SipProvider) requestEvent.getSource();
Request originalRequest = requestEvent.getRequest();
ServerTransaction serverTransaction = requestEvent.getServerTransaction();
Request request = (Request) originalRequest.clone();
String requestMethod = request.getMethod();
if(logger.isLoggable(Level.FINEST)) {
logger.finest("transaction " + serverTransaction);
logger.finest("dialog " + requestEvent.getDialog());
}
try {
if(!Request.ACK.equals(requestMethod) && serverTransaction == null) {
serverTransaction = sipProvider.getNewServerTransaction(originalRequest);
}
//send a Trying to stop retransmissions
if(Request.INVITE.equals(requestMethod)) {
Response tryingResponse = messageFactory.createResponse
(Response.TRYING,request);
serverTransaction.sendResponse(tryingResponse);
}
updateStats(request);
String callID = ((CallIdHeader) request.getHeader(CallIdHeader.NAME)).getCallId();
// we check if the callID is already linked to a node if it is it means that the request is a subsequent request
// reINVITE, etc...
if (dialogCreationMethods.contains(requestMethod) && register.getGluedNode(callID) == null) {
processDialogCreatingRequest(sipProvider,
originalRequest, serverTransaction, request);
} else {
processNonDialogCreatingRequest(sipProvider,
originalRequest, serverTransaction, request);
}
} catch (TransactionAlreadyExistsException taex ) {
// Already processed this request so just return.
return;
} catch (Throwable throwable) {
logger.log(Level.SEVERE, "Unexpected exception while forwarding the request " + request, throwable);
if(!Request.ACK.equalsIgnoreCase(requestMethod)) {
try {
Response response=messageFactory.createResponse(Response.SERVER_INTERNAL_ERROR,originalRequest);
if (serverTransaction !=null) {
serverTransaction.sendResponse(response);
} else {
if (sipProvider.equals(this.externalSipProvider)) {
externalSipProvider.sendResponse(response);
} else {
internalSipProvider.sendResponse(response);
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Unexpected exception while trying to send the error response for this " + request, e);
}
}
}
}
private void updateStats(Message message) {
if(message instanceof Request) {
requestsProcessed.incrementAndGet();
} else {
responsesProcessed.incrementAndGet();
}
}
/**
* @param serverTransaction
* @param request
* @throws ParseException
* @throws InvalidArgumentException
* @throws TransactionUnavailableException
* @throws SipException
*/
private void processNonDialogCreatingRequest(SipProvider sipProvider, Request originalRequest,
ServerTransaction serverTransaction, Request request) throws ParseException, InvalidArgumentException,
TransactionUnavailableException, SipException {
String requestMethod = request.getMethod();
boolean isAck = false;
// CANCEL is hop by hop, so replying to the CANCEL by generating a 200 OK and sending a CANCEL
if (Request.CANCEL.equals(requestMethod)) {
processCancel(sipProvider, originalRequest, serverTransaction);
return;
}
if(logger.isLoggable(Level.FINEST)) {
logger.finest("got NON dialog creating request:\n " + request);
}
decreaseMaxForwardsHeader(sipProvider, request);
SIPNode sipNode = removeRouteHeadersMeantForLB(request);
if (!Request.ACK.equals(requestMethod)) {
// Check if the node is still alive for subsequent requests
RouteHeader routeHeader = (RouteHeader) request.getHeader(RouteHeader.NAME);
Map<String, String> parameters = null;
boolean isSIPNodePresent = true;
String callID = ((CallIdHeader) request.getHeader(CallIdHeader.NAME)).getCallId();
if(routeHeader != null ) {
SipURI route = ((SipURI)routeHeader.getAddress().getURI());
isSIPNodePresent = register.isSIPNodePresent(route.getHost(), route.getPort(), route.getTransportParam());
if(!isSIPNodePresent) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("node " + route + " is not alive anymore, picking another one ");
}
parameters = new HashMap<String, String>();
Iterator<String> routeParametersIt = route.getParameterNames();
while(routeParametersIt.hasNext()) {
String routeParameterName = routeParametersIt.next();
String routeParameterValue = route.getParameter(routeParameterName);
parameters.put(routeParameterName, routeParameterValue);
}
register.unStickSessionFromNode(callID);
request.removeFirst(RouteHeader.NAME);
addRouteToNode(originalRequest, serverTransaction, request, parameters);
}
} else {
SIPNode node = register.getGluedNode(callID);
// checking if the gleued node is still alive, if not we pick a new node
if(node == null || !register.isSIPNodePresent(node.getIp(), node.getPort(), node.getTransports()[0])) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("node " + node + " is not alive anymore, picking another one ");
}
register.unStickSessionFromNode(callID);
node = register.stickSessionToNode(callID, null);
}
//we change the request uri only if the request is coming from the external side
if(sipProvider.equals(this.externalSipProvider)) {
if(node != null) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("request coming from external, setting the request URI to the one of the node " + node);
}
SipURI requestURI = (SipURI)request.getRequestURI();
requestURI.setHost(node.getIp());
requestURI.setPort(node.getPort());
requestURI.setTransportParam(node.getTransports()[0]);
} else {
//No node present yet to forward the request to, thus sending 500 final error response
Response response = messageFactory.createResponse
(Response.SERVER_INTERNAL_ERROR,originalRequest);
serverTransaction.sendResponse(response);
return;
}
}
}
} else {
isAck = true;
}
// BYE coming from the callee by example
ViaHeader viaHeader = headerFactory.createViaHeader(
this.myHost, this.myPort, ListeningPoint.UDP, null);
// Add the via header to the top of the header list.
request.addHeader(viaHeader);
if(logger.isLoggable(Level.FINEST)) {
logger.finest("ViaHeader added " + viaHeader);
}
SipProvider sendingSipProvider = externalSipProvider;
if(sipProvider.equals(this.externalSipProvider)) {
sendingSipProvider = internalSipProvider;
}
if(logger.isLoggable(Level.FINEST)) {
logger.finest("sending the request:\n" + request + "\n on the other side");
}
if(isAck) {
sendingSipProvider.sendRequest(request);
} else {
ClientTransaction ctx = sendingSipProvider.getNewClientTransaction(request);
serverTransaction.setApplicationData(ctx);
ctx.setApplicationData(serverTransaction);
ctx.sendRequest();
}
}
/**
* @param requestEvent
* @param sipProvider
* @param originalRequest
* @param serverTransaction
* @param request
* @throws ParseException
* @throws InvalidArgumentException
* @throws SipException
* @throws TransactionUnavailableException
*/
private void processDialogCreatingRequest(
SipProvider sipProvider, Request originalRequest,
ServerTransaction serverTransaction, Request request)
throws ParseException, InvalidArgumentException, SipException,
TransactionUnavailableException {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("got dialog creating request:\n"+request);
}
decreaseMaxForwardsHeader(sipProvider, request);
addLBRecordRoute(sipProvider, request);
SIPNode sipNode = removeRouteHeadersMeantForLB(request);
if(sipNode == null) {
//if the sip node is null it means the request comes from external
//thus we need to add a route header
if(logger.isLoggable(Level.FINEST)) {
logger.finest("request is for UAS or proxy case");
}
addRouteToNode(originalRequest, serverTransaction, request, null);
} else {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("request is coming from UAC");
}
//if the sip node is not null it means the request comes from internal
//so we stick the node to the call id but don't add a route
String callID = ((CallIdHeader) request.getHeader(CallIdHeader.NAME)).getCallId();
SIPNode node = register.stickSessionToNode(callID, sipNode);
if(node == null) {
//No node present yet to forward the request to, thus sending 500 final error response
Response response = messageFactory.createResponse
(Response.SERVER_INTERNAL_ERROR,originalRequest);
serverTransaction.sendResponse(response);
return;
}
}
// Add the via header to the top of the header list.
ViaHeader viaHeader = headerFactory.createViaHeader(
this.myHost, this.myPort, ListeningPoint.UDP, null);
request.addHeader(viaHeader);
if(logger.isLoggable(Level.FINEST)) {
logger.finest("ViaHeader added " + viaHeader);
}
SipProvider sendingSipProvider = internalSipProvider;
if(sipProvider.equals(this.internalSipProvider)) {
sendingSipProvider = externalSipProvider;
}
if(logger.isLoggable(Level.FINEST)) {
logger.finest("sending the request:\n" + request + "\n on the other side");
}
//sending request
if(Request.ACK.equalsIgnoreCase(request.getMethod())) {
sendingSipProvider.sendRequest(request);
} else {
ClientTransaction ctx = sendingSipProvider.getNewClientTransaction(request);
serverTransaction.setApplicationData(ctx);
ctx.setApplicationData(serverTransaction);
ctx.sendRequest();
}
}
/**
* @param sipProvider
* @param request
* @throws ParseException
*/
private void addLBRecordRoute(SipProvider sipProvider, Request request)
throws ParseException {
if(sipProvider.equals(externalSipProvider)) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("adding Record Router Header :" + externalRecordRouteHeader);
}
request.addHeader(externalRecordRouteHeader);
if(logger.isLoggable(Level.FINEST)) {
logger.finest("adding Record Router Header :" + internalRecordRouteHeader);
}
request.addHeader(internalRecordRouteHeader);
} else {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("adding Record Router Header :" + internalRecordRouteHeader);
}
request.addHeader(internalRecordRouteHeader);
if(logger.isLoggable(Level.FINEST)) {
logger.finest("adding Record Router Header :" + externalRecordRouteHeader);
}
request.addHeader(externalRecordRouteHeader);
}
}
/**
* @param originalRequest
* @param serverTransaction
* @param request
* @param parameters
* @throws ParseException
* @throws SipException
* @throws InvalidArgumentException
*/
private void addRouteToNode(Request originalRequest,
ServerTransaction serverTransaction, Request request, Map<String, String> parameters)
throws ParseException, SipException, InvalidArgumentException {
String callID = ((CallIdHeader) request.getHeader(CallIdHeader.NAME)).getCallId();
SIPNode node = register.stickSessionToNode(callID, null);
if(node != null) {
//Adding Route Header pointing to the node the sip balancer wants to forward to
SipURI routeSipUri = addressFactory
.createSipURI(null, node.getIp());
routeSipUri.setPort(node.getPort());
routeSipUri.setLrParam();
if(parameters != null) {
Set<Entry<String, String>> routeParameters= parameters.entrySet();
for (Entry<String, String> entry : routeParameters) {
routeSipUri.setParameter(entry.getKey(), entry.getValue());
}
}
RouteHeader route = headerFactory.createRouteHeader(addressFactory.createAddress(routeSipUri));
request.addFirst(route);
} else {
//No node present yet to forward the request to, thus sending 500 final error response
Response response = messageFactory.createResponse
(Response.SERVER_INTERNAL_ERROR,originalRequest);
serverTransaction.sendResponse(response);
}
}
/**
* Remove the different route headers that are meant for the Load balancer.
* There is two cases here :
* <ul>
* <li>* Requests coming from external and going to the cluster : dialog creating requests can have route header so that they go through the LB and subsequent requests
* will have route headers since the LB record routed</li>
* <li>* Requests coming from the cluster and going to external : dialog creating requests can have route header so that they go through the LB - those requests will define in the route header
* the originating node of the request so that that subsequent requests are routed to the originating node if still alive</li>
* </ul>
*
* @param request
*/
private SIPNode removeRouteHeadersMeantForLB(Request request) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("Checking if there is any route headers meant for the LB to remove...");
}
SIPNode node = null;
//Removing first routeHeader if it is for the sip balancer
RouteHeader routeHeader = (RouteHeader) request.getHeader(RouteHeader.NAME);
if(routeHeader != null) {
SipURI routeUri = (SipURI)routeHeader.getAddress().getURI();
//FIXME check against a list of host we may have too
if(!isRouteHeaderExternal(routeUri.getHost(), routeUri.getPort())) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("this route header is for the LB removing it " + routeUri);
}
request.removeFirst(RouteHeader.NAME);
node = checkRouteHeaderForSipNode(routeUri);
//since we used double record routing we may have 2 routes corresponding to us here
// for ACK and BYE from caller for example
routeHeader = (RouteHeader) request.getHeader(RouteHeader.NAME);
if(routeHeader != null) {
routeUri = (SipURI)routeHeader.getAddress().getURI();
//FIXME check against a list of host we may have too
if(!isRouteHeaderExternal(routeUri.getHost(), routeUri.getPort())) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("this route header is for the LB removing it " + routeUri);
}
request.removeFirst(RouteHeader.NAME);
if(node == null) {
node = checkRouteHeaderForSipNode(routeUri);
}
}
}
}
}
if(node !=null) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("Following node information has been found in one of the route Headers " + node);
}
}
return node;
}
/**
* Check if the sip uri is meant for the LB same host and same port
* @param sipUri sip Uri to check
* @return
*/
private boolean isRouteHeaderExternal(String host, int port) {
//FIXME check against a list of host we may have too
if(host.equalsIgnoreCase(myHost) && (port == myExternalPort || port == myPort)) {
return false;
}
return true;
}
/**
* This will check if in the route header there is information on which node from the cluster send the request.
* If the request is not received from the cluster, this information will not be present.
* @param routeHeader the route header to check
* @return the corresponding Sip Node
*/
private SIPNode checkRouteHeaderForSipNode(SipURI routeSipUri) {
SIPNode node = null;
String hostNode = routeSipUri.getParameter(ROUTE_PARAM_NODE_HOST);
String hostPort = routeSipUri.getParameter(ROUTE_PARAM_NODE_PORT);
if(hostNode != null && hostPort != null) {
int port = Integer.parseInt(hostPort);
node = register.getNode(hostNode, port, routeSipUri.getTransportParam());
}
return node;
}
/**
* @param sipProvider
* @param request
* @throws InvalidArgumentException
* @throws ParseException
* @throws SipException
*/
private void decreaseMaxForwardsHeader(SipProvider sipProvider,
Request request) throws InvalidArgumentException, ParseException,
SipException {
// Decreasing the Max Forward Header
if(logger.isLoggable(Level.FINEST)) {
logger.finest("Decreasing the Max Forward Header ");
}
MaxForwardsHeader maxForwardsHeader = (MaxForwardsHeader) request.getHeader(MaxForwardsHeader.NAME);
if (maxForwardsHeader == null) {
maxForwardsHeader = headerFactory.createMaxForwardsHeader(70);
request.addHeader(maxForwardsHeader);
} else {
if(maxForwardsHeader.getMaxForwards() - 1 > 0) {
maxForwardsHeader.setMaxForwards(maxForwardsHeader.getMaxForwards() - 1);
} else {
//Max forward header equals to 0, thus sending too many hops response
Response response = messageFactory.createResponse
(Response.TOO_MANY_HOPS,request);
sipProvider.sendResponse(response);
}
}
}
/**
* @param originalRequest
* @param serverTransaction
* @throws ParseException
* @throws SipException
* @throws InvalidArgumentException
* @throws TransactionUnavailableException
*/
private void processCancel(SipProvider sipProvider, Request originalRequest, ServerTransaction serverTransaction) throws ParseException,
SipException, InvalidArgumentException,
TransactionUnavailableException {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("process Cancel " + originalRequest);
}
Transaction inviteTransaction = ((ServerTransactionExt) serverTransaction).getCanceledInviteTransaction();
String callID = ((CallIdHeader) originalRequest.getHeader(CallIdHeader.NAME)).getCallId();
SIPNode node = register.getGluedNode(callID);
if (node == null || !register.isSIPNodePresent(node.getIp(), node.getPort(), node.getTransports()[0])) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("Previous node has failed. New node is " + node);
}
node = register.getNextNode();
}
if(node != null) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("Using node " + node);
}
Response response = messageFactory.createResponse
(Response.OK,originalRequest);
serverTransaction.sendResponse(response);
//
ClientTransaction ctx = (ClientTransaction)inviteTransaction.getApplicationData();
Request cancelRequest = ctx.createCancel();
// cancelRequest.addHeader(viaHeader);
cancelRequest.removeFirst(RouteHeader.NAME);
SipURI routeSipUri = addressFactory
.createSipURI(null, node.getIp());
routeSipUri.setPort(node.getPort());
routeSipUri.setLrParam();
RouteHeader route = headerFactory.createRouteHeader(addressFactory.createAddress(routeSipUri));
cancelRequest.addFirst(route);
SipProvider sendingSipProvider = internalSipProvider;
if(sipProvider.equals(internalSipProvider)) {
sendingSipProvider = externalSipProvider;
}
// Create a new transaction just to add the Via Header
ClientTransaction cancelClientTransaction = sendingSipProvider
.getNewClientTransaction(cancelRequest);
// And send statelessly because tx-stateful doesnt respect the Route header
sendingSipProvider.sendRequest(
cancelRequest);
} else {
//No node present yet to forward the request to, thus sending 500 final error response
logger.severe("No node present yet to forward the request " + originalRequest);
Response response = messageFactory.createResponse
(Response.SERVER_INTERNAL_ERROR,originalRequest);
serverTransaction.sendResponse(response);
}
}
/*
* (non-Javadoc)
* @see javax.sip.SipListener#processResponse(javax.sip.ResponseEvent)
*/
public void processResponse(ResponseEvent responseEvent) {
SipProvider sipProvider = (SipProvider) responseEvent.getSource();
Response originalResponse = responseEvent.getResponse();
ClientTransaction clientTransaction = responseEvent.getClientTransaction();
if(logger.isLoggable(Level.FINEST)) {
logger.finest("got response :\n" + originalResponse);
}
//stateful proxy must not forward 100 Trying
if (originalResponse.getStatusCode() == 100) {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("dropping 100 response");
}
return;
}
//we drop retransmissions since the proxy tx will retransmit for us
// wrong see Issue 805 http://code.google.com/p/mobicents/issues/detail?id=805
// if(clientTransaction == null) {
// if(logger.isLoggable(Level.FINEST)) {
// logger.finest("dropping retransmissions");
// }
// return;
// }
updateStats(originalResponse);
if(!Request.CANCEL.equalsIgnoreCase(((CSeqHeader)originalResponse.getHeader(CSeqHeader.NAME)).getMethod())) {
Response response = (Response) originalResponse.clone();
// Topmost via header is me. As it is reponse to external request
ViaHeader viaHeader = (ViaHeader) response.getHeader(ViaHeader.NAME);
//FIXME check against a list of host we may have too
if(!isRouteHeaderExternal(viaHeader.getHost(), viaHeader.getPort())) {
response.removeFirst(ViaHeader.NAME);
}
// if (sipProvider == this.internalSipProvider) {
// if(logger.isLoggable(Level.FINEST)) {
// logger.finest("GOT RESPONSE INTERNAL:\n"+response);
// }
// //Register will be cleaned in the processXXXTerminated jsip callback
// //Here if we get response other than 100-2xx we have to clean register from this session
//// if(dialogCreationMethods.contains(method) && !(100<=status && status<300)) {
//// register.unStickSessionFromNode(callID);
//// }
// } else {
// //Topmost via header is proxy, we leave it
// //This happens as proxy sets RR to external interface, but it sets Via to itself.
// if(logger.isLoggable(Level.FINEST)) {
// logger.finest("GOT RESPONSE INTERNAL, FOR UAS REQ:\n"+response);
// }
// //Register will be cleaned in the processXXXTerminated jsip callback
// //Here we should care only for BYE, all other are send without any change
// //We dont even bother status, as BYE means that UAS wants to terminate.
//// if(method.equals(Request.BYE)) {
//// register.unStickSessionFromNode(callID);
//// }
// }
try {
if(clientTransaction != null) {
// non retransmission case
ServerTransaction serverTransaction = (ServerTransaction)clientTransaction.getApplicationData();
serverTransaction.sendResponse(response);
} else {
// retransmission case : we forward on the other sip provider
if(sipProvider.equals(externalSipProvider)) {
internalSipProvider.sendResponse(response);
} else {
externalSipProvider.sendResponse(response);
}
}
} catch (Exception ex) {
logger.log(Level.SEVERE, "Unexpected exception while forwarding the response " + response +
" (transaction=" + clientTransaction + " / dialog=" + responseEvent.getDialog() + "", ex);
}
} else {
if(logger.isLoggable(Level.FINEST)) {
logger.finest("dropping CANCEL responses, snce it hop by hop");
}
}
}
/*
* (non-Javadoc)
* @see javax.sip.SipListener#processTimeout(javax.sip.TimeoutEvent)
*/
public void processTimeout(TimeoutEvent timeoutEvent) {
Transaction transaction = null;
if(timeoutEvent.isServerTransaction()) {
transaction = timeoutEvent.getServerTransaction();
if(logger.isLoggable(Level.FINEST)) {
logger.finest("timeout => " + transaction.getRequest().toString());
}
} else {
transaction = timeoutEvent.getClientTransaction();
if(logger.isLoggable(Level.FINEST)) {
logger.finest("timeout => " + transaction.getRequest().toString());
}
}
String callId = ((CallIdHeader)transaction.getRequest().getHeader(CallIdHeader.NAME)).getCallId();
register.unStickSessionFromNode(callId);
}
/*
* (non-Javadoc)
* @see javax.sip.SipListener#processTransactionTerminated(javax.sip.TransactionTerminatedEvent)
*/
public void processTransactionTerminated(
TransactionTerminatedEvent transactionTerminatedEvent) {
Transaction transaction = null;
if(transactionTerminatedEvent.isServerTransaction()) {
transaction = transactionTerminatedEvent.getServerTransaction();
if(logger.isLoggable(Level.FINEST)) {
logger.finest("timeout => " + transaction.getRequest().toString());
}
} else {
transaction = transactionTerminatedEvent.getClientTransaction();
if(logger.isLoggable(Level.FINEST)) {
logger.finest("timeout => " + transaction.getRequest().toString());
}
}
if(Request.BYE.equals(transaction.getRequest().getMethod())) {
String callId = ((CallIdHeader)transaction.getRequest().getHeader(CallIdHeader.NAME)).getCallId();
register.unStickSessionFromNode(callId);
}
}
/**
* @return the requestsProcessed
*/
public long getNumberOfRequestsProcessed() {
return requestsProcessed.get();
}
/**
* @return the requestsProcessed
*/
public long getNumberOfResponsesProcessed() {
return responsesProcessed.get();
}
}