org.mobicents.servlet.sip.undertow.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.undertow;
import gov.nist.javax.sip.ListeningPointExt;
import java.lang.management.ManagementFactory;
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.log4j.Logger;
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 class is based on org.mobicents.servlet.sip.catalina.SipProtocolHandler class from sip-servlet-as7 project,
* re-implemented for jboss as10 (wildfly) by:
*
* @author [email protected]
*
*/
public class SipProtocolHandler implements MBeanRegistration {
// the logger
private static final Logger logger = Logger.getLogger(SipProtocolHandler.class.getName());
public static final String IS_SIP_CONNECTOR = "isSipConnector";
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 SipConnector sipConnector;
private SipStack sipStack;
private boolean started = false;
public ExtendedListeningPoint extendedListeningPoint;
// defining sip stack properties
private Properties sipStackProperties;
private Map attributes = new HashMap();
public SipProtocolHandler() {
sipConnector = new SipConnector();
}
public SipProtocolHandler(SipConnector connector) {
sipConnector = connector;
}
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());
// https://github.com/RestComm/sip-servlets/issues/111
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) {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(tpOname);
}
if (rgOname != null) {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(rgOname);
}
setStarted(false);
sipStack = null;
}
/**
* {@inheritDoc}
*/
public Object getAttribute(String attribute) {
return attributes.get(attribute);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("rawtypes")
public Iterator getAttributeNames() {
return attributes.keySet().iterator();
}
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() {
// This is optionnal, no implementation there
}
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
}
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 !");
}
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() != null && 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 " + sipConnector.isUseStun() + ", stunAddress "
+ sipConnector.getStunServerAddress() + ", stunPort : " + sipConnector.getStunServerPort()
+ ", useStaticAddress: " + sipConnector.isUseStaticAddress() + ", staticServerAddress "
+ sipConnector.getStaticServerAddress() + ", staticServerPort " + sipConnector.getStaticServerPort());
if (this.domain != null) {
rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
ManagementFactory.getPlatformMBeanServer().registerMBean(sipStack, rgOname);
}
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 useStun
*/
public boolean isUseStun() {
return sipConnector.isUseStun();
}
/**
* @param useStun the useStun to set
*/
public void setUseStun(boolean useStun) {
sipConnector.setUseStun(useStun);
}
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 getAddress() {
return getName();
}
public String getHostNames() {
return sipConnector.getHostNames();
}
public void setHostNames(String hostNames) {
sipConnector.setHostNames(hostNames);
}
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;
}
@SuppressWarnings("deprecation")
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 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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy