/*
* 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 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.ViaImpl;
import com.ericsson.ssa.sip.dns.SipTransports;
import com.ericsson.ssa.sip.dns.TargetResolver;
import com.ericsson.ssa.sip.dns.TargetTuple;
import org.jvnet.glassfish.comms.clb.core.CLBConstants;
import org.jvnet.glassfish.comms.clb.core.ConsistentHashRequest;
import org.jvnet.glassfish.comms.clb.core.EndPoint;
import org.jvnet.glassfish.comms.clb.core.Router;
import org.jvnet.glassfish.comms.clb.core.ServerInstance;
import org.jvnet.glassfish.comms.clb.core.util.LoadbalancerUtil;
import org.jvnet.glassfish.comms.util.LogUtil;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ListIterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jvnet.glassfish.comms.clb.core.Controller;
import org.jvnet.glassfish.comms.clb.core.monitor.CLBMonitoringManager;
/**
* 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)
*/
class SipLoadBalancerIncomingHandler {
private static final Logger logger = LogUtil.CLB_LOGGER.getLogger();
private static final CLBMonitoringManager clbMonitoringManager =
CLBMonitoringManager.getInstance();
private Controller controller;
private Socket localSipTcpSocket; // Socket for traffic between F-E to B-E and responses
private ServerInstance localInstance;
/**
* Creates an instance and associates it with the specified hash key
* extractor and server instance lookup.
*
*/
public SipLoadBalancerIncomingHandler(Controller controller, Socket localSipTcpSocket) {
this.controller = controller;
this.localSipTcpSocket = localSipTcpSocket;
localInstance = null;
}
public SipLoadBalancerIncomingHandler(Controller controller) {
this.controller = controller;
localInstance = controller.getLocalInstance();
localSipTcpSocket = null;
}
/**
* 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)
* @param serverSelector the server selector
* @return a possible new connection; null means continue on this server
* @throws SipRoutingException thrown in case request was malformed
*/
public Connection handleIncomingRequest(ConsistentHashRequest req,
Router router) throws SipRoutingException {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER,
"Handle incoming request");
}
SipServletRequestImpl request = (SipServletRequestImpl) req.getSipRequest();
if(clbMonitoringManager.isCLBMonitoringEnabled()){
clbMonitoringManager.getCLBStatsUpdater().
incrementTotalIncomingSipRequestsFE();
}
ServerInstance serverInstance = router.selectInstance(req);
if (serverInstance == null) {
throw new SipRoutingException("Could not find a server");
}
String hashkey = req.getHashKey();
request.setBeKey(hashkey);
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER,
"Hash key: " + hashkey + "; server: " +
serverInstance.getName()+"; isLocal: "+serverInstance.isLocalInstance());
}
if (serverInstance.isLocalInstance()) {
if(clbMonitoringManager.isCLBMonitoringEnabled()){
clbMonitoringManager.getCLBStatsUpdater().
incrementTotalLocalSipRequestsFE();
}
// Continue on this instance
return null;
} else {
if(clbMonitoringManager.isCLBMonitoringEnabled()){
clbMonitoringManager.getCLBStatsUpdater().
incrementTotalProxiedSipRequestsFE();
}
// Proxy to the other instance
Socket serverAddress = getServerAddress(
serverInstance.getEndPoint(CLBConstants.SIP_PROTOCOL));
Socket outgoingSocket = null;
//update via with proper param
if(controller.getLocalInstance() != null){
EndPoint endPoint = controller.getLocalInstance().getEndPoint(
CLBConstants.SIP_PROTOCOL);
pushVia(request,
endPoint.getHost(),
endPoint.getPort(),
true);
outgoingSocket = getServerAddress(endPoint);
}else{
pushVia(request, localSipTcpSocket.getHostName(),
localSipTcpSocket.getPort(), true);
outgoingSocket = localSipTcpSocket;
}
encodeRemote(request);
encodeClientCert(request);
encodeBeKeyHeader(request, hashkey);
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER,
"Proxy to other instance (" + serverAddress +
"); request: " + request);
}
Connection connection = new Connection(SipTransports.TCP_PROT,
null, serverAddress);
//set outgoing socket
connection.setOutgoingEndpoint(outgoingSocket);
return connection;
}
}
private Socket getServerAddress(EndPoint serverAddress) {
return new Socket(serverAddress.getIPAddress(), serverAddress.getPort());
}
/**
* Handle a response that as been received by this instance. It shall either
* be served by this instance, or be re-routed to another instance.
*
* @param response the response; the response may be modified (headers added
* or changed)
* @return a possible list of new connection which shall be tried in the
* order specified in the list; if null continue on the current
* server.
* @throws SipRoutingException thrown in case response was corrupt, the
* caller shall do no further processing but just drop the
* response.
*/
public Connection handleIncomingResponse(SipServletResponseImpl response)
throws SipRoutingException {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "Handle incoming response.");
}
// Get topmost header
Header viaHeader = response.getRawHeader(Header.VIA);
if (viaHeader != null) {
ListIterator<String> viaIterator;
viaIterator = viaHeader.getValues();
if (viaIterator.hasNext()) {
if (clbMonitoringManager.isCLBMonitoringEnabled()) {
clbMonitoringManager.getCLBStatsUpdater().
incrementTotalOutgoingSipResponsesFE();
}
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();
viaHeader.setReadOnly(true);
// Extract connection
Connection connection = LoadbalancerUtil.getConnection(via);
if (connection != null && connection == Connection.UDP) {
// 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", e.getMessage());
throw new SipRoutingException("Unable to resolve address", e);
}
}
if(connection == null){
// This is plain wrong: connection information shall always exist!
throw new SipRoutingException(
"Was about to direct incoming response back to client, " +
"but could not find connection information to client in response.");
}
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER,
"Response was from from back-end forward it to the client via the connection: " +
connection);
}
return connection;
} else {
if(clbMonitoringManager.isCLBMonitoringEnabled()){
clbMonitoringManager.getCLBStatsUpdater().
incrementTotalIncomingSipResponsesFE();
}
// This response was received from an external party and
// shall possibly be forwarded to a back-end
ServerInstance serverInstance =
getServerInstanceForResponse(response);
return handleResponseFromExternalParty(response,
serverInstance);
}
}
}
// No Via? The response must be corrupt, drop it!
throw new SipRoutingException(
"No Via on response, shall never happen; drop the response! : " + response.toString());
}
// ---------------- Internal methods --------------------
private ServerInstance getServerInstanceForResponse(
SipServletResponseImpl response) throws SipRoutingException {
// Get topmost Via
Header viaHeader = response.getRawHeader(Header.VIA);
if (viaHeader != null) {
ListIterator<String> viaIterator;
viaIterator = viaHeader.getValues();
if (viaIterator.hasNext()) {
ViaImpl via = new ViaImpl(viaIterator.next());
String instanceID = via.getParameter(
CLBConstants.BE_ROUTE_PARAM);
if(instanceID == null || instanceID.length() <=2){
// Should never happen, drop response
throw new SipRoutingException(
"No BERoute found in response, shall never happen; drop the response! : " + response.toString());
}
//remove the quotes around the beroute value
instanceID = instanceID.substring(
1, (instanceID.length() - 1));
ServerInstance serverInstance =
controller.getGlobalInstanceMap().
getServerInstance(instanceID);
if (serverInstance == null) {
// Should never happen, drop response
throw new SipRoutingException(
"No matching instance found for BERoute \""
+ instanceID + "\" found in response, shall"
+ " never happen; drop the response! : " + response.toString());
}
return serverInstance;
}
}
throw new SipRoutingException(
"No Via on response, shall never happen; drop the response! : " + response.toString());
}
private Connection handleResponseFromExternalParty(
SipServletResponseImpl response,
ServerInstance serverInstance) throws SipRoutingException {
if (!serverInstance.isHealthy()) {
throw new SipRoutingException("Unable to route response as " +
serverInstance.getName() + " is unhealthy");
}
if (!serverInstance.isEnabled()) {
throw new SipRoutingException("Unable to route response as " +
serverInstance.getName() + " is disabled");
}
// Note, it does not matter that the response actually arrived on another socket.
// We just want to match it against the socket encoded in 'beroute' to see that
// this is the instance that sent the request.
if (serverInstance.isLocalInstance()) {
if (clbMonitoringManager.isCLBMonitoringEnabled()) {
clbMonitoringManager.getCLBStatsUpdater().
incrementTotalLocalSipResponsesFE();
}
// On the right server, continue on it
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER,
"The response came from the outside but we are on the right server, continue here.");
}
return null;
} else {
if (clbMonitoringManager.isCLBMonitoringEnabled()) {
clbMonitoringManager.getCLBStatsUpdater().
incrementTotalProxiedSipResponsesFE();
}
// The response shall be re-routed to another server
encodeRemote(response);
encodeClientCert(response); // TODO Can a response ever have a client cert?
Connection connection = new Connection(SipTransports.TCP_PROT, null,
getServerAddress(serverInstance.getEndPoint(
CLBConstants.SIP_PROTOCOL)));
//set outgoing socket
Socket outgoingSocket = null;
if(controller.getLocalInstance() != null){
EndPoint endPoint = controller.getLocalInstance().getEndPoint(
CLBConstants.SIP_PROTOCOL);
outgoingSocket = getServerAddress(endPoint);
}else{
outgoingSocket = localSipTcpSocket;
}
connection.setOutgoingEndpoint(outgoingSocket);
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER,
"The response came from the outside but we are on wrong server, " +
"re-route to : " + serverInstance.getName() +
" via the connection: " + connection);
}
return connection;
}
}
// TODO move this method to LoadbalancerUtil.
static void pushVia(SipServletRequestImpl request, String hostName,
int port, boolean frontend) throws SipRoutingException {
// This Via is used to route an incoming response if the request is being proxied and the
// response goes via UDP or if the TCP connection via which the request was sent
// is broken.
Header viaHeader = request.getRawHeader(Header.VIA);
ViaImpl topVia = null;
String branchId;
if (viaHeader == null) {
throw new SipRoutingException("No Via header on incoming request!");
} else {
topVia = new ViaImpl(viaHeader.getValue());
branchId = LoadbalancerUtil.createBranchId(request, topVia);
}
ViaImpl via = new ViaImpl("SIP", SipTransports.TCP_PROT.name(),
hostName, port);
if (frontend) {
// Set flag indicating that this Via was pushed by the front-end
via.setParameter(CLBConstants.FE_LB_PARAM, null);
// Save connection between client and front-end
Connection connection;
if (request.getRemote().getProtocol() != SipTransports.UDP_PROT) {
// Encode information about the connection between the client and the
// front-end
connection = new Connection(request.getRemote().getProtocol(),
new Socket(request.getLocal().getAddress().getHostAddress(),
request.getLocal().getPort()),
new Socket(request.getRemote().getIP(),
request.getRemote().getPort()));
} else {
// In case of UDP, only save a marker
connection = Connection.UDP;
}
via.setParameter(CLBConstants.CONNID_PARAM, connection.getEncodedValue());
} else {
// Fix for issue#1418
// This via is pushed by the CLB in back-end instance.
// Set flag indicating that this Via was pushed by the back-end
via.setParameter(CLBConstants.BE_LB_PARAM, null);
}
via.setParameter(ViaImpl.PARAM_BRANCH, branchId);
viaHeader.setValue(via.toString(), true);
}
private void encodeClientCert(SipServletMessageImpl message)
throws SipRoutingException {
// Encode a possible client certificate
X509Certificate[] clientCerts = message.getCertificate();
if (clientCerts != null) {
for (X509Certificate clientCert : clientCerts) {
try {
message.addHeader(Header.PROXY_AUTH_CERT_HEADER,
LoadbalancerUtil.encodeParameter(
clientCert.getEncoded(), true));
} catch (CertificateEncodingException e) {
throw new SipRoutingException("Could not encode client certificate.",
e);
}
}
}
}
private void encodeRemote(SipServletMessageImpl msg) {
msg.setHeader(Header.PROXY_REMOTE_HEADER,
msg.getRemote().getProtocol().name() + ":" +
msg.getRemote().getIP() + ":" + msg.getRemote().getPort());
}
private void encodeBeKeyHeader(SipServletMessageImpl msg, String bekey) {
msg.setHeader(Header.PROXY_BEKEY_HEADER, bekey);
}
}