Package org.jvnet.glassfish.comms.clb.core.sip

Source Code of org.jvnet.glassfish.comms.clb.core.sip.SipLoadBalancerBackend

/*
* 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 org.jvnet.glassfish.comms.clb.core.sip;

import java.util.ListIterator;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipServletRequest;

import org.jvnet.glassfish.comms.clb.core.CLBRuntimeException;
import org.jvnet.glassfish.comms.util.LogUtil;

import com.ericsson.ssa.sip.AddressImpl;
import com.ericsson.ssa.sip.Header;
import com.ericsson.ssa.sip.SipServletMessageImpl;
import com.ericsson.ssa.sip.SipServletRequestImpl;
import com.ericsson.ssa.sip.SipServletResponseImpl;
import com.ericsson.ssa.sip.URIImpl;
import com.ericsson.ssa.sip.ViaImpl;

import com.ericsson.ssa.sip.dns.TargetTuple;
import com.ericsson.ssa.sip.dns.SipTransports;

import com.ericsson.ssa.sip.dns.TargetResolver;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import java.util.ArrayList;
import java.util.List;

import org.jvnet.glassfish.comms.clb.core.CLBConstants;
import org.jvnet.glassfish.comms.clb.core.ProxyKeyExtractor;
import org.jvnet.glassfish.comms.clb.core.util.LoadbalancerUtil;

/**
* This class implements the SIP load balancer routing logic. It implements both
* the front-end logic (incoming requests and responses) and back-end logic
* (both incoming and outgoing requests and responses)
*/
public class SipLoadBalancerBackend {

    private static Logger logger = LogUtil.CLB_LOGGER.getLogger();
    private String instanceID;
    private Socket localAddress;
    private SipClientToClbFEMapper sipClientToClbFEMapper;
   
    /**
     * Creates an instance and associates it with the specified hash key
     * extractor and server instance lookup.
     *
     * @param instanceID the ID of the current instance
     */
    public SipLoadBalancerBackend(String instanceID, Socket localAddress)
            throws CLBRuntimeException {
        this.instanceID = instanceID;
        this.localAddress = localAddress;
        sipClientToClbFEMapper = SipClientToClbFEMapper.getInstance();
    }

    /**
     * Handle a request that as been received by this instance. It shall either
     * be served by this instance, or be proxied to another instance.
     *
     * @param req the request; the request may be modified (headers added or
     *                changed)
     */
    public void handleIncomingRequest(SipServletRequestImpl req) throws SipRoutingException {
        if (logger.isLoggable(Level.FINER)) {
            logger.log(Level.FINER, "Handle incoming request");
        }

        pushBackendLBVia(req);

        sipClientToClbFEMapper.removeFEAddressMapping(req);
       
        if (req.getBeKey() == null) {
            // The SipLoadBalancerIncomingHandler was not present (this is a
            // pure backend). Extract BEKey that was extracted by front-end
            String beKey = ProxyKeyExtractor.extractHashKey(req);

            if (logger.isLoggable(Level.FINER)) {
                logger.log(Level.FINER, "Extracted BE key: " + beKey);
            }

            if (beKey != null) {
                req.setBeKey(beKey);
            }
        }

        saveIncomingConnection(req);
        decodeClientCert(req);
        decodeRemote(req);
        //If request is UDP, there is no need to save incoming connection
        //response can directly go back to client
        if(req.getRemote().getProtocol() == SipTransports.UDP_PROT
                && LoadbalancerUtil.isAsymmetric()){
            req.setSystemAttribute(CLBConstants.CONNID_ATTR,
                    Connection.UDP);
            Header viaHeader = req.getRawHeader(Header.VIA);

            if (viaHeader != null) {
                ListIterator<String> viaIterator;
                viaIterator = viaHeader.getValues();

                if (viaIterator.hasNext()) {
                    ViaImpl via = new ViaImpl(viaIterator.next());
                    // Extract 'felb' flag
                    String frontEndLb = via.getParameter(CLBConstants.FE_LB_PARAM);

                    if (frontEndLb != null) {
                        // This response was received from the back-end,
                        // pop Via, extract connection and forward it to the client.
                        viaHeader.setReadOnly(false);
                        viaIterator.remove();
                    }
                }
            }
        }
    }

    /**
     * Handle a request that as been created or proxied by this instance.
     *
     * @param request the request; the request may be modified (headers added or
     *                changed)
     * @return a possible new connection; if null continue with the connection
     *         in the transaction stack.
     * @throws SipRoutingException thrown in case request was malformed
     */
    public Connection handleOutgoingRequest(SipServletRequestImpl request)
            throws SipRoutingException {
        if (logger.isLoggable(Level.FINER)) {
            logger.log(Level.FINER, "Handle outgoing request");
        }

        // Check if hash key has been previously saved
        // Note! The request will not have a hashkey in case there was no application handling the request and it has been proxied.       
        String hashkey = request.getBeKeyFromSession();
        if(hashkey == null){
            logger.log(Level.WARNING, "clb.sip.be_key_is_missing_in_request",
                    new Object[]{request});
        }
        if (logger.isLoggable(Level.FINER)) {
            logger.log(Level.FINER, "Hashkey: " + hashkey);
        }

        // Set BERoute parameter on topmost Via
        // Add quotes around it, as it may contain special characters
        ViaImpl via = getTopVia(request);
        via.setParameter(CLBConstants.BE_ROUTE_PARAM,
                CLBConstants.QUOTE + instanceID + CLBConstants.QUOTE);

        sipClientToClbFEMapper.pushRouteHeader(request, localAddress.getHostName(),
                localAddress.getPort());
       
        // Check if the application has set Contact
        if (request.isContactIndicated()) {
            // Check if this request is initiated from this instance

            Header contactHeader = request.getRawHeader(Header.CONTACT);
            if (contactHeader != null) {
                // There is a contact, we need to add the BE-KEY to it
                try {
                    AddressImpl addressValue = (AddressImpl) contactHeader.getAddressValue();
                    if (addressValue != null) {
                        // Fetch current read-only status
                        boolean readOnly = addressValue.isReadOnly();
                        // Allow writes on Address object
                        addressValue.setReadOnly(false);
                        URIImpl uriImpl = (URIImpl) addressValue.getURI();
                        if(uriImpl != null){
                            uriImpl.encodeBeKey(hashkey);
                        }
                        // Reset read-only status
                        addressValue.setReadOnly(readOnly);
                    }
                } catch (ServletParseException e) {
                    throw new SipRoutingException("Malformed Contact", e);
                }
            }

            replaceTopVia(request, via);

            if (logger.isLoggable(Level.FINER)) {
                logger.log(Level.FINER,
                        "The request originates from this instance, altered request: " + request);
            }

            return null;
        } else if (request.isRecordRouteIndicated()) {
            // This request was proxied, save ID of incoming connection.
            String encodedConnection;
            if ((encodedConnection=getEncondedConnection(request)) == null) {
                throw new SipRoutingException("Could not find connection information in a proxied request, shall never happen!");
            } else {
                via.setParameter(CLBConstants.CONNID_PARAM, encodedConnection);
            }


            Header rrHeader = request.getRawHeader(Header.RECORD_ROUTE);
            AddressImpl rrAddr;
            try {
                rrAddr = (AddressImpl) rrHeader.getAddressValue();
            } catch (ServletParseException e) {
                throw new SipRoutingException("Malformed record-Route", e);
            }

            // The R-R was added by the application, thus we shall set 'bekey'
            // Fetch current read-only status
            boolean readOnly = rrAddr.isReadOnly();
            // Allow writes on Address object
            rrAddr.setReadOnly(false);
            URIImpl uriImpl = (URIImpl) rrAddr.getURI();
            if (uriImpl != null) {
                uriImpl.encodeBeKey(hashkey);
            }
            // Reset read-only status
            rrAddr.setReadOnly(readOnly);

            replaceTopVia(request, via);

            if (logger.isLoggable(Level.FINER)) {
                logger.log(Level.FINER,
                        "The request was proxied, altered request: " + request);
            }

            return null;
        } else {
            // This request was either:
            // a) proxied but not Record-Routed; save ID of incoming connection so that response can travel back on it.
            // b) a non-dialog request originating from this instance; no connection information will exist.
            String encodedConnection;
            if ((encodedConnection=getEncondedConnection(request)) != null) {
                via.setParameter(CLBConstants.CONNID_PARAM, encodedConnection);
            }

            replaceTopVia(request, via);

            return null;
        }
    }

    void handleIncomingResponse(SipServletResponseImpl response)
            throws SipRoutingException {
        // Get topmost header
        Header viaHeader = response.getRawHeader(Header.VIA);

        if (viaHeader != null) {
            ListIterator<String> viaIterator;
            viaIterator = viaHeader.getValues();

            if (viaIterator.hasNext()) {
                ViaImpl via = new ViaImpl(viaIterator.next());
                // Extract connection
                Connection connection = LoadbalancerUtil.getConnection(via);
                if (connection != null) {
                    // The top Via contained connection information, the origin request was proxied
                    // by the application; the connection information either identifies
                    // - the connection between the front-end and this instance
                    // or
                    // - the client and this instance.
                    // (Note, it is a valid case that no connection ID in the response;
                    // this occurs if the origin request was sent from an UAC on this instance)
                    response.setSystemAttribute(CLBConstants.CONNID_ATTR, connection);
                }

                decodeClientCert(response); // TODO Can a response ever have a client cert?
                decodeRemote(response);
                return;
            }
        }
        // No Via? The response must be corrupt, drop it!
        throw new SipRoutingException(
            "No Via on response, shall never happen; drop the response!");
    }

    private String getEncondedConnection(SipServletRequestImpl request) {
        Connection c = (Connection) request.getAttribute(CLBConstants.CONNID_ATTR);

        if (c != null) {
            return c.getEncodedValue();
        }
        return null;
    }

    /**
     * Handle a response that has been created or proxied by this instance.
     *
     * @param response the response; the response may be modified (headers added
     *                or changed)
     * @return a possible new connection; if null continue with the connection
     *         in the transaction stack.
     * @throws SipRoutingException thrown if message was malformed
     */
    public Connection handleOutgoingResponse(SipServletResponseImpl response)
            throws SipRoutingException {
        if (logger.isLoggable(Level.FINER)) {
            logger.log(Level.FINER, "Handle outgoing response");
        }
        if (response.isSerialized()) {
            return null;
        }

        sipClientToClbFEMapper.createFEAddressMapping(response);
       
        popBackendLBVia(response);
       
        // Note! The response will not have a hashkey in case there was no application handling the request       
        String hashkey = response.getBeKeyFromSession();
        if(hashkey == null){
            logger.log(Level.WARNING, "clb.sip.be_key_is_missing_in_response",
                    new Object[]{response});
        }
        if (hashkey != null) {
            if (response.isContactIndicated()) {
                // This is a UAS.
                Header contactHeader = response.getRawHeader(Header.CONTACT);

                if (contactHeader != null) {
                    AddressImpl contactAddress;

                    try {
                        contactAddress = (AddressImpl) (contactHeader.getAddressValue());
                    } catch (ServletParseException e) {
                        throw new SipRoutingException("Malformed Contact", e);
                    }

                    URIImpl contactUri = ((URIImpl) contactAddress.getURI());

                    contactAddress = ((AddressImpl) contactAddress.clone(true, true));
                    contactUri = (URIImpl) contactUri.clone();
                    contactUri.encodeBeKey(hashkey);
                    // Allow writes on cloned Address object
                    contactAddress.setReadOnly(false);
                    contactAddress.setURI(contactUri);
                    contactHeader.setReadOnly(false);
                    contactHeader.removeValues();
                    contactHeader.setValue(contactAddress.toString(), true);
                    contactHeader.setReadOnly(true);
                }
            } else if (response.getMethod().equalsIgnoreCase("INVITE") && response.getStatus() >= 300){
                // This is a non-2xx response to an INVITE, set bekey in the To-header
                Header toHeader = response.getRawHeader(Header.TO);

                if (toHeader != null) {
                    AddressImpl toAddress;

                    try {
                        toAddress = (AddressImpl) (toHeader.getAddressValue());
                    } catch (ServletParseException e) {
                        throw new SipRoutingException("Malformed To-header", e);
                    }

                    URIImpl toUri = ((URIImpl) toAddress.getURI());

                    toAddress = ((AddressImpl) toAddress.clone(true, true));
                    toUri = (URIImpl) toUri.clone();
                    toUri.encodeBeKey(hashkey);
                    // Allow writes on cloned Address object
                    toAddress.setReadOnly(false);
                    toAddress.setURI(toUri);
                    toHeader.setReadOnly(false);
                    toHeader.removeValues();
                    toHeader.setValue(toAddress.toString(), true);
                    toHeader.setReadOnly(true);
                }
            }
        }

        // Get the connection that was extracted from the Via in the (possible) incoming response
        Connection connection = (Connection) response.getRequest().getAttribute(CLBConstants.CONNID_ATTR);
        if (connection == Connection.UDP &&
                LoadbalancerUtil.isAsymmetric()) {
            // The origin request was sent via UDP.
            // There will thus *not* exist a connection between the client and this instance.
            // Resolve the Via and use the resolved address as destination.
            try {
                TargetTuple tt = TargetResolver.getInstance().resolveResponse(response);
                connection = new Connection(tt.getProtocol(), null, new Socket(tt.getIP(), tt.getPort()));
            } catch (Exception e) {
                logger.log(Level.WARNING, "clb.sip.warning.resolve_failed");
                if(logger.isLoggable(Level.FINE)){
                    logger.log(Level.FINE, "clb.caught_an_exception", e);
                }
                throw new SipRoutingException("Unable to resolve address", e);
            }
        }
       
        if (logger.isLoggable(Level.FINER)) {
            logger.log(Level.FINER, "Altered response: " + response);
        }

        return connection;
    }

    private void pushBackendLBVia(SipServletRequestImpl req) throws SipRoutingException {
        if(!ProxyKeyExtractor.isSipRequestProxied(req)){
            // Issue#1418
            // The request has arrived at this back-end instance without passing a front-end;
            // push a dummy via to make sure that branch-ID is created in the same way as if the
            // request had been proxied by a front-end instance.
            // This is to make sure that branch-ID for a CANCEL is the same as the initial request,
            // regardless if the CANCEL and the initial request arrives to the back-end via different
            // paths (directly or via front-end).
            SipLoadBalancerIncomingHandler.pushVia(req,
                    localAddress.getHostName(), localAddress.getPort(), false);
        }
    }
   
    private void popBackendLBVia(SipServletResponseImpl response) {
        // Issue #1418
        // Check if the origin request arrived at the back-end directly and if
        // the dummy Via with the 'belb' parameter was pushed. In that case pop
        // that Via.
        if (getTopVia(response).getParameter(CLBConstants.BE_LB_PARAM) != null) {
            popTopVia(response);
        }
    }

    private ViaImpl getTopVia(SipServletMessageImpl msg) {
        return new ViaImpl(msg.getRawHeader(Header.VIA).getValue());
    }
   
    private void popTopVia(SipServletMessageImpl msg) {
        Header viaHeader = msg.getRawHeader(Header.VIA);
        viaHeader.setReadOnly(false);
        ListIterator<String> viaIter = viaHeader.getValues();

        while (viaIter.hasNext()) {
            viaIter.next();
            viaIter.remove();

            break;
        }
        viaHeader.setReadOnly(true);
    }

    private void replaceTopVia(SipServletMessageImpl msg, ViaImpl newTopVia) {
        Header viaHeader = msg.getRawHeader(Header.VIA);
        viaHeader.setReadOnly(false);

        ListIterator<String> viaIter = viaHeader.getValues();

        while (viaIter.hasNext()) {
            viaIter.next();
            viaIter.remove();

            break;
        }

        viaHeader.setValue(newTopVia.toString(), true);
        viaHeader.setReadOnly(true);
    }

    private void decodeRemote(SipServletMessageImpl msg)
            throws SipRoutingException {
        String remote = msg.getHeader(Header.PROXY_REMOTE_HEADER);

        if (remote != null) {
            msg.removeHeader(Header.PROXY_REMOTE_HEADER);

            StringTokenizer st = new StringTokenizer(remote, ":", false);
            String[] addrArr = new String[3];
            int i = 0;

            while (st.hasMoreTokens() && (i < addrArr.length)) {
                addrArr[i++] = st.nextToken();
            }

            // Now addrArr[0] = transport, addrArr[1]=IP address and
            // addrArr[2]=port
            if (i == addrArr.length) {
                try {
                    msg.setInitialRemote(
                    new TargetTuple(SipTransports.getTransport(addrArr[0]),
                    addrArr[1], Integer.parseInt(addrArr[2])));
                } catch (NumberFormatException e) {
                    throw new SipRoutingException("Malformed " +
                            Header.PROXY_REMOTE_HEADER, e);
                } catch (Exception e) {
                    throw new SipRoutingException("Malformed " +
                            Header.PROXY_REMOTE_HEADER, e);
                }
            }
        }
    }

    private void decodeClientCert(SipServletMessageImpl message)
            throws SipRoutingException {
        ListIterator<String> headers = message.getHeaders(Header.PROXY_AUTH_CERT_HEADER);
        List<X509Certificate> certs = new ArrayList<X509Certificate>();

        while (headers.hasNext()) {
            String header = headers.next();
            message.removeHeader(Header.PROXY_AUTH_CERT_HEADER);

            try {
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                byte[] clientCertBytes = LoadbalancerUtil.decodeParameterToBytes(header,
                        true);
                ByteArrayInputStream bais = new ByteArrayInputStream(clientCertBytes);
                certs.add((X509Certificate) cf.generateCertificate(bais));
            } catch (CertificateException e) {
                throw new SipRoutingException("Could not decode " +
                        Header.PROXY_AUTH_CERT_HEADER, e);
            } catch (IOException e) {
                throw new SipRoutingException("Could not decode " +
                        Header.PROXY_AUTH_CERT_HEADER, e);
            }
        }

        if (certs.size() > 0) {
            if (message instanceof SipServletRequest) {
                message.setCertificate(certs.toArray(new X509Certificate[certs.size()]));
            } else {
                message.setCertificate(certs.toArray(new X509Certificate[certs.size()]));
            }
        }
    }
   
    private void saveIncomingConnection(SipServletMessageImpl msg) {
        msg.setSystemAttribute(CLBConstants.CONNID_ATTR,
            new Connection(msg.getRemote().getProtocol(),
                new Socket(msg.getLocal().getHostName(),
                    msg.getLocal().getPort()),
                new Socket(msg.getRemote().getIP(), msg.getRemote().getPort())));
    }
}
TOP

Related Classes of org.jvnet.glassfish.comms.clb.core.sip.SipLoadBalancerBackend

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.