/* * 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.servlet.sip.proxy; import gov.nist.javax.sip.header.Via; import java.io.IOException; import java.io.Serializable; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import javax.servlet.sip.Proxy; import javax.servlet.sip.ProxyBranch; import javax.servlet.sip.SipServletRequest; import javax.servlet.sip.SipServletResponse; import javax.servlet.sip.SipURI; import javax.servlet.sip.URI; import javax.sip.SipException; import javax.sip.SipProvider; import javax.sip.header.ContactHeader; import javax.sip.header.Header; import javax.sip.header.RecordRouteHeader; import javax.sip.header.ViaHeader; import javax.sip.message.Request; import javax.sip.message.Response; import org.apache.log4j.Logger; import org.mobicents.servlet.sip.JainSipUtils; import org.mobicents.servlet.sip.address.SipURIImpl; import org.mobicents.servlet.sip.address.TelURLImpl; import org.mobicents.servlet.sip.core.session.MobicentsSipSession; import org.mobicents.servlet.sip.message.SipFactoryImpl; import org.mobicents.servlet.sip.message.SipServletRequestImpl; import org.mobicents.servlet.sip.message.SipServletResponseImpl; /** * @author root * */ public class ProxyImpl implements Proxy, Serializable { private static final long serialVersionUID = 1L; private static transient Logger logger = Logger.getLogger(ProxyImpl.class); private transient SipServletRequestImpl originalRequest; private transient SipServletResponseImpl bestResponse; private transient ProxyBranchImpl bestBranch; private boolean recurse = true; private int proxyTimeout; private int seqSearchTimeout; private boolean supervised = true; private boolean recordRoutingEnabled; private boolean parallel = true; private boolean addToPath; protected transient SipURI pathURI; protected transient SipURI recordRouteURI; private transient SipURI outboundInterface; private transient SipFactoryImpl sipFactoryImpl; private boolean isNoCancel; // clustering : will be recreated when loaded from the cache private transient ProxyUtils proxyUtils; private transient Map<URI, ProxyBranch> proxyBranches; private boolean started; private boolean ackReceived = false; private boolean tryingSent = false; // This branch is the final branch (set when the final response has been sent upstream by the proxy) // that will be used for proxying subsequent requests private ProxyBranchImpl finalBranchForSubsequentRequests; // Keep the URI of the previous SIP entity that sent the original request to us (either another proxy or UA) private SipURI previousNode; // The From-header of the initiator of the request. Used to determine the direction of the request. // Caller -> Callee or Caller <- Callee private String callerFromHeader; public ProxyImpl(SipServletRequestImpl request, SipFactoryImpl sipFactoryImpl) { this.originalRequest = request; this.sipFactoryImpl = sipFactoryImpl; this.proxyBranches = new LinkedHashMap<URI, ProxyBranch> (); this.proxyUtils = new ProxyUtils(sipFactoryImpl, this); this.proxyTimeout = 180; // 180 secs default this.outboundInterface = ((MobicentsSipSession)request.getSession()).getOutboundInterface(); this.callerFromHeader = request.getFrom().toString(); this.previousNode = extractPreviousNodeFromRequest(request); } /* * This method will find the address of the machine that is the previous dialog path node. * If there are proxies before the current one that are adding Record-Route we should visit them, * otherwise just send to the client directly. And we don't want to visit proxies that are not * Record-Routing, because they are not in the dialog path. */ private SipURI extractPreviousNodeFromRequest(SipServletRequestImpl request) { SipURI uri = null; try { // First check for record route RecordRouteHeader rrh = (RecordRouteHeader) request.getMessage().getHeader(RecordRouteHeader.NAME); if(rrh != null) { javax.sip.address.SipURI sipUri = (javax.sip.address.SipURI) rrh.getAddress().getURI(); uri = new SipURIImpl(sipUri); } else { // If no record route is found then use the last via (the originating endpoint) ListIterator<ViaHeader> viaHeaders = request.getMessage().getHeaders(ViaHeader.NAME); ViaHeader lastVia = null; while(viaHeaders.hasNext()) { lastVia = viaHeaders.next(); } String uriString = ((Via)lastVia).getSentBy().toString(); uri = sipFactoryImpl.createSipURI(null, uriString); if(lastVia.getTransport() != null) { uri.setTransportParam(lastVia.getTransport()); } else { uri.setTransportParam("udp"); } } } catch (Exception e) { // We shouldn't completely fail in this case because it is rare to visit this code logger.error("Failed parsing previous address ", e); } return uri; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#cancel() */ public void cancel() { cancelAllExcept(null, null, null, null, true); } /* * (non-Javadoc) * @see javax.servlet.sip.Proxy#cancel(java.lang.String[], int[], java.lang.String[]) */ public void cancel(String[] protocol, int[] reasonCode, String[] reasonText) { cancelAllExcept(null, protocol, reasonCode, reasonText, true); } public void cancelAllExcept(ProxyBranch except, String[] protocol, int[] reasonCode, String[] reasonText, boolean throwExceptionIfCannotCancel) { if(ackReceived) throw new IllegalStateException("There has been an ACK received on this branch. Can not cancel."); for(ProxyBranch proxyBranch : proxyBranches.values()) { if(!proxyBranch.equals(except)) { try { proxyBranch.cancel(protocol, reasonCode, reasonText); } catch (IllegalStateException e) { // TODO: Instead of catching excpetions here just determine if the branch is cancellable if(throwExceptionIfCannotCancel) throw e; } } } } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#createProxyBranches(java.util.List) */ public List<ProxyBranch> createProxyBranches(List<? extends URI> targets) { ArrayList<ProxyBranch> list = new ArrayList<ProxyBranch>(); for(URI target: targets) { if(target == null) throw new NullPointerException("URI can't be null"); if(!JainSipUtils.checkScheme(target.toString())) { throw new IllegalArgumentException("Scheme " + target.getScheme() + " is not supported"); } ProxyBranchImpl branch = new ProxyBranchImpl(target, this); branch.setRecordRoute(recordRoutingEnabled); branch.setRecurse(recurse); list.add(branch); this.proxyBranches.put(target, branch); } return list; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getAddToPath() */ public boolean getAddToPath() { return addToPath; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getOriginalRequest() */ public SipServletRequest getOriginalRequest() { return originalRequest; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getParallel() */ public boolean getParallel() { return this.parallel; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getPathURI() */ public SipURI getPathURI() { if(!this.addToPath) throw new IllegalStateException("You must setAddToPath(true) before getting URI"); return this.pathURI; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getProxyBranch(javax.servlet.sip.URI) */ public ProxyBranch getProxyBranch(URI uri) { return this.proxyBranches.get(uri); } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getProxyBranches() */ public List<ProxyBranch> getProxyBranches() { return new ArrayList<ProxyBranch>(this.proxyBranches.values()); } /** * @return the finalBranchForSubsequentRequest */ public ProxyBranchImpl getFinalBranchForSubsequentRequests() { return finalBranchForSubsequentRequests; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getProxyTimeout() */ public int getProxyTimeout() { return this.proxyTimeout; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getRecordRoute() */ public boolean getRecordRoute() { return this.recordRoutingEnabled; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getRecordRouteURI() */ public SipURI getRecordRouteURI() { if(!this.recordRoutingEnabled) throw new IllegalStateException("You must setRecordRoute(true) before getting URI"); return this.recordRouteURI; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getRecurse() */ public boolean getRecurse() { return this.recurse; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getSequentialSearchTimeout() */ public int getSequentialSearchTimeout() { return this.seqSearchTimeout; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getStateful() */ public boolean getStateful() { return true; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#getSupervised() */ public boolean getSupervised() { return this.supervised; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#proxyTo(java.util.List) */ public void proxyTo(List<? extends URI> uris) { for (URI uri : uris) { if(uri == null) throw new NullPointerException("URI can't be null"); if(!JainSipUtils.checkScheme(uri.toString())) { throw new IllegalArgumentException("Scheme " + uri.getScheme() + " is not supported"); } ProxyBranchImpl branch = new ProxyBranchImpl((SipURI) uri, this); branch.setRecordRoute(recordRoutingEnabled); branch.setRecurse(recurse); this.proxyBranches.put(uri, branch); } startProxy(); } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#proxyTo(javax.servlet.sip.URI) */ public void proxyTo(URI uri) { if(uri == null) throw new NullPointerException("URI can't be null"); if(!JainSipUtils.checkScheme(uri.toString())) { throw new IllegalArgumentException("Scheme " + uri.getScheme() + " is not supported"); } ProxyBranchImpl branch = new ProxyBranchImpl(uri, this); branch.setRecordRoute(recordRoutingEnabled); branch.setRecurse(recurse); this.proxyBranches.put(uri, branch); startProxy(); } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#setAddToPath(boolean) */ public void setAddToPath(boolean p) { if(started) { throw new IllegalStateException("Cannot set a record route on an already started proxy"); } if(this.pathURI == null) { this.pathURI = new SipURIImpl ( JainSipUtils.createRecordRouteURI( sipFactoryImpl.getSipNetworkInterfaceManager(), null)); } addToPath = p; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#setParallel(boolean) */ public void setParallel(boolean parallel) { this.parallel = parallel; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#setProxyTimeout(int) */ public void setProxyTimeout(int seconds) { if(seconds<=0) throw new IllegalArgumentException("Negative or zero timeout not allowed"); proxyTimeout = seconds; for(ProxyBranch proxyBranch : proxyBranches.values()) { boolean inactive = ((ProxyBranchImpl)proxyBranch).isCanceled() || ((ProxyBranchImpl)proxyBranch).isTimedOut(); if(!inactive) { proxyBranch.setProxyBranchTimeout(seconds); } } } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#setRecordRoute(boolean) */ public void setRecordRoute(boolean rr) { if(started) { throw new IllegalStateException("Cannot set a record route on an already started proxy"); } if(rr) { this.recordRouteURI = new SipURIImpl ( JainSipUtils.createRecordRouteURI( sipFactoryImpl.getSipNetworkInterfaceManager(), null)); if(logger.isDebugEnabled()) { logger.debug("Record routing enabled for proxy, Record Route used will be : " + recordRouteURI.toString()); } } this.recordRoutingEnabled = rr; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#setRecurse(boolean) */ public void setRecurse(boolean recurse) { this.recurse = recurse; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#setSequentialSearchTimeout(int) */ public void setSequentialSearchTimeout(int seconds) { seqSearchTimeout = seconds; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#setStateful(boolean) */ public void setStateful(boolean stateful) { //NOTHING } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#setSupervised(boolean) */ public void setSupervised(boolean supervised) { this.supervised = supervised; } /* (non-Javadoc) * @see javax.servlet.sip.Proxy#startProxy() */ public void startProxy() { if(this.ackReceived) throw new IllegalStateException("Can't start. ACK has been received."); if(!this.originalRequest.isInitial()) throw new IllegalStateException("Applications should not attepmt to " + "proxy subsequent requests. Proxying the initial request is " + "sufficient to carry all subsequent requests through the same" + " path."); // Only send TRYING when the request is INVITE, needed by testProxyGen2xx form TCK (it sends MESSAGE) if(this.originalRequest.getMethod().equals(Request.INVITE) && !tryingSent) { // Send provisional TRYING. Chapter 10.2 // We must send only one TRYING no matter how many branches we spawn later. // This is needed for tests like testProxyBranchRecurse tryingSent = true; logger.info("Sending 100 Trying to the source"); SipServletResponse trying = originalRequest.createResponse(100); try { trying.send(); } catch (IOException e) { logger.error("Cannot send the 100 Trying",e); } } started = true; if(this.parallel) { for (ProxyBranch pb : this.proxyBranches.values()) { if(!((ProxyBranchImpl)pb).isStarted()) ((ProxyBranchImpl)pb).start(); } } else { startNextUntriedBranch(); } } public SipURI getOutboundInterface() { return outboundInterface; } public void onFinalResponse(ProxyBranchImpl branch) { //Get the final response SipServletResponseImpl response = (SipServletResponseImpl) branch.getResponse(); // Cancel all others if 2xx or 6xx 10.2.4 and it's not a retransmission if(!isNoCancel && response.getTransaction() != null) { if(this.getParallel()) { if( (response.getStatus() >= 200 && response.getStatus() < 300) || (response.getStatus() >= 600 && response.getStatus() < 700) ) { cancelAllExcept(branch, null, null, null, false); } } } // Recurse if allowed if(response.getStatus() >= 300 && response.getStatus() < 400 && getRecurse()) { // We may want to store these for "moved permanently" and others ListIterator<Header> headers = response.getMessage().getHeaders(ContactHeader.NAME); while(headers.hasNext()) { ContactHeader contactHeader = (ContactHeader) headers.next(); javax.sip.address.URI addressURI = contactHeader.getAddress().getURI(); URI contactURI = null; if (addressURI instanceof javax.sip.address.SipURI) { contactURI = new SipURIImpl( (javax.sip.address.SipURI) addressURI); } else if (addressURI instanceof javax.sip.address.TelURL) { contactURI = new TelURLImpl( (javax.sip.address.TelURL) addressURI); } ProxyBranchImpl recurseBranch = new ProxyBranchImpl(contactURI, this); recurseBranch.setRecordRoute(recordRoutingEnabled); recurseBranch.setRecurse(recurse); this.proxyBranches.put(contactURI, recurseBranch); branch.addRecursedBranch(branch); if(parallel) recurseBranch.start(); // if not parallel, just adding it to the list is enough } } // Sort best do far if(bestResponse == null || bestResponse.getStatus() > response.getStatus()) { //Assume 600 and 400 are equally bad, the better one is the one that came first (TCK doBranchBranchTest) if(bestResponse != null) { if(response.getStatus()<400) { bestResponse = response; bestBranch = branch; } } else { bestResponse = response; bestBranch = branch; } } // Check if we are waiting for more response if(parallel && allResponsesHaveArrived()) { finalBranchForSubsequentRequests = bestBranch; sendFinalResponse(bestResponse, bestBranch); } if(!parallel) { if(bestResponse.getStatus()>=200 && bestResponse.getStatus()<300) { finalBranchForSubsequentRequests = bestBranch; sendFinalResponse(bestResponse, bestBranch); } else { if(allResponsesHaveArrived()) { sendFinalResponse(bestResponse, bestBranch); } else { startNextUntriedBranch(); } } } } public void onBranchTimeOut(ProxyBranchImpl branch) { if(this.bestBranch == null) this.bestBranch = branch; if(allResponsesHaveArrived()) { sendFinalResponse(bestResponse, bestBranch); } else { if(!parallel) { branch.cancel(); startNextUntriedBranch(); } } } // In sequential proxying get some untried branch and start it, then wait for response and repeat public void startNextUntriedBranch() { if(this.parallel) throw new IllegalStateException("This method is only for sequantial proxying"); for(ProxyBranch pb: this.proxyBranches.values()) { ProxyBranchImpl pbi = (ProxyBranchImpl) pb; if(!pbi.isStarted()) { pbi.start(); return; } } } public boolean allResponsesHaveArrived() { for(ProxyBranch pb: this.proxyBranches.values()) { ProxyBranchImpl pbi = (ProxyBranchImpl) pb; SipServletResponse response = pb.getResponse(); // The unstarted branches still haven't got a chance to get response if(!pbi.isStarted()) { return false; } if(pbi.isStarted() && !pbi.isTimedOut() && !pbi.isCanceled()) { if(response == null || // if there is no response yet response.getStatus() < Response.OK) { // or if the response if not final return false; // then we should wait more } } } return true; } public void sendFinalResponse(SipServletResponseImpl response, ProxyBranchImpl proxyBranch) { // If we didn't get any response and only a timeout just return a timeout if(proxyBranch.isTimedOut()) { try { originalRequest.createResponse(Response.REQUEST_TIMEOUT).send(); return; } catch (IOException e) { throw new IllegalStateException("Failed to send a timeout response", e); } } //Otherwise proceed with proxying the response SipServletResponseImpl proxiedResponse = getProxyUtils().createProxiedResponse(response, proxyBranch); if(proxiedResponse == null) { return; // this response was addressed to this proxy } if(proxiedResponse.getTransaction() != null) { // non retransmission case try { proxiedResponse.send(); proxyBranches = new LinkedHashMap<URI, ProxyBranch> (); originalRequest = null; bestBranch = null; bestResponse = null; } catch (Exception e) { logger.error("A problem occured while proxying the final response", e); } } else { // retransmission case, RFC3261 specifies that the retrans should be proxied statelessly String transport = JainSipUtils.findTransport(proxiedResponse.getMessage()); SipProvider sipProvider = getSipFactoryImpl().getSipNetworkInterfaceManager().findMatchingListeningPoint( transport, false).getSipProvider(); try { sipProvider.sendResponse((Response)proxiedResponse.getMessage()); } catch (SipException e) { logger.error("A problem occured while proxying the final response retransmission", e); } } } ProxyUtils getProxyUtils() { if(proxyUtils == null) { proxyUtils = new ProxyUtils(sipFactoryImpl, this); } return proxyUtils; } /** * @return the bestResponse */ public SipServletResponseImpl getBestResponse() { return bestResponse; } public void setOriginalRequest(SipServletRequestImpl originalRequest) { this.originalRequest = originalRequest; } /** * {@inheritDoc} */ public boolean getNoCancel() { return isNoCancel; } /** * {@inheritDoc} */ public void setNoCancel(boolean isNoCancel) { this.isNoCancel = isNoCancel; } /** * @return the sipFactoryImpl */ public SipFactoryImpl getSipFactoryImpl() { return sipFactoryImpl; } /** * @param sipFactoryImpl the sipFactoryImpl to set */ public void setSipFactoryImpl(SipFactoryImpl sipFactoryImpl) { this.sipFactoryImpl = sipFactoryImpl; } /** * {@inheritDoc} */ public void setOutboundInterface(InetAddress inetAddress) { String address = inetAddress.getHostAddress(); List<SipURI> list = this.sipFactoryImpl.getSipNetworkInterfaceManager().getOutboundInterfaces(); SipURI networkInterface = null; for(SipURI networkInterfaceURI:list) { if(networkInterfaceURI.toString().contains(address)) { networkInterface = networkInterfaceURI; } } if(networkInterface == null) throw new IllegalArgumentException("Network interface for " + inetAddress.getHostAddress() + " not found"); outboundInterface = networkInterface; } /** * {@inheritDoc} */ public void setOutboundInterface(InetSocketAddress inetSocketAddress) { String address = inetSocketAddress.getAddress().getHostAddress() + ":" + inetSocketAddress.getPort(); List<SipURI> list = this.sipFactoryImpl.getSipNetworkInterfaceManager().getOutboundInterfaces(); SipURI networkInterface = null; for(SipURI networkInterfaceURI:list) { if(networkInterfaceURI.toString().contains(address)) { networkInterface = networkInterfaceURI; } } if(networkInterface == null) throw new IllegalArgumentException("Network interface for " + address + " not found"); outboundInterface = networkInterface; } public void setAckReceived(boolean received) { this.ackReceived = received; } public boolean getAckReceived() { return this.ackReceived; } public SipURI getPreviousNode() { return previousNode; } public String getCallerFromHeader() { return callerFromHeader; } public void setCallerFromHeader(String initiatorFromHeader) { this.callerFromHeader = initiatorFromHeader; } }