org.mobicents.servlet.sip.catalina.SipProtocolHandler Maven / Gradle / Ivy
/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2015, Telestax Inc and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see
*
*/
package org.mobicents.servlet.sip.catalina;
import gov.nist.javax.sip.ListeningPointExt;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.sip.ListeningPoint;
import javax.sip.SipProvider;
import javax.sip.SipStack;
import net.java.stun4j.StunAddress;
import net.java.stun4j.client.NetworkConfigurationDiscoveryProcess;
import net.java.stun4j.client.StunDiscoveryReport;
import org.apache.catalina.Executor;
import org.apache.coyote.Adapter;
import org.apache.coyote.ProtocolHandler;
import org.apache.log4j.Logger;
import org.apache.tomcat.util.modeler.Registry;
import org.mobicents.ha.javax.sip.ClusteredSipStack;
import org.mobicents.ha.javax.sip.LoadBalancerHeartBeatingService;
import org.mobicents.ha.javax.sip.SipLoadBalancer;
import org.mobicents.servlet.sip.JainSipUtils;
import org.mobicents.servlet.sip.SipConnector;
import org.mobicents.servlet.sip.core.ExtendedListeningPoint;
import org.mobicents.servlet.sip.core.SipApplicationDispatcher;
/**
* This is the sip protocol handler that will get called upon creation of the
* tomcat connector defined in the server.xml.
To use a sip connector, one
* need to specify a new connector in server.xml with
* org.mobicents.servlet.sip.startup.SipProtocolHandler as the value for the
* protocol attribute.
*
* Some of the fields (representing the sip stack propeties) get populated
* automatically by the container.
*
* @author Jean Deruelle
*
*/
public class SipProtocolHandler implements ProtocolHandler, MBeanRegistration {
public static final String IS_SIP_CONNECTOR = "isSipConnector";
// the logger
private static final Logger logger = Logger.getLogger(SipProtocolHandler.class.getName());
// *
protected ObjectName tpOname = null;
// *
protected ObjectName rgOname = null;
/**
* The random port number generator that we use in getRandomPortNumer()
*/
private static Random portNumberGenerator = new Random();
private Adapter adapter = null;
private Map attributes = new HashMap();
private SipStack sipStack;
/*
* the extended listening point with global ip address and port for it
*/
public ExtendedListeningPoint extendedListeningPoint;
// defining sip stack properties
private Properties sipStackProperties;
private boolean started = false;
private SipConnector sipConnector;
public SipProtocolHandler() {
sipConnector = new SipConnector();
}
public SipProtocolHandler(SipConnector connector) {
sipConnector = connector;
}
/**
* {@inheritDoc}
*/
public void destroy() throws Exception {
if(logger.isDebugEnabled()) {
logger.debug("Stopping a sip protocol handler");
}
//Jboss specific unloading case
SipApplicationDispatcher sipApplicationDispatcher = (SipApplicationDispatcher)
getAttribute(SipApplicationDispatcher.class.getSimpleName());
if(sipApplicationDispatcher != null && extendedListeningPoint != null) {
if(logger.isDebugEnabled()) {
logger.debug("Removing the Sip Application Dispatcher as a sip listener for listening point " + extendedListeningPoint);
}
extendedListeningPoint.getSipProvider().removeSipListener(sipApplicationDispatcher);
sipApplicationDispatcher.getSipNetworkInterfaceManager().removeExtendedListeningPoint(extendedListeningPoint);
}
// removing listening point and sip provider
if(sipStack != null) {
if(extendedListeningPoint != null) {
if(extendedListeningPoint.getSipProvider().getListeningPoints().length == 1) {
sipStack.deleteSipProvider(extendedListeningPoint.getSipProvider());
if(logger.isDebugEnabled()) {
logger.debug("Removing the sip provider");
}
} else {
if(logger.isDebugEnabled()) {
logger.debug("Removing the following Listening Point " + extendedListeningPoint + " from the sip provider");
}
extendedListeningPoint.getSipProvider().removeListeningPoint(extendedListeningPoint.getListeningPoint());
}
if(logger.isDebugEnabled()) {
logger.debug("Removing the following Listening Point " + extendedListeningPoint);
}
sipStack.deleteListeningPoint(extendedListeningPoint.getListeningPoint());
if(sipConnector.isUseLoadBalancer() && sipStack instanceof ClusteredSipStack &&
((ClusteredSipStack)sipStack).getLoadBalancerHeartBeatingService() != null) {
if(logger.isDebugEnabled()) {
logger.debug("SipConnector " + extendedListeningPoint.getListeningPoint() + " remove to use Load Balancer for outbound traffic");
}
LoadBalancerHeartBeatingService loadBalancerHeartBeatingService = ((ClusteredSipStack)sipStack).getLoadBalancerHeartBeatingService();
loadBalancerHeartBeatingService.removeSipConnector(extendedListeningPoint.getListeningPoint(), extendedListeningPoint.getLoadBalancer());
}
extendedListeningPoint = null;
}
}
if (tpOname != null)
Registry.getRegistry(null, null).unregisterComponent(tpOname);
if (rgOname != null)
Registry.getRegistry(null, null).unregisterComponent(rgOname);
setStarted(false);
sipStack = null;
}
public Adapter getAdapter() {
return adapter;
}
/**
* {@inheritDoc}
*/
public Object getAttribute(String attribute) {
return attributes.get(attribute);
}
/**
* {@inheritDoc}
*/
public Iterator getAttributeNames() {
return attributes.keySet().iterator();
}
/**
* {@inheritDoc}
*/
public void init() throws Exception {
setAttribute(IS_SIP_CONNECTOR,Boolean.TRUE);
}
public void pause() throws Exception {
//This is optionnal, no implementation there
}
public void resume() throws Exception {
// This is optionnal, no implementation there
}
public void setAdapter(Adapter adapter) {
this.adapter = adapter;
}
/**
* {@inheritDoc}
*/
public void setAttribute(String arg0, Object arg1) {
attributes.put(arg0, arg1);
}
public void start() throws Exception {
if(logger.isDebugEnabled()) {
logger.debug("Starting a sip protocol handler");
}
Integer portFromConfig = sipConnector.getPort();
String set = System.getProperty("jboss.service.binding.set");
int setIncrememt = 0;
if(set != null) {
int setNumber = set.charAt(set.length() - 1) - '0';
if(setNumber>0 && setNumber<10) {
setIncrememt = (setNumber) * 100; // don't attempt to compute if the port set is custom outside that range
}
if(logger.isInfoEnabled()) {
logger.info("Computed port increment for MSS SIP ports is " + setIncrememt + " from " + set);
}
}
portFromConfig += setIncrememt;
sipConnector.setPort(portFromConfig);
final String ipAddress = sipConnector.getIpAddress();
final int port = sipConnector.getPort();
final String signalingTransport = sipConnector.getTransport();
try {
//checking the external ip address if stun enabled
String globalIpAddress = null;
int globalPort = -1;
boolean useStun = sipConnector.isUseStun();
if (useStun) {
if(InetAddress.getByName(ipAddress).isLoopbackAddress()) {
logger.warn("The Ip address provided is the loopback address, stun won't be enabled for it");
} else {
//chooses stun port randomly
DatagramSocket randomSocket = initRandomPortSocket();
int randomPort = randomSocket.getLocalPort();
randomSocket.disconnect();
randomSocket.close();
randomSocket = null;
StunAddress localStunAddress = new StunAddress(ipAddress,
randomPort);
StunAddress serverStunAddress = new StunAddress(
sipConnector.getStunServerAddress(), sipConnector.getStunServerPort());
NetworkConfigurationDiscoveryProcess addressDiscovery = new NetworkConfigurationDiscoveryProcess(
localStunAddress, serverStunAddress);
addressDiscovery.start();
StunDiscoveryReport report = addressDiscovery
.determineAddress();
if(report.getPublicAddress() != null) {
globalIpAddress = report.getPublicAddress().getSocketAddress().getAddress().getHostAddress();
globalPort = report.getPublicAddress().getPort();
//TODO set a timer to retry the binding and provide a callback to update the global ip address and port
} else {
useStun = false;
logger.error("Stun discovery failed to find a valid public ip address, disabling stun !");
}
if(logger.isInfoEnabled()) {
logger.info("Stun report = " + report);
}
addressDiscovery.shutDown();
}
//TODO add it as a listener for global ip address changes if STUN rediscover a new addess at some point
}
ListeningPointExt listeningPoint = (ListeningPointExt) sipStack.createListeningPoint(ipAddress,
port, signalingTransport);
if(useStun) {
// TODO: (ISSUE-CONFUSION) Check what is the confusion here, why not use the globalport. It ends up putting the local port everywhere
// listeningPoint.setSentBy(globalIpAddress + ":" + globalPort);
listeningPoint.setSentBy(globalIpAddress + ":" + port);
}
boolean createSipProvider = false;
SipProvider sipProvider = null;
if(sipStack.getSipProviders().hasNext()) {
sipProvider = (SipProvider) sipStack.getSipProviders().next();
for (ListeningPoint listeningPointTemp : sipProvider.getListeningPoints()) {
if(!(listeningPointTemp.getIPAddress().equalsIgnoreCase(listeningPoint.getIPAddress()) && listeningPointTemp.getPort() == listeningPoint.getPort())) {
createSipProvider = true;
break;
}
}
} else {
createSipProvider = true;
}
if(createSipProvider) {
sipProvider = sipStack.createSipProvider(listeningPoint);
} else {
sipProvider.addListeningPoint(listeningPoint);
}
//creating the extended listening point
extendedListeningPoint = new ExtendedListeningPoint(sipProvider, listeningPoint, sipConnector);
extendedListeningPoint.setUseStaticAddress(false);
extendedListeningPoint.setGlobalIpAddress(globalIpAddress);
extendedListeningPoint.setGlobalPort(globalPort);
extendedListeningPoint.setUseLoadBalancer(sipConnector.isUseLoadBalancer());
// https://github.com/RestComm/sip-servlets/issues/111
LoadBalancerHeartBeatingService loadBalancerHeartBeatingService = null;
if(sipConnector.isUseLoadBalancer() && sipStack instanceof ClusteredSipStack &&
((ClusteredSipStack)sipStack).getLoadBalancerHeartBeatingService() != null) {
if(logger.isDebugEnabled()) {
logger.debug("SipConnector " + listeningPoint + " set to use Load Balancer for outbound traffic");
}
loadBalancerHeartBeatingService = ((ClusteredSipStack)sipStack).getLoadBalancerHeartBeatingService();
// https://github.com/RestComm/sip-servlets/issues/137
if(sipConnector.getLoadBalancerAddress() != null && loadBalancerHeartBeatingService != null) {
InetAddress loadBalancerAddress = null;
try {
loadBalancerAddress = InetAddress.getByName(sipConnector.getLoadBalancerAddress());
} catch (UnknownHostException e) {
throw new IllegalArgumentException(
"Something wrong with load balancer host creation.", e);
}
SipLoadBalancer loadBalancer = new SipLoadBalancer(
loadBalancerHeartBeatingService,
loadBalancerAddress,
sipConnector.getLoadBalancerSipPort(),
-1,
sipConnector.getLoadBalancerRmiPort());
extendedListeningPoint.setLoadBalancer(loadBalancer);
loadBalancerHeartBeatingService.addSipConnector(listeningPoint, loadBalancer);
} else {
loadBalancerHeartBeatingService.addSipConnector(listeningPoint);
}
}
//make the extended listening Point available to the service implementation
setAttribute(ExtendedListeningPoint.class.getSimpleName(), extendedListeningPoint);
SipApplicationDispatcher sipApplicationDispatcher = (SipApplicationDispatcher)
getAttribute(SipApplicationDispatcher.class.getSimpleName());
//Jboss specific loading case
if(sipApplicationDispatcher != null) {
// Let's add it to hostnames, so that IP load balancer appears as localhost. Otherwise requestst addressed there will
// get forwarded and especially in case of failover this might be severe error.
if(sipConnector.getStaticServerAddress() != null) {
sipApplicationDispatcher.addHostName(sipConnector.getStaticServerAddress() + ":" + sipConnector.getStaticServerPort());
if(logger.isDebugEnabled()) {
logger.debug("Adding hostname for IP load balancer " + sipConnector.getStaticServerAddress());
}
}
if(logger.isDebugEnabled()) {
logger.debug("Adding the Sip Application Dispatcher as a sip listener for connector listening on port " + port);
}
sipProvider.addSipListener(sipApplicationDispatcher);
sipApplicationDispatcher.getSipNetworkInterfaceManager().addExtendedListeningPoint(extendedListeningPoint);
}
logger.info("Sip Connector started on ip address : " + ipAddress
+ ",port " + port + ", transport " + signalingTransport +
", useStun " + useStun + ", stunAddress " + sipConnector.getStunServerAddress() + ", stunPort : " + sipConnector.getStaticServerPort() +
", useLoadBalancer " + sipConnector.isUseLoadBalancer());
if (this.domain != null) {
// try {
// tpOname = new ObjectName
// (domain + ":" + "type=ThreadPool,name=" + getName());
// Registry.getRegistry(null, null)
// .registerComponent(endpoint, tpOname, null );
// } catch (Exception e) {
// logger.error("Can't register endpoint");
// }
rgOname=new ObjectName
(domain + ":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null, null).registerComponent
( sipStack, rgOname, null );
}
setStarted(true);
} catch (Exception ex) {
logger.error(
"A problem occured while setting up SIP Connector " + ipAddress + ":" + port + "/" + signalingTransport + "-- check server.xml for tomcat. ", ex);
} finally {
if(!isStarted()) {
destroy();
}
}
}
/**
* Initializes and binds a socket that on a random port number. The method
* would try to bind on a random port and retry 5 times until a free port
* is found.
*
* @return the socket that we have initialized on a randomport number.
*/
private DatagramSocket initRandomPortSocket() {
int bindRetries = 5;
int currentlyTriedPort =
getRandomPortNumber(JainSipUtils.MIN_PORT_NUMBER, JainSipUtils.MAX_PORT_NUMBER);
DatagramSocket resultSocket = null;
//we'll first try to bind to a random port. if this fails we'll try
//again (bindRetries times in all) until we find a free local port.
for (int i = 0; i < bindRetries; i++) {
try {
resultSocket = new DatagramSocket(currentlyTriedPort);
//we succeeded - break so that we don't try to bind again
break;
}
catch (SocketException exc) {
if (exc.getMessage().indexOf("Address already in use") == -1) {
logger.fatal("An exception occurred while trying to create"
+ "a local host discovery socket.", exc);
return null;
}
//port seems to be taken. try another one.
logger.debug("Port " + currentlyTriedPort + " seems in use.");
currentlyTriedPort =
getRandomPortNumber(JainSipUtils.MIN_PORT_NUMBER, JainSipUtils.MAX_PORT_NUMBER);
logger.debug("Retrying bind on port " + currentlyTriedPort);
}
}
return resultSocket;
}
/**
* Returns a random local port number, greater than min and lower than max.
*
* @param min the minimum allowed value for the returned port number.
* @param max the maximum allowed value for the returned port number.
*
* @return a random int located between greater than min and lower than max.
*/
public static int getRandomPortNumber(int min, int max) {
return portNumberGenerator.nextInt(max - min) + min;
}
/**
* @return the signalingTransport
*/
public String getSignalingTransport() {
return sipConnector.getTransport();
}
/**
* @param signalingTransport
* the signalingTransport to set
* @throws Exception
*/
public void setSignalingTransport(String transport) throws Exception {
sipConnector.setTransport(transport);
if(isStarted()) {
destroy();
start();
}
}
/**
* @return the port
*/
public int getPort() {
return sipConnector.getPort();
}
/**
* @param port
* the port to set
* @throws Exception
*/
public void setPort(int port) throws Exception {
sipConnector.setPort(port);
if(isStarted()) {
destroy();
start();
}
}
public void setIpAddress(String ipAddress) throws Exception {
sipConnector.setIpAddress(ipAddress);
if(isStarted()) {
destroy();
start();
}
}
public String getIpAddress() {
return sipConnector.getIpAddress();
}
/**
* @return the stunServerAddress
*/
public String getStunServerAddress() {
return sipConnector.getStunServerAddress();
}
/**
* @param stunServerAddress the stunServerAddress to set
*/
public void setStunServerAddress(String stunServerAddress) {
sipConnector.setStunServerAddress(stunServerAddress);
}
/**
* @return the stunServerPort
*/
public int getStunServerPort() {
return sipConnector.getStunServerPort();
}
/**
* @param stunServerPort the stunServerPort to set
*/
public void setStunServerPort(int stunServerPort) {
sipConnector.setStunServerPort(stunServerPort);
}
/**
* @return the useLoadBalancer
*/
public boolean isUseLoadBalancer() {
return sipConnector.isUseLoadBalancer();
}
/**
* @param useLoadBalancer the useLoadBalancer to set
*/
public void setUseLoadBalancer(boolean useLoadBalancer) {
sipConnector.setUseLoadBalancer(useLoadBalancer);
}
/**
* @param useStun the useStun to set
*/
public void setUseStun(boolean useStun) {
sipConnector.setUseStun(useStun);
}
/**
* @return the useStun
*/
public boolean isUseStun() {
return sipConnector.isUseStun();
}
public String getStaticServerAddress() {
return sipConnector.getStaticServerAddress();
}
public void setStaticServerAddress(String staticServerAddress) {
sipConnector.setStaticServerAddress(staticServerAddress);
}
public int getStaticServerPort() {
return sipConnector.getStaticServerPort();
}
public void setStaticServerPort(int staticServerPort) {
sipConnector.setStaticServerPort(staticServerPort);
}
public boolean isUseStaticAddress() {
return sipConnector.isUseStaticAddress();
}
public void setUseStaticAddress(boolean useStaticAddress) {
sipConnector.setUseStaticAddress(useStaticAddress);
}
public String getHostNames() {
return sipConnector.getHostNames();
}
public void setHostNames(String hostNames) {
sipConnector.setHostNames(hostNames);
}
public String getAddress() {
return getName();
}
public InetAddress getInetIpAddress() {
try {
return InetAddress.getByName(sipConnector.getIpAddress());
} catch (UnknownHostException e) {
logger.error("unexpected exception while getting the ipaddress of the sip protocol handler", e);
return null;
}
}
public void setInetIpAddress(InetAddress ia) {
sipConnector.setIpAddress(ia.getHostAddress());
}
public String getTransport() {
return sipConnector.getTransport();
}
/**
* @return the sipStackProperties
*/
public Properties getSipStackProperties() {
return sipStackProperties;
}
/**
* @param sipStackProperties the sipStackProperties to set
*/
public void setSipStackProperties(Properties sipStackProperties) {
this.sipStackProperties = sipStackProperties;
}
public boolean isReplaceStaticServerAddressForInternalRoutingRequest() {
return sipConnector.isReplaceStaticServerAddressForInternalRoutingRequest();
}
public void setReplaceStaticServerAddressForInternalRoutingRequest(
boolean value) {
sipConnector.setReplaceStaticServerAddressForInternalRoutingRequest(value);
}
/**
* @param sipConnector the sipConnector to set
*/
public void setSipConnector(SipConnector sipConnector) {
this.sipConnector = sipConnector;
}
/**
* @return the sipConnector
*/
public SipConnector getSipConnector() {
return sipConnector;
}
public String getName() {
String encodedAddr = "";
if (getInetIpAddress() != null) {
encodedAddr = "" + getInetIpAddress();
if (encodedAddr.startsWith("/"))
encodedAddr = encodedAddr.substring(1);
encodedAddr = URLEncoder.encode(encodedAddr) + "-";
}
return ("sip-" + getTransport() + "-" + encodedAddr + getPort());
}
// -------------------- JMX related methods --------------------
// *
protected String domain;
protected ObjectName oname;
protected MBeanServer mserver;
public ObjectName getObjectName() {
return oname;
}
public String getDomain() {
return domain;
}
public ObjectName preRegister(MBeanServer server,
ObjectName name) throws Exception {
oname=name;
mserver=server;
domain=name.getDomain();
return name;
}
public void postRegister(Boolean registrationDone) {
}
public void preDeregister() throws Exception {
}
public void postDeregister() {
}
/**
* @param started the started to set
*/
public void setStarted(boolean started) {
this.started = started;
}
/**
* @return the started
*/
public boolean isStarted() {
return started;
}
public void setSipStack(SipStack sipStack) {
this.sipStack = sipStack;
}
public Executor getExecutor() {
// TODO Auto-generated method stub
// throw new UnsupportedOperationException();
//Issue 80: http://code.google.com/p/sipservlets/issues/detail?id=80
return null;
}
@Override
public void stop() throws Exception {
// TODO Auto-generated method stub
// throw new UnsupportedOperationException();
destroy();
}
@Override
public boolean isAprRequired() {
return false;
}
@Override
public boolean isCometSupported() {
return false;
}
@Override
public boolean isCometTimeoutSupported() {
return false;
}
@Override
public boolean isSendfileSupported() {
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy