Skip to content

Commit 5cd9dc7

Browse files
committed
Work in progress for Restcomm act as Proxy feature
This refer to #2286
1 parent a4dcece commit 5cd9dc7

File tree

7 files changed

+1147
-10
lines changed

7 files changed

+1147
-10
lines changed

Diff for: restcomm/restcomm.application/src/main/webapp/WEB-INF/conf/restcomm.xml

+25
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,31 @@
7474
<account></account>
7575
</ims-authentication>
7676

77+
<!-- If set to true, Restcomm will proxy out any call that doesn't match hosted Voice Application, to the proxy address:port -->
78+
<!-- using Dial SIP. If username and password are provided it will be added as part of the Dial SIP URI-->
79+
<!-- Using this feature, Restcomm will keep RMS in the path-->
80+
<acting-as-proxy>
81+
<enabled>false</enabled>
82+
<!-- If set to true, From header will be used to extract the From URI, otherwise Contact header will be used-->
83+
<use-from-header>true</use-from-header>
84+
<proxy-rules>
85+
<rule>
86+
<from-uri></from-uri>
87+
<to-uri></to-uri>
88+
<!-- Optionally provide username -->
89+
<proxy-to-username></proxy-to-username>
90+
<proxy-to-password></proxy-to-password>
91+
</rule>
92+
<rule>
93+
<from-uri></from-uri>
94+
<to-uri></to-uri>
95+
<!-- Optionally provide username -->
96+
<proxy-to-username></proxy-to-username>
97+
<proxy-to-password></proxy-to-password>
98+
</rule>
99+
</proxy-rules>
100+
</acting-as-proxy>
101+
77102
<!-- Interval time in seconds that Restcomm will send keepalive messages (OPTIONS) to registered clients -->
78103
<ping-interval>60</ping-interval>
79104

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.restcomm.connect.commons.telephony;
2+
3+
4+
/**
5+
* Created by gvagenas on 26/06/2017.
6+
*/
7+
public class ProxyRule {
8+
private final String fromUri;
9+
private final String toUri;
10+
private final String username;
11+
private final String password;
12+
13+
public ProxyRule (final String fromUri, final String toUri, final String username, final String password) {
14+
this.fromUri = fromUri;
15+
this.toUri = toUri;
16+
this.username = username;
17+
this.password = password;
18+
}
19+
20+
public String getFromUri () {
21+
return fromUri;
22+
}
23+
24+
public String getToUri () {
25+
return toUri;
26+
}
27+
28+
public String getPassword () {
29+
return password;
30+
}
31+
32+
public String getUsername () {
33+
return username;
34+
}
35+
}

Diff for: restcomm/restcomm.telephony/src/main/java/org/restcomm/connect/telephony/Call.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,7 @@ public void execute(final Object message) throws Exception {
11201120
ringing.send();
11211121
} catch (IllegalStateException exception) {
11221122
if(logger.isDebugEnabled()) {
1123-
logger.debug("Exception while creating 180 response to inbound invite request");
1123+
logger.debug("Exception while creating 180 response to inbound invite request, "+exception);
11241124
}
11251125
fsm.transition(message, canceled);
11261126
}

Diff for: restcomm/restcomm.telephony/src/main/java/org/restcomm/connect/telephony/CallManager.java

+114-9
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,18 @@
3030
import akka.event.Logging;
3131
import akka.event.LoggingAdapter;
3232
import akka.util.Timeout;
33-
3433
import com.google.i18n.phonenumbers.NumberParseException;
3534
import com.google.i18n.phonenumbers.PhoneNumberUtil;
3635
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
37-
3836
import gov.nist.javax.sip.header.UserAgent;
39-
4037
import org.apache.commons.configuration.Configuration;
38+
import org.apache.commons.configuration.HierarchicalConfiguration;
4139
import org.joda.time.DateTime;
4240
import org.restcomm.connect.commons.configuration.RestcommConfiguration;
4341
import org.restcomm.connect.commons.dao.Sid;
4442
import org.restcomm.connect.commons.patterns.StopObserving;
4543
import org.restcomm.connect.commons.telephony.CreateCallType;
44+
import org.restcomm.connect.commons.telephony.ProxyRule;
4645
import org.restcomm.connect.commons.util.SdpUtils;
4746
import org.restcomm.connect.commons.util.UriUtils;
4847
import org.restcomm.connect.dao.AccountsDao;
@@ -89,7 +88,6 @@
8988
import org.restcomm.connect.telephony.api.UpdateCallScript;
9089
import org.restcomm.connect.telephony.api.util.B2BUAHelper;
9190
import org.restcomm.connect.telephony.api.util.CallControlHelper;
92-
9391
import scala.concurrent.Await;
9492
import scala.concurrent.Future;
9593
import scala.concurrent.duration.Duration;
@@ -109,7 +107,6 @@
109107
import javax.servlet.sip.TelURL;
110108
import javax.sip.header.RouteHeader;
111109
import javax.sip.message.Response;
112-
113110
import java.io.IOException;
114111
import java.net.InetAddress;
115112
import java.net.URI;
@@ -199,6 +196,10 @@ public final class CallManager extends UntypedActor {
199196
private String imsDomain;
200197
private String imsAccount;
201198

199+
private boolean actAsProxyOut;
200+
private List<ProxyRule> proxyOutRules;
201+
private boolean isActAsProxyOutUseFromHeader;
202+
202203
// used for sending warning and error logs to notification engine and to the console
203204
private void sendNotification(String errMessage, int errCode, String errType, boolean createNotification) {
204205
NotificationsDao notifications = storage.getNotificationsDao();
@@ -321,6 +322,32 @@ public CallManager(final Configuration configuration, final ServletContext conte
321322
&& imsDomain != null && !imsDomain.isEmpty();
322323
}
323324
}
325+
if (!runtime.subset("acting-as-proxy").isEmpty() && !runtime.subset("acting-as-proxy").subset("proxy-rules").isEmpty()) {
326+
final Configuration proxyConfiguration = runtime.subset("acting-as-proxy");
327+
final Configuration proxyOutRulesConf = proxyConfiguration.subset("proxy-rules");
328+
this.actAsProxyOut = proxyConfiguration.getBoolean("enabled", false);
329+
if (actAsProxyOut) {
330+
isActAsProxyOutUseFromHeader = proxyConfiguration.getBoolean("use-from-header", true);
331+
proxyOutRules = new ArrayList<ProxyRule>();
332+
333+
List<HierarchicalConfiguration> rulesList = ((HierarchicalConfiguration)proxyOutRulesConf).configurationsAt("rule");
334+
for(HierarchicalConfiguration rule : rulesList){
335+
String fromHost = rule.getString("from-uri");
336+
String toHost = rule.getString("to-uri");
337+
final String username = rule.getString("proxy-to-username");
338+
final String password = rule.getString("proxy-to-password");
339+
ProxyRule proxyRule = new ProxyRule(fromHost, toHost, username, password);
340+
proxyOutRules.add(proxyRule);
341+
}
342+
343+
if (logger.isInfoEnabled()) {
344+
String msg = String.format("`ActAsProxy` feature is enabled with %d rules.",proxyOutRules.size());
345+
logger.info(msg);
346+
}
347+
348+
actAsProxyOut = actAsProxyOut && (proxyOutRules != null) && !proxyOutRules.isEmpty();
349+
}
350+
}
324351
firstTimeCleanup();
325352
}
326353

@@ -534,11 +561,12 @@ private void invite(final Object message) throws IOException, NumberParseExcepti
534561
IExtensionCreateCallRequest er = new CreateCall(fromUser, toUser, "", "", false, 0, CreateCallType.PSTN, client.getAccountSid(), null,null, null, null);
535562
ec.executePreOutboundAction(er, this.extensions);
536563
if (er.isAllowed()) {
537-
if (isWebRTC(request)) {
564+
if (actAsProxyOut) {
565+
processRequestAndProxyOut(request, client, toUser);
566+
} else if (isWebRTC(request)) {
538567
//This is a WebRTC client that dials out
539568
//TODO: should we inject headers for this case?
540-
proxyThroughMediaServer(request, client, toUser);
541-
569+
proxyThroughMediaServerAsNumber(request, client, toUser);
542570
} else {
543571
// https://telestax.atlassian.net/browse/RESTCOMM-335
544572
String proxyURI = activeProxy;
@@ -589,6 +617,10 @@ private void invite(final Object message) throws IOException, NumberParseExcepti
589617
// This is a call to a registered DID (application)
590618
return;
591619
}
620+
if (actAsProxyOut) {
621+
processRequestAndProxyOut(request, client, toUser);
622+
return;
623+
}
592624
}
593625
final SipServletResponse response = request.createResponse(SC_NOT_FOUND);
594626
response.send();
@@ -751,7 +783,80 @@ private boolean isWebRTC(final SipServletRequest request) {
751783
return false;
752784
}
753785

754-
private void proxyThroughMediaServer(final SipServletRequest request, final Client client, final String destNumber) {
786+
private void processRequestAndProxyOut (final SipServletRequest request, final Client client, final String destNumber) {
787+
String requestFromHost = null;
788+
789+
ProxyRule matchedProxyRule = null;
790+
791+
SipURI fromUri = null;
792+
try {
793+
if (isActAsProxyOutUseFromHeader) {
794+
fromUri = ((SipURI) request.getFrom().getURI());
795+
} else {
796+
fromUri = ((SipURI) request.getAddressHeader("Contact").getURI());
797+
}
798+
} catch (ServletParseException e) {
799+
logger.error("Problem while trying to process an `ActAsProxy` request, "+e);
800+
}
801+
requestFromHost = fromUri.getHost()+":"+fromUri.getPort();
802+
803+
for (ProxyRule proxyRule: proxyOutRules) {
804+
if (requestFromHost != null) {
805+
if (requestFromHost.equalsIgnoreCase(proxyRule.getFromUri())) {
806+
matchedProxyRule = proxyRule;
807+
break;
808+
}
809+
}
810+
}
811+
812+
if (matchedProxyRule != null) {
813+
String sipUri = String.format("sip:%s@%s", destNumber, matchedProxyRule.getToUri());
814+
String rcml;
815+
if (matchedProxyRule.getUsername() != null && !matchedProxyRule.getUsername().isEmpty() && matchedProxyRule.getPassword() != null && !matchedProxyRule.getPassword().isEmpty()) {
816+
rcml = String.format("<Response><Dial><Sip username=\"%s\" password=\"%s\">%s</Sip></Dial></Response>", matchedProxyRule.getUsername(), matchedProxyRule.getPassword(), sipUri);
817+
} else {
818+
rcml = String.format("<Response><Dial><Sip>%s</Sip></Dial></Response>", sipUri);
819+
}
820+
821+
final VoiceInterpreterBuilder builder = new VoiceInterpreterBuilder(system);
822+
builder.setConfiguration(configuration);
823+
builder.setStorage(storage);
824+
builder.setCallManager(self());
825+
builder.setConferenceManager(conferences);
826+
builder.setBridgeManager(bridges);
827+
builder.setSmsService(sms);
828+
829+
Sid accountSid = null;
830+
String apiVersion = null;
831+
if (client != null) {
832+
accountSid = client.getAccountSid();
833+
apiVersion = client.getApiVersion();
834+
} else {
835+
//Todo get Administrators account from RestcommConfiguration
836+
accountSid = new Sid("ACae6e420f425248d6a26948c17a9e2acf");
837+
apiVersion = RestcommConfiguration.getInstance().getMain().getApiVersion();
838+
}
839+
840+
builder.setAccount(accountSid);
841+
builder.setVersion(apiVersion);
842+
final Account account = storage.getAccountsDao().getAccount(accountSid);
843+
builder.setEmailAddress(account.getEmailAddress());
844+
builder.setRcml(rcml);
845+
builder.setMonitoring(monitoring);
846+
final ActorRef interpreter = builder.build();
847+
final ActorRef call = call(null);
848+
final SipApplicationSession application = request.getApplicationSession();
849+
application.setAttribute(Call.class.getName(), call);
850+
call.tell(request, self());
851+
interpreter.tell(new StartInterpreter(call), self());
852+
} else {
853+
if (logger.isInfoEnabled()) {
854+
logger.info("No rule matched for the `ActAsProxy` feature");
855+
}
856+
}
857+
}
858+
859+
private void proxyThroughMediaServerAsNumber (final SipServletRequest request, final Client client, final String destNumber) {
755860
String rcml = "<Response><Dial>"+destNumber+"</Dial></Response>";
756861
final VoiceInterpreterBuilder builder = new VoiceInterpreterBuilder(system);
757862
builder.setConfiguration(configuration);

0 commit comments

Comments
 (0)