Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2014, 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.restcomm.connect.telephony;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.actor.ReceiveTimeout;
import akka.actor.UntypedActor;
import akka.actor.UntypedActorContext;
import akka.actor.UntypedActorFactory;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.util.Timeout;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import gov.nist.javax.sip.header.UserAgent;
import org.apache.commons.configuration.Configuration;
import org.joda.time.DateTime;
import org.restcomm.connect.commons.configuration.RestcommConfiguration;
import org.restcomm.connect.commons.dao.Sid;
import org.restcomm.connect.commons.patterns.StopObserving;
import org.restcomm.connect.commons.util.SdpUtils;
import org.restcomm.connect.commons.util.UriUtils;
import org.restcomm.connect.dao.AccountsDao;
import org.restcomm.connect.dao.ApplicationsDao;
import org.restcomm.connect.dao.CallDetailRecordsDao;
import org.restcomm.connect.dao.ClientsDao;
import org.restcomm.connect.dao.DaoManager;
import org.restcomm.connect.dao.IncomingPhoneNumbersDao;
import org.restcomm.connect.dao.NotificationsDao;
import org.restcomm.connect.dao.RegistrationsDao;
import org.restcomm.connect.dao.entities.Account;
import org.restcomm.connect.dao.entities.Application;
import org.restcomm.connect.dao.entities.CallDetailRecord;
import org.restcomm.connect.dao.entities.Client;
import org.restcomm.connect.dao.entities.IncomingPhoneNumber;
import org.restcomm.connect.dao.entities.Notification;
import org.restcomm.connect.dao.entities.Registration;
import org.restcomm.connect.extension.api.CallRequest;
import org.restcomm.connect.extension.api.ExtensionResponse;
import org.restcomm.connect.extension.api.ExtensionType;
import org.restcomm.connect.extension.api.RestcommExtensionException;
import org.restcomm.connect.extension.api.RestcommExtensionGeneric;
import org.restcomm.connect.extension.controller.ExtensionController;
import org.restcomm.connect.interpreter.StartInterpreter;
import org.restcomm.connect.interpreter.StopInterpreter;
import org.restcomm.connect.interpreter.VoiceInterpreterBuilder;
import org.restcomm.connect.monitoringservice.MonitoringService;
import org.restcomm.connect.mscontrol.api.MediaServerControllerFactory;
import org.restcomm.connect.telephony.api.CallInfo;
import org.restcomm.connect.telephony.api.CallManagerResponse;
import org.restcomm.connect.telephony.api.CallResponse;
import org.restcomm.connect.telephony.api.CallStateChanged;
import org.restcomm.connect.telephony.api.CreateCall;
import org.restcomm.connect.telephony.api.DestroyCall;
import org.restcomm.connect.telephony.api.ExecuteCallScript;
import org.restcomm.connect.telephony.api.GetActiveProxy;
import org.restcomm.connect.telephony.api.GetCall;
import org.restcomm.connect.telephony.api.GetCallInfo;
import org.restcomm.connect.telephony.api.GetCallObservers;
import org.restcomm.connect.telephony.api.GetProxies;
import org.restcomm.connect.telephony.api.GetRelatedCall;
import org.restcomm.connect.telephony.api.Hangup;
import org.restcomm.connect.telephony.api.InitializeOutbound;
import org.restcomm.connect.telephony.api.SwitchProxy;
import org.restcomm.connect.telephony.api.UpdateCallScript;
import org.restcomm.connect.telephony.api.util.B2BUAHelper;
import org.restcomm.connect.telephony.api.util.CallControlHelper;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import javax.sdp.SdpParseException;
import javax.servlet.ServletContext;
import javax.servlet.sip.Address;
import javax.servlet.sip.AuthInfo;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipApplicationSessionEvent;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipServletResponse;
import javax.servlet.sip.SipSession;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.TelURL;
import javax.sip.header.RouteHeader;
import javax.sip.message.Response;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import static akka.pattern.Patterns.ask;
import static javax.servlet.sip.SipServlet.OUTBOUND_INTERFACES;
import static javax.servlet.sip.SipServletResponse.SC_ACCEPTED;
import static javax.servlet.sip.SipServletResponse.SC_BAD_REQUEST;
import static javax.servlet.sip.SipServletResponse.SC_FORBIDDEN;
import static javax.servlet.sip.SipServletResponse.SC_NOT_FOUND;
import static javax.servlet.sip.SipServletResponse.SC_OK;
import static javax.servlet.sip.SipServletResponse.SC_SERVER_INTERNAL_ERROR;
/**
* @author [email protected] (Thomas Quintana)
* @author [email protected]
* @author [email protected]
* @author [email protected]
*/
public final class CallManager extends UntypedActor {
static final int ERROR_NOTIFICATION = 0;
static final int WARNING_NOTIFICATION = 1;
static final Pattern PATTERN = Pattern.compile("[\\*#0-9]{1,12}");
static final String EMAIL_SENDER = "[email protected]";
static final String EMAIL_SUBJECT = "RestComm Error Notification - Attention Required";
static final int DEFAUL_IMS_PROXY_PORT = -1;
private final ActorSystem system;
private final Configuration configuration;
private final ServletContext context;
private final MediaServerControllerFactory msControllerFactory;
private final ActorRef conferences;
private final ActorRef bridges;
private final ActorRef sms;
private final SipFactory sipFactory;
private final DaoManager storage;
private final ActorRef monitoring;
// configurable switch whether to use the To field in a SIP header to determine the callee address
// alternatively the Request URI can be used
private boolean useTo;
private boolean authenticateUsers;
private AtomicInteger numberOfFailedCalls;
private AtomicBoolean useFallbackProxy;
private boolean allowFallback;
private boolean allowFallbackToPrimary;
private int maxNumberOfFailedCalls;
private String primaryProxyUri;
private String primaryProxyUsername, primaryProxyPassword;
private String fallBackProxyUri;
private String fallBackProxyUsername, fallBackProxyPassword;
private String activeProxy;
private String activeProxyUsername, activeProxyPassword;
private String mediaExternalIp;
private String myHostIp;
private String proxyIp;
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
private CreateCall createCallRequest;
private SwitchProxy switchProxyRequest;
//Control whether Restcomm will patch Request-URI and SDP for B2BUA calls
private boolean patchForNatB2BUASessions;
//List of extensions for CallManager
List extensions;
// IMS authentication
private boolean actAsImsUa;
private String imsProxyAddress;
private int imsProxyPort;
private String imsDomain;
private String imsAccount;
// used for sending warning and error logs to notification engine and to the console
private void sendNotification(String errMessage, int errCode, String errType, boolean createNotification) {
NotificationsDao notifications = storage.getNotificationsDao();
Notification notification;
if (errType == "warning") {
if(logger.isDebugEnabled()) {
// https://github.com/RestComm/Restcomm-Connect/issues/1419 moved to debug to avoid polluting logs
logger.debug(errMessage); // send message to console
}
if (createNotification) {
notification = notification(ERROR_NOTIFICATION, errCode, errMessage);
notifications.addNotification(notification);
}
} else if (errType == "error") {
// https://github.com/RestComm/Restcomm-Connect/issues/1419 moved to debug to avoid polluting logs
if(logger.isDebugEnabled()) {
logger.debug(errMessage); // send message to console
}
if (createNotification) {
notification = notification(ERROR_NOTIFICATION, errCode, errMessage);
notifications.addNotification(notification);
}
} else if (errType == "info") {
// https://github.com/RestComm/Restcomm-Connect/issues/1419 moved to debug to avoid polluting logs
if(logger.isDebugEnabled()) {
logger.debug(errMessage); // send message to console
}
}
}
public CallManager(final Configuration configuration, final ServletContext context, final ActorSystem system,
final MediaServerControllerFactory msControllerFactory, final ActorRef conferences, final ActorRef bridges,
final ActorRef sms, final SipFactory factory, final DaoManager storage) {
super();
this.system = system;
this.configuration = configuration;
this.context = context;
this.msControllerFactory = msControllerFactory;
this.conferences = conferences;
this.bridges = bridges;
this.sms = sms;
this.sipFactory = factory;
this.storage = storage;
final Configuration runtime = configuration.subset("runtime-settings");
final Configuration outboundProxyConfig = runtime.subset("outbound-proxy");
SipURI outboundIntf = outboundInterface("udp");
if (outboundIntf != null) {
myHostIp = ((SipURI) outboundIntf).getHost().toString();
} else {
String errMsg = "SipURI outboundIntf is null";
sendNotification(errMsg, 14001, "error", false);
if (context == null)
errMsg = "SipServlet context is null";
sendNotification(errMsg, 14002, "error", false);
}
Configuration mediaConf = configuration.subset("media-server-manager");
mediaExternalIp = mediaConf.getString("mgcp-server.external-address");
proxyIp = runtime.subset("telestax-proxy").getString("uri").replaceAll("http://", "").replaceAll(":2080", "");
if (mediaExternalIp == null || mediaExternalIp.isEmpty())
mediaExternalIp = myHostIp;
if (proxyIp == null || proxyIp.isEmpty())
proxyIp = myHostIp;
this.useTo = runtime.getBoolean("use-to");
this.authenticateUsers = runtime.getBoolean("authenticate");
this.primaryProxyUri = outboundProxyConfig.getString("outbound-proxy-uri");
this.primaryProxyUsername = outboundProxyConfig.getString("outbound-proxy-user");
this.primaryProxyPassword = outboundProxyConfig.getString("outbound-proxy-password");
this.fallBackProxyUri = outboundProxyConfig.getString("fallback-outbound-proxy-uri");
this.fallBackProxyUsername = outboundProxyConfig.getString("fallback-outbound-proxy-user");
this.fallBackProxyPassword = outboundProxyConfig.getString("fallback-outbound-proxy-password");
this.activeProxy = primaryProxyUri;
this.activeProxyUsername = primaryProxyUsername;
this.activeProxyPassword = primaryProxyPassword;
numberOfFailedCalls = new AtomicInteger();
numberOfFailedCalls.set(0);
useFallbackProxy = new AtomicBoolean();
useFallbackProxy.set(false);
allowFallback = outboundProxyConfig.getBoolean("allow-fallback", false);
maxNumberOfFailedCalls = outboundProxyConfig.getInt("max-failed-calls", 20);
allowFallbackToPrimary = outboundProxyConfig.getBoolean("allow-fallback-to-primary", false);
patchForNatB2BUASessions = runtime.getBoolean("patch-for-nat-b2bua-sessions", true);
//Monitoring Service
this.monitoring = (ActorRef) context.getAttribute(MonitoringService.class.getName());
extensions = ExtensionController.getInstance().getExtensions(ExtensionType.CallManager);
if (logger.isInfoEnabled()) {
logger.info("CallManager extensions: "+(extensions != null ? extensions.size() : "0"));
}
if(!runtime.subset("ims-authentication").isEmpty()){
final Configuration imsAuthentication = runtime.subset("ims-authentication");
this.actAsImsUa = imsAuthentication.getBoolean("act-as-ims-ua");
if (actAsImsUa) {
this.imsProxyAddress = imsAuthentication.getString("proxy-address");
this.imsProxyPort = imsAuthentication.getInt("proxy-port");
if (imsProxyPort == 0) {
imsProxyPort = DEFAUL_IMS_PROXY_PORT;
}
this.imsDomain = imsAuthentication.getString("domain");
this.imsAccount = imsAuthentication.getString("account");
if (actAsImsUa && (imsProxyAddress == null || imsProxyAddress.isEmpty()
|| imsDomain == null || imsDomain.isEmpty())) {
logger.warning("ims proxy-address or domain is not configured");
}
this.actAsImsUa = actAsImsUa && imsProxyAddress != null && !imsProxyAddress.isEmpty()
&& imsDomain != null && !imsDomain.isEmpty();
}
}
firstTimeCleanup();
}
private void firstTimeCleanup() {
if (logger.isInfoEnabled())
logger.info("Initial CallManager cleanup. Will check running state calls in DB and update state of the calls.");
String instanceId = RestcommConfiguration.getInstance().getMain().getInstanceId();
Sid sid = new Sid(instanceId);
final CallDetailRecordsDao callDetailRecordsDao = storage.getCallDetailRecordsDao();
callDetailRecordsDao.updateInCompleteCallDetailRecordsToCompletedByInstanceId(sid);
List results = callDetailRecordsDao.getInCompleteCallDetailRecordsByInstanceId(sid);
if (logger.isInfoEnabled()) {
logger.info("There are: " + results.size() + " calls in progress after cleanup.");
}
}
private ActorRef call() {
return system.actorOf(new Props(new UntypedActorFactory() {
private static final long serialVersionUID = 1L;
@Override
public UntypedActor create() throws Exception {
return new Call(sipFactory, msControllerFactory.provideCallController(), configuration);
}
}));
}
private void check(final Object message) throws IOException {
final SipServletRequest request = (SipServletRequest) message;
String content = new String(request.getRawContent());
if (request.getContentLength() == 0
|| !("application/sdp".equals(request.getContentType()) || content.contains("application/sdp"))) {
final SipServletResponse response = request.createResponse(SC_BAD_REQUEST);
response.send();
}
}
private void destroy(final Object message) throws Exception {
final UntypedActorContext context = getContext();
final DestroyCall request = (DestroyCall) message;
ActorRef call = request.call();
if (call != null) {
if(logger.isInfoEnabled()) {
logger.info("About to destroy call: "+request.call().path()+", call isTerminated(): "+sender().isTerminated()+", sender: "+sender());
}
context.stop(call);
}
}
private void invite(final Object message) throws IOException, NumberParseException, ServletParseException {
final ActorRef self = self();
final SipServletRequest request = (SipServletRequest) message;
// Make sure we handle re-invites properly.
if (!request.isInitial()) {
SipApplicationSession appSession = request.getApplicationSession();
ActorRef call = null;
if (appSession.getAttribute(Call.class.getName()) != null) {
call = (ActorRef) appSession.getAttribute(Call.class.getName());
}
if (call != null) {
if (logger.isInfoEnabled()) {
logger.info("For In-Dialog INVITE dispatched to Call actor: "+call.path());
}
call.tell(request, self);
return;
}
if (logger.isInfoEnabled()) {
logger.info("No call actor found will respond 200OK for In-dialog INVITE: "+request.getRequestURI().toString());
}
final SipServletResponse okay = request.createResponse(SC_OK);
okay.send();
return;
}
if (actAsImsUa) {
boolean isFromIms = isFromIms(request);
if (!isFromIms) {
//This is a WebRTC client that dials out to IMS
String user = request.getHeader("X-RestComm-Ims-User");
String pass = request.getHeader("X-RestComm-Ims-Password");
request.removeHeader("X-RestComm-Ims-User");
request.removeHeader("X-RestComm-Ims-Password");
imsProxyThroughMediaServer(request, null, request.getTo().getURI(), user, pass, isFromIms);
return;
} else {
//This is a IMS that dials out to WebRTC client
imsProxyThroughMediaServer(request, null, request.getTo().getURI(), "", "", isFromIms);
return;
}
}
//Run proInboundAction Extensions here
// If it's a new invite lets try to handle it.
final AccountsDao accounts = storage.getAccountsDao();
final ApplicationsDao applications = storage.getApplicationsDao();
// Try to find an application defined for the client.
final SipURI fromUri = (SipURI) request.getFrom().getURI();
String fromUser = fromUri.getUser();
final ClientsDao clients = storage.getClientsDao();
final Client client = clients.getClient(fromUser);
if (client != null) {
// Make sure we force clients to authenticate.
if (!authenticateUsers // https://github.com/Mobicents/RestComm/issues/29 Allow disabling of SIP authentication
|| CallControlHelper.checkAuthentication(request, storage)) {
// if the client has authenticated, try to redirect to the Client VoiceURL app
// otherwise continue trying to process the Client invite
if (redirectToClientVoiceApp(self, request, accounts, applications, client)) {
return;
} // else continue trying other ways to handle the request
} else {
// Since the client failed to authenticate, we will take no further action at this time.
return;
}
}
// TODO Enforce some kind of security check for requests coming from outside SIP UAs such as ITSPs that are not
// registered
final String toUser = CallControlHelper.getUserSipId(request, useTo);
final String ruri = ((SipURI) request.getRequestURI()).getHost();
final String toHost = ((SipURI) request.getTo().getURI()).getHost();
final String toHostIpAddress = InetAddress.getByName(toHost).getHostAddress();
final String toPort = String.valueOf(((SipURI) request.getTo().getURI()).getPort()).equalsIgnoreCase("-1") ? "5060"
: String.valueOf(((SipURI) request.getTo().getURI()).getHost());
final String transport = ((SipURI) request.getTo().getURI()).getTransportParam() == null ? "udp" : ((SipURI) request
.getTo().getURI()).getTransportParam();
SipURI outboundIntf = outboundInterface(transport);
if(logger.isInfoEnabled()) {
logger.info("ToHost: " + toHost);
logger.info("ruri: " + ruri);
logger.info("myHostIp: " + myHostIp);
logger.info("mediaExternalIp: " + mediaExternalIp);
logger.info("proxyIp: " + proxyIp);
}
if (client != null) { // make sure the caller is a registered client and not some external SIP agent that we have little control over
Client toClient = clients.getClient(toUser);
if (toClient != null) { // looks like its a p2p attempt between two valid registered clients, lets redirect to the b2bua
if(logger.isInfoEnabled()) {
logger.info("Client is not null: " + client.getLogin() + " will try to proxy to client: "+ toClient);
}
CallRequest callRequest = new CallRequest(fromUser, toUser, CallRequest.Type.CLIENT,
client.getAccountSid(), false, false);
if (executePreOutboundAction(callRequest)) {
if (B2BUAHelper.redirectToB2BUA(request, client, toClient, storage, sipFactory, patchForNatB2BUASessions)) {
if(logger.isInfoEnabled()) {
logger.info("Call to CLIENT. myHostIp: " + myHostIp + " mediaExternalIp: " + mediaExternalIp + " toHost: "
+ toHost + " fromClient: " + client.getUri() + " toClient: " + toClient.getUri());
}
// if all goes well with proxying the invitation on to the next client
// then we can end further processing of this INVITE
} else {
String errMsg = "Cannot Connect to Client: " + toClient.getFriendlyName()
+ " : Make sure the Client exist or is registered with Restcomm";
sendNotification(errMsg, 11001, "warning", true);
final SipServletResponse resp = request.createResponse(SC_NOT_FOUND, "Cannot complete P2P call");
resp.send();
}
} else {
//Extensions didn't allowed this call
if (logger.isDebugEnabled()) {
final String errMsg = "Client not Allowed to make this outbound call";
logger.debug(errMsg);
}
String errMsg = "Cannot Connect to Client: " + toClient.getFriendlyName()
+ " : Make sure the Client exist or is registered with Restcomm";
sendNotification(errMsg, 11001, "warning", true);
final SipServletResponse resp = request.createResponse(SC_FORBIDDEN, "Call not allowed");
resp.send();
}
executePostOutboundAction(callRequest);
return;
} else {
// toClient is null or we couldn't make the b2bua call to another client. check if this call is for a registered
// DID (application)
if (redirectToHostedVoiceApp(self, request, accounts, applications, toUser)) {
// This is a call to a registered DID (application)
return;
}
// This call is not a registered DID (application). Try to proxy out this call.
// log to console and to notification engine
String errMsg = "A Restcomm Client is trying to call a Number/DID that is not registered with Restcomm";
sendNotification(errMsg, 11002, "info", true);
if (isWebRTC(request)) {
//This is a WebRTC client that dials out
proxyThroughMediaServer(request, client, toUser);
return;
}
// https://telestax.atlassian.net/browse/RESTCOMM-335
final String proxyURI = activeProxy;
final String proxyUsername = activeProxyUsername;
final String proxyPassword = activeProxyPassword;
SipURI from = null;
SipURI to = null;
boolean callToSipUri = false;
// proxy DID or number if the outbound proxy fields are not empty in the restcomm.xml
if (proxyURI != null && !proxyURI.isEmpty()) {
// String destination = ((SipURI)request.getTo().getURI()).getUser();
CallRequest callRequest = new CallRequest(fromUser,toUser, CallRequest.Type.PSTN, client.getAccountSid(), false, false);
if (executePreOutboundAction(callRequest)) {
proxyOut(request, client, toUser, toHost, toHostIpAddress, toPort, outboundIntf, proxyURI, proxyUsername, proxyPassword, from, to, callToSipUri);
} else {
final SipServletResponse response = request.createResponse(SC_FORBIDDEN, "Call request not allowed");
response.send();
if (logger.isDebugEnabled()) {
logger.debug("Call request now allowed: "+callRequest.toString());
}
}
executePostOutboundAction(callRequest);
return;
} else {
String msg = "Restcomm tried to proxy this call to an outbound party but it seems the outbound proxy is not configured.";
sendNotification(errMsg, 11004, "warning", true);
}
}
} else {
// Client is null, check if this call is for a registered DID (application)
if (redirectToHostedVoiceApp(self, request, accounts, applications, toUser)) {
// This is a call to a registered DID (application)
return;
}
}
final SipServletResponse response = request.createResponse(SC_NOT_FOUND);
response.send();
// We didn't find anyway to handle the call.
String errMsg = "Restcomm cannot process this call because the destination number " + toUser
+ "cannot be found or there is application attached to that";
sendNotification(errMsg, 11005, "error", true);
}
private boolean proxyOut(SipServletRequest request, Client client, String toUser, String toHost, String toHostIpAddress, String toPort, SipURI outboundIntf, String proxyURI, String proxyUsername, String proxyPassword, SipURI from, SipURI to, boolean callToSipUri) throws UnknownHostException {
final Configuration runtime = configuration.subset("runtime-settings");
final boolean useLocalAddressAtFromHeader = runtime.getBoolean("use-local-address", false);
final boolean outboudproxyUserAtFromHeader = runtime.subset("outbound-proxy").getBoolean(
"outboudproxy-user-at-from-header", true);
final String fromHost = ((SipURI) request.getFrom().getURI()).getHost();
final String fromHostIpAddress = InetAddress.getByName(fromHost).getHostAddress();
// final String fromPort = String.valueOf(((SipURI) request.getFrom().getURI()).getPort()).equalsIgnoreCase("-1") ? "5060"
// : String.valueOf(((SipURI) request.getFrom().getURI()).getHost());
if(logger.isInfoEnabled()) {
logger.info("fromHost: " + fromHost + "fromHostIP: " + fromHostIpAddress + "myHostIp: " + myHostIp + " mediaExternalIp: " + mediaExternalIp
+ " toHost: " + toHost + " toHostIP: " + toHostIpAddress + " proxyUri: " + proxyURI);
}
if ((myHostIp.equalsIgnoreCase(toHost) || mediaExternalIp.equalsIgnoreCase(toHost)) ||
(myHostIp.equalsIgnoreCase(toHostIpAddress) || mediaExternalIp.equalsIgnoreCase(toHostIpAddress))
// https://github.com/RestComm/Restcomm-Connect/issues/1357
|| (fromHost.equalsIgnoreCase(toHost) || fromHost.equalsIgnoreCase(toHostIpAddress))
|| (fromHostIpAddress.equalsIgnoreCase(toHost) || fromHostIpAddress.equalsIgnoreCase(toHostIpAddress))) {
if(logger.isInfoEnabled()) {
logger.info("Call to NUMBER. myHostIp: " + myHostIp + " mediaExternalIp: " + mediaExternalIp
+ " toHost: " + toHost + " proxyUri: " + proxyURI);
}
try {
if (useLocalAddressAtFromHeader) {
if (outboudproxyUserAtFromHeader) {
from = (SipURI) sipFactory.createSipURI(proxyUsername,
mediaExternalIp + ":" + outboundIntf.getPort());
} else {
from = sipFactory.createSipURI(((SipURI) request.getFrom().getURI()).getUser(),
mediaExternalIp + ":" + outboundIntf.getPort());
}
} else {
if (outboudproxyUserAtFromHeader) {
// https://telestax.atlassian.net/browse/RESTCOMM-633. Use the outbound proxy username as
// the userpart of the sip uri for the From header
from = (SipURI) sipFactory.createSipURI(proxyUsername, proxyURI);
} else {
from = sipFactory.createSipURI(((SipURI) request.getFrom().getURI()).getUser(), proxyURI);
}
}
to = sipFactory.createSipURI(((SipURI) request.getTo().getURI()).getUser(), proxyURI);
} catch (Exception exception) {
if(logger.isInfoEnabled()) {
logger.info("Exception: " + exception);
}
}
} else {
if(logger.isInfoEnabled()) {
logger.info("Call to SIP URI. myHostIp: " + myHostIp + " mediaExternalIp: " + mediaExternalIp
+ " toHost: " + toHost + " proxyUri: " + proxyURI);
}
from = sipFactory.createSipURI(((SipURI) request.getFrom().getURI()).getUser(), outboundIntf.getHost()
+ ":" + outboundIntf.getPort());
to = sipFactory.createSipURI(toUser, toHost + ":" + toPort);
callToSipUri = true;
}
if (B2BUAHelper.redirectToB2BUA(request, client, from, to, proxyUsername, proxyPassword, storage,
sipFactory, callToSipUri, patchForNatB2BUASessions)) {
return true;
}
return false;
}
private boolean isWebRTC(final SipServletRequest request) {
String transport = request.getTransport();
String userAgent = request.getHeader(UserAgent.NAME);
//The check for request.getHeader(UserAgentHeader.NAME).equals("sipunit") has been added in order to be able to test this feature with sipunit at the Restcomm testsuite
if (userAgent != null && !userAgent.isEmpty() && userAgent.equalsIgnoreCase("wss-sipunit")) {
return true;
}
if (!request.getInitialTransport().equalsIgnoreCase(transport)) {
transport = request.getInitialTransport();
if ("ws".equalsIgnoreCase(transport) || "wss".equalsIgnoreCase(transport))
return true;
}
try {
if (SdpUtils.isWebRTCSDP(request.getContentType(), request.getRawContent())) {
return true;
}
} catch (SdpParseException e) {}
catch (IOException e) {}
return false;
}
private void proxyThroughMediaServer(final SipServletRequest request, final Client client, final String destNumber) {
String rcml = ""+destNumber+"";
final VoiceInterpreterBuilder builder = new VoiceInterpreterBuilder(system);
builder.setConfiguration(configuration);
builder.setStorage(storage);
builder.setCallManager(self());
builder.setConferenceManager(conferences);
builder.setBridgeManager(bridges);
builder.setSmsService(sms);
builder.setAccount(client.getAccountSid());
builder.setVersion(client.getApiVersion());
final Account account = storage.getAccountsDao().getAccount(client.getAccountSid());
builder.setEmailAddress(account.getEmailAddress());
builder.setRcml(rcml);
builder.setMonitoring(monitoring);
final ActorRef interpreter = builder.build();
final ActorRef call = call();
final SipApplicationSession application = request.getApplicationSession();
application.setAttribute(Call.class.getName(), call);
call.tell(request, self());
interpreter.tell(new StartInterpreter(call), self());
}
private void info(final SipServletRequest request) throws IOException {
final ActorRef self = self();
final SipApplicationSession application = request.getApplicationSession();
// if this response is coming from a client that is in a p2p session with another registered client
// we will just proxy the response
SipSession linkedB2BUASession = B2BUAHelper.getLinkedSession(request);
if (linkedB2BUASession != null) {
if (logger.isInfoEnabled()) {
logger.info(String.format("B2BUA: Got INFO request: \n %s", request));
}
request.getSession().setAttribute(B2BUAHelper.B2BUA_LAST_REQUEST, request);
SipServletRequest clonedInfo = linkedB2BUASession.createRequest("INFO");
linkedB2BUASession.setAttribute(B2BUAHelper.B2BUA_LAST_REQUEST, clonedInfo);
// Issue #307: https://telestax.atlassian.net/browse/RESTCOMM-307
SipURI toInetUri = (SipURI) request.getSession().getAttribute(B2BUAHelper.TO_INET_URI);
SipURI fromInetUri = (SipURI) request.getSession().getAttribute(B2BUAHelper.FROM_INET_URI);
InetAddress infoRURI = null;
try {
infoRURI = InetAddress.getByName(((SipURI) clonedInfo.getRequestURI()).getHost());
} catch (UnknownHostException e) {
}
if (patchForNatB2BUASessions) {
if (toInetUri != null && infoRURI == null) {
if(logger.isInfoEnabled()){
logger.info("Using the real ip address of the sip client " + toInetUri.toString()
+ " as a request uri of the CloneBye request");
}
clonedInfo.setRequestURI(toInetUri);
} else if (toInetUri != null
&& (infoRURI.isSiteLocalAddress() || infoRURI.isAnyLocalAddress() || infoRURI.isLoopbackAddress())) {
if(logger.isInfoEnabled()){
logger.info("Using the real ip address of the sip client " + toInetUri.toString()
+ " as a request uri of the CloneInfo request");
}
clonedInfo.setRequestURI(toInetUri);
} else if (fromInetUri != null
&& (infoRURI.isSiteLocalAddress() || infoRURI.isAnyLocalAddress() || infoRURI.isLoopbackAddress())) {
if(logger.isInfoEnabled()){
logger.info("Using the real ip address of the sip client " + fromInetUri.toString()
+ " as a request uri of the CloneInfo request");
}
clonedInfo.setRequestURI(fromInetUri);
}
}
clonedInfo.send();
} else {
final ActorRef call = (ActorRef) application.getAttribute(Call.class.getName());
call.tell(request, self);
}
}
private void transfer(SipServletRequest request) throws Exception {
//Transferor is the one that initates the transfer
String transferor = ((SipURI)request.getAddressHeader("Contact").getURI()).getUser();
//Transferee is the one that gets transfered
String transferee = ((SipURI)request.getAddressHeader("To").getURI()).getUser();
//Trasnfer target, where the transferee will be transfered
String transferTarget = ((SipURI)request.getAddressHeader("Refer-To").getURI()).getUser();
CallDetailRecord cdr = null;
CallDetailRecordsDao dao = storage.getCallDetailRecordsDao();
SipServletResponse servletResponse = null;
final SipApplicationSession appSession = request.getApplicationSession();
//Initates the transfer
ActorRef transferorActor = (ActorRef) appSession.getAttribute(Call.class.getName());
if (transferorActor == null) {
if (logger.isInfoEnabled()) {
logger.info("Transferor Call Actor is null, cannot proceed with SIP Refer");
}
servletResponse = request.createResponse(SC_NOT_FOUND);
servletResponse.setHeader("Reason", "SIP REFER should be sent in dialog");
servletResponse.setHeader("Event", "refer");
servletResponse.send();
return;
}
final Timeout expires = new Timeout(Duration.create(60, TimeUnit.SECONDS));
Future