Files
resiprocate/apps/reConServer/reConServer.cxx
2024-10-21 17:27:29 +01:00

1571 lines
61 KiB
C++

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef WIN32
#include <conio.h>
#else
/**
Linux (POSIX) implementation of _kbhit().
Morgan McGuire, morgan@cs.brown.edu
*/
#include <stdio.h>
#include <sys/select.h>
#include <termios.h>
#ifndef __GNUC__
#include <stropts.h>
#endif
#include <sys/ioctl.h>
int _kbhit() {
static const int STDIN = 0;
static bool initialized = false;
if (! initialized) {
// Use termios to turn off line buffering
termios term;
tcgetattr(STDIN, &term);
term.c_lflag &= ~ICANON;
tcsetattr(STDIN, TCSANOW, &term);
setbuf(stdin, NULL);
initialized = true;
}
int bytesWaiting;
ioctl(STDIN, FIONREAD, &bytesWaiting);
return bytesWaiting;
}
#endif
#include "resip/stack/InteropHelper.hxx"
#include "resip/recon/UserAgent.hxx"
#include "AppSubsystem.hxx"
#include "reConServerConfig.hxx"
#include "reConServer.hxx"
#include "MyMessageDecorator.hxx"
#include "MyConversationManager.hxx"
#ifdef BUILD_PYTHON
#include "PyConversationManager.hxx"
#endif
#include "B2BCallManager.hxx"
#include "CDRFile.hxx"
#include "RegistrationForwarder.hxx"
#include <rutil/Log.hxx>
#include <rutil/Logger.hxx>
#include <rutil/DnsUtil.hxx>
#include <rutil/BaseException.hxx>
#include <rutil/WinLeakCheck.hxx>
#include <resip/stack/HEPSipMessageLoggingHandler.hxx>
#include <reflow/HEPRTCPEventLoggingHandler.hxx>
#ifdef USE_SIPXTAPI
#include <resip/recon/SipXHelper.hxx>
#include <os/OsSysLog.h>
#include <resip/recon/SipXMediaStackAdapter.hxx>
#endif
using namespace reconserver;
using namespace recon;
using namespace resip;
using namespace flowmanager;
using namespace std;
#define RESIPROCATE_SUBSYSTEM AppSubsystem::RECONSERVER
void sleepSeconds(unsigned int seconds)
{
#ifdef WIN32
Sleep(seconds*1000);
#else
sleep(seconds);
#endif
}
static bool finished = false;
NameAddr uri("sip:noreg@127.0.0.1");
bool autoAnswerEnabled = false; // If enabled then reConServer will automatically answer incoming calls by adding to lowest numbered conversation
std::shared_ptr<ConversationProfile> conversationProfile;
static ReConServerConfig::MediaStack mediaStack = ReConServerConfig::sipXtapi;
int main(int argc, char** argv)
{
ReConServerProcess proc;
return proc.main(argc, argv);
}
ReConServerProcess::ReConServerProcess()
{
}
ReConServerProcess::~ReConServerProcess()
{
}
void ReConServerProcess::processCommandLine(Data& commandline, MyConversationManager& myConversationManager, MyUserAgent& myUserAgent)
{
Data command;
#define MAX_ARGS 5
Data arg[MAX_ARGS];
ParseBuffer pb(commandline);
pb.skipWhitespace();
if(pb.eof()) return;
const char *start = pb.position();
pb.skipToOneOf(ParseBuffer::Whitespace);
pb.data(command, start);
// Get arguments (up to MAX_ARGS)
int currentArg = 0;
while(!pb.eof() && currentArg < MAX_ARGS)
{
pb.skipWhitespace();
if(!pb.eof())
{
const char *start = pb.position();
pb.skipToOneOf(ParseBuffer::Whitespace);
pb.data(arg[currentArg++], start);
}
}
// Process commands
if(isEqualNoCase(command, "quit") || isEqualNoCase(command, "q") || isEqualNoCase(command, "exit"))
{
finished=true;
return;
}
if(isEqualNoCase(command, "createconv") || isEqualNoCase(command, "cc"))
{
myConversationManager.createConversation();
return;
}
if(isEqualNoCase(command, "destroyconv") || isEqualNoCase(command, "dc"))
{
unsigned long handle = arg[0].convertUnsignedLong();
if(handle != 0)
{
myConversationManager.destroyConversation(handle);
}
else
{
InfoLog( << "Invalid command format: <'destroyconv'|'dc'> <convHandle>");
}
return;
}
if(isEqualNoCase(command, "joinconv") || isEqualNoCase(command, "jc"))
{
unsigned long handleSrc = arg[0].convertUnsignedLong();
unsigned long handleDest = arg[1].convertUnsignedLong();
if(handleSrc != 0 && handleDest != 0)
{
myConversationManager.joinConversation(handleSrc, handleDest);
}
else
{
InfoLog( << "Invalid command format: <'joinconv'|'jc'> <sourceConvHandle> <destConvHandle>");
}
return;
}
if(isEqualNoCase(command, "createlocal") || isEqualNoCase(command, "clp"))
{
myConversationManager.createLocalParticipant();
return;
}
if(isEqualNoCase(command, "createremote") || isEqualNoCase(command, "crp"))
{
unsigned long handle = arg[0].convertUnsignedLong();
ConversationManager::ParticipantForkSelectMode mode = ConversationManager::ForkSelectAutomatic;
if(handle != 0 && !arg[1].empty())
{
if(!arg[2].empty() && isEqualNoCase(arg[2], "manual"))
{
mode = ConversationManager::ForkSelectManual;
}
try
{
NameAddr dest(arg[1]);
myConversationManager.createRemoteParticipant(handle, dest, mode);
}
catch(...)
{
NameAddr dest(uri);
dest.uri().user() = arg[1];
myConversationManager.createRemoteParticipant(handle, dest, mode);
}
}
else
{
InfoLog( << "Invalid command format: <'createremote'|'crp'> <convHandle> <destURI> [<'manual'>] (last arg is fork select mode, 'auto' is default).");
}
return;
}
if(isEqualNoCase(command, "createmedia") || isEqualNoCase(command, "cmp"))
{
unsigned long handle = arg[0].convertUnsignedLong();
unsigned long duration = arg[2].convertUnsignedLong();
if(handle != 0 && !arg[1].empty())
{
try
{
Uri url(arg[1]);
if(duration != 0)
{
url.param(p_duration) = duration;
}
myConversationManager.createMediaResourceParticipant(handle, url);
}
catch(resip::BaseException& e)
{
InfoLog( << "Invalid url format: <'createmedia'|'cmp'> <convHandle> <mediaURL> [<durationMs>]: " << e);
}
catch(...)
{
InfoLog( << "Invalid url format: <'createmedia'|'cmp'> <convHandle> <mediaURL> [<durationMs>]");
}
}
else
{
//myConversationManager.createMediaResourceParticipant(1, Uri("http://www.sillyhumor.com/answer/helloo.wav"));
InfoLog( << "Invalid command format: <'createmedia'|'cmp'> <convHandle> <mediaURL> [<durationMs>]");
}
return;
}
if(isEqualNoCase(command, "destroypart") || isEqualNoCase(command, "dp"))
{
unsigned long handle = arg[0].convertUnsignedLong();
if(handle != 0)
{
myConversationManager.destroyParticipant(handle);
}
else
{
InfoLog( << "Invalid command format: <'destroypart'|'dp'> <parthandle>");
}
return;
}
if(isEqualNoCase(command, "addpart") || isEqualNoCase(command, "ap"))
{
unsigned long convHandle = arg[0].convertUnsignedLong();
unsigned long partHandle = arg[1].convertUnsignedLong();
if(convHandle != 0 && partHandle != 0)
{
myConversationManager.addParticipant(convHandle, partHandle);
}
else
{
InfoLog( << "Invalid command format: <'addpart'|'ap'> <convHandle> <partHandle>");
}
return;
}
if(isEqualNoCase(command, "remotepart") || isEqualNoCase(command, "rp"))
{
unsigned long convHandle = arg[0].convertUnsignedLong();
unsigned long partHandle = arg[1].convertUnsignedLong();
if(convHandle != 0 && partHandle != 0)
{
myConversationManager.removeParticipant(convHandle, partHandle);
}
else
{
InfoLog( << "Invalid command format: <'removepart'|'rp'> <convHandle> <partHandle>");
}
return;
}
if(isEqualNoCase(command, "movepart") || isEqualNoCase(command, "mp"))
{
unsigned long partHandle = arg[0].convertUnsignedLong();
unsigned long srcConvHandle = arg[1].convertUnsignedLong();
unsigned long dstConvHandle = arg[2].convertUnsignedLong();
if(partHandle != 0 && srcConvHandle != 0 && dstConvHandle != 0)
{
myConversationManager.moveParticipant(partHandle, srcConvHandle, dstConvHandle);
}
else
{
InfoLog( << "Invalid command format: <'movepart'|'mp'> <partHandle> <srcConvHandle> <dstConvHandle>");
}
return;
}
if(isEqualNoCase(command, "partcontrib") || isEqualNoCase(command, "pc"))
{
unsigned long convHandle = arg[0].convertUnsignedLong();
unsigned long partHandle = arg[1].convertUnsignedLong();
if(partHandle != 0 && convHandle != 0)
{
myConversationManager.modifyParticipantContribution(convHandle, partHandle, arg[2].convertUnsignedLong(), arg[3].convertUnsignedLong());
}
else
{
InfoLog( << "Invalid command format: <'partcontrib'|'pc'> <convHandle> <partHandle> <inputGain> <outputGain> (gain in percentage)");
}
return;
}
if(isEqualNoCase(command, "bridgematrix") || isEqualNoCase(command, "bm"))
{
myConversationManager.outputBridgeMatrix();
return;
}
if(isEqualNoCase(command, "alert") || isEqualNoCase(command, "al"))
{
unsigned long partHandle = arg[0].convertUnsignedLong();
bool early = true;
if(partHandle != 0)
{
if(!arg[1].empty() && isEqualNoCase(arg[1], "noearly"))
{
early = false;
}
myConversationManager.alertParticipant(partHandle, early);
}
else
{
InfoLog( << "Invalid command format: <'alert'|'al'> <partHandle> [<'noearly'>] (last arg is early flag, enabled by default)");
}
return;
}
if(isEqualNoCase(command, "answer") || isEqualNoCase(command, "an"))
{
unsigned long partHandle = arg[0].convertUnsignedLong();
if(partHandle != 0)
{
myConversationManager.answerParticipant(partHandle);
}
else
{
InfoLog( << "Invalid command format: <'answer'|'an'> <partHandle>");
}
return;
}
if(isEqualNoCase(command, "reject") || isEqualNoCase(command, "rj"))
{
unsigned long partHandle = arg[0].convertUnsignedLong();
unsigned long status = arg[1].convertUnsignedLong();
if(partHandle != 0)
{
if(status == 0) status = 486;
myConversationManager.rejectParticipant(partHandle, status);
}
else
{
InfoLog( << "Invalid command format: <'reject'|'rj'> <partHandle> [<statusCode>] (default status code is 486)");
}
return;
}
if(isEqualNoCase(command, "redirect") || isEqualNoCase(command, "rd"))
{
unsigned long partHandle = arg[0].convertUnsignedLong();
if(partHandle != 0 && !arg[1].empty())
{
try
{
NameAddr dest(arg[1]);
myConversationManager.redirectParticipant(partHandle, dest);
}
catch(...)
{
NameAddr dest(uri);
dest.uri().user() = arg[1];
myConversationManager.redirectParticipant(partHandle, dest);
}
}
else
{
InfoLog( << "Invalid command format: <'redirect'|'rd'> <partHandle> <destURI>");
}
return;
}
if(isEqualNoCase(command, "redirectTo") || isEqualNoCase(command, "rt"))
{
unsigned long partHandle = arg[0].convertUnsignedLong();
unsigned long destPartHandle = arg[1].convertUnsignedLong();
if(partHandle != 0 && destPartHandle != 0)
{
myConversationManager.redirectToParticipant(partHandle, destPartHandle);
}
else
{
InfoLog( << "Invalid command format: <'redirectTo'|'rt'> <partHandle> <destPartHandle>");
}
return;
}
if(mediaStack == ReConServerConfig::sipXtapi)
{
#ifdef USE_SIPXTAPI
SipXMediaStackAdapter& mediaStackAdapter = static_cast<SipXMediaStackAdapter&>(myConversationManager.getMediaStackAdapter());
if(isEqualNoCase(command, "volume") || isEqualNoCase(command, "sv"))
{
unsigned long volume = arg[0].convertUnsignedLong();
mediaStackAdapter.setSpeakerVolume(volume);
InfoLog( << "Speaker volume set to " << volume);
return;
}
if(isEqualNoCase(command, "gain") || isEqualNoCase(command, "sg"))
{
unsigned long gain = arg[0].convertUnsignedLong();
mediaStackAdapter.setMicrophoneGain(gain);
InfoLog( << "Microphone gain set to " << gain);
return;
}
if(isEqualNoCase(command, "mute") || isEqualNoCase(command, "mm"))
{
bool enable = arg[0].convertUnsignedLong() != 0;
mediaStackAdapter.muteMicrophone(enable);
InfoLog( << "Microphone mute " << (enable ? "enabled" : "disabled"));
return;
}
if(isEqualNoCase(command, "echocanel") || isEqualNoCase(command, "aec"))
{
bool enable = arg[0].convertUnsignedLong() != 0;
mediaStackAdapter.enableEchoCancel(enable);
InfoLog( << "Echo cancellation " << (enable ? "enabled" : "disabled"));
return;
}
if(isEqualNoCase(command, "autogain") || isEqualNoCase(command, "agc"))
{
bool enable = arg[0].convertUnsignedLong() != 0;
mediaStackAdapter.enableAutoGainControl(enable);
InfoLog( << "Automatic gain control " << (enable ? "enabled" : "disabled"));
return;
}
if(isEqualNoCase(command, "noisereduction") || isEqualNoCase(command, "nr"))
{
bool enable = arg[0].convertUnsignedLong() != 0;
mediaStackAdapter.enableNoiseReduction(enable);
return;
}
#else
resip_assert(0);
#endif
}
if(isEqualNoCase(command, "subscribe") || isEqualNoCase(command, "cs"))
{
unsigned int subTime = arg[2].convertUnsignedLong();
if(!arg[0].empty() && !arg[1].empty() && subTime != 0 && !arg[3].empty() && !arg[4].empty())
{
try
{
NameAddr dest(arg[1]);
Mime mime(arg[3], arg[4]);
myUserAgent.createSubscription(arg[0], dest, subTime, mime);
}
catch(...)
{
NameAddr dest(uri);
Mime mime(arg[3], arg[4]);
dest.uri().user() = arg[1];
myUserAgent.createSubscription(arg[0], dest, subTime, mime);
}
}
else
{
InfoLog( << "Invalid command format: <'subscribe'|'cs'> <eventType> <targetUri> <subTime> <mimeType> <mimeSubType>");
}
return;
}
if(isEqualNoCase(command, "destsub") || isEqualNoCase(command, "ds"))
{
unsigned int subHandle = arg[0].convertUnsignedLong();
if(subHandle > 0)
{
myUserAgent.destroySubscription(subHandle);
}
else
{
InfoLog( << "Invalid command format: <'destsub'|'ds'> <subHandle>");
}
return;
}
if(isEqualNoCase(command, "autoans") || isEqualNoCase(command, "aa"))
{
bool enable = arg[0].convertUnsignedLong() != 0;
autoAnswerEnabled = enable;
InfoLog( << "Autoanswer " << (enable ? "enabled" : "disabled"));
return;
}
if(mediaStack == ReConServerConfig::sipXtapi)
{
#ifdef USE_SIPXTAPI
SipXMediaStackAdapter& mediaStackAdapter = static_cast<SipXMediaStackAdapter&>(myConversationManager.getMediaStackAdapter());
if(isEqualNoCase(command, "setcodecs") || isEqualNoCase(command, "sc"))
{
Data codecId;
std::vector<unsigned int> idList;
ParseBuffer pb(arg[0]);
pb.skipWhitespace();
while(!pb.eof())
{
const char *start = pb.position();
pb.skipToOneOf(ParseBuffer::Whitespace, ","); // white space or ","
pb.data(codecId, start);
idList.push_back(codecId.convertUnsignedLong());
if(!pb.eof())
{
pb.skipChar(',');
}
}
if(!idList.empty())
{
Data ipAddress(conversationProfile->sessionCaps().session().connection().getAddress());
// Note: Technically modifying the conversation profile at runtime like this is not
// thread safe. But it should be fine for this test consoles purposes.
mediaStackAdapter.buildSessionCapabilities(ipAddress, idList, conversationProfile->sessionCaps());
}
return;
}
#else
resip_assert(0);
#endif
}
if(isEqualNoCase(command, "securemedia") || isEqualNoCase(command, "sm"))
{
ConversationProfile::SecureMediaMode secureMediaMode = ConversationProfile::NoSecureMedia;
bool secureMediaRequired = false;
if(isEqualNoCase(arg[0], "Srtp"))
{
secureMediaMode = ConversationProfile::Srtp;
}
else if(isEqualNoCase(arg[0], "SrtpReq"))
{
secureMediaMode = ConversationProfile::Srtp;
secureMediaRequired = true;
}
#ifdef USE_SSL
else if(isEqualNoCase(arg[0], "SrtpDtls"))
{
secureMediaMode = ConversationProfile::SrtpDtls;
}
else if(isEqualNoCase(arg[0], "SrtpDtlsReq"))
{
secureMediaMode = ConversationProfile::SrtpDtls;
secureMediaRequired = true;
}
#endif
else
{
arg[0] = "None"; // for display output only
}
// Note: Technically modifying the conversation profile at runtime like this is not
// thread safe. But it should be fine for this test consoles purposes.
conversationProfile->secureMediaMode() = secureMediaMode;
conversationProfile->secureMediaRequired() = secureMediaRequired;
InfoLog( << "Secure media mode set to: " << arg[0]);
return;
}
if(isEqualNoCase(command, "natmode") || isEqualNoCase(command, "nm"))
{
ConversationProfile::NatTraversalMode natTraversalMode = ConversationProfile::NoNatTraversal;
if(isEqualNoCase(arg[0], "Bind"))
{
natTraversalMode = ConversationProfile::StunBindDiscovery;
}
else if(isEqualNoCase(arg[0], "UdpAlloc"))
{
natTraversalMode = ConversationProfile::TurnUdpAllocation;
}
else if(isEqualNoCase(arg[0], "TcpAlloc"))
{
natTraversalMode = ConversationProfile::TurnTcpAllocation;
}
#ifdef USE_SSL
else if(isEqualNoCase(arg[0], "TlsAlloc"))
{
natTraversalMode = ConversationProfile::TurnTlsAllocation;
}
#endif
else
{
arg[0] = "None"; // for display output only
}
// Note: Technically modifying the conversation profile at runtime like this is not
// thread safe. But it should be fine for this test consoles purposes.
conversationProfile->natTraversalMode() = natTraversalMode;
InfoLog( << "NAT traversal mode set to: " << arg[0]);
return;
}
if(isEqualNoCase(command, "natserver") || isEqualNoCase(command, "ns"))
{
Data natTraversalServerHostname;
unsigned short natTraversalServerPort = 3478;
// Read server and port
ParseBuffer pb(arg[0]);
pb.skipWhitespace();
const char *start = pb.position();
pb.skipToOneOf(ParseBuffer::Whitespace, ":"); // white space or ":"
pb.data(natTraversalServerHostname, start);
if(!pb.eof())
{
pb.skipChar(':');
start = pb.position();
pb.skipToOneOf(ParseBuffer::Whitespace); // white space
Data port;
pb.data(port, start);
natTraversalServerPort = port.convertUnsignedLong();
}
// Note: Technically modifying the conversation profile at runtime like this is not
// thread safe. But it should be fine for this test consoles purposes.
conversationProfile->natTraversalServerHostname() = natTraversalServerHostname;
conversationProfile->natTraversalServerPort() = natTraversalServerPort;
InfoLog( << "NAT traversal STUN/TURN server set to: " << natTraversalServerHostname << ":" << natTraversalServerPort);
return;
}
if(isEqualNoCase(command, "natuser") || isEqualNoCase(command, "nu"))
{
// Note: Technically modifying the conversation profile at runtime like this is not
// thread safe. But it should be fine for this test consoles purposes.
conversationProfile->stunUsername() = arg[0];
InfoLog( << "STUN/TURN user set to: " << arg[0]);
return;
}
if(isEqualNoCase(command, "natpwd") || isEqualNoCase(command, "np"))
{
// Note: Technically modifying the conversation profile at runtime like this is not
// thread safe. But it should be fine for this test consoles purposes.
conversationProfile->stunPassword() = arg[0];
InfoLog( << "STUN/TURN password set to: " << arg[0]);
return;
}
if(isEqualNoCase(command, "starttimer") || isEqualNoCase(command, "st"))
{
unsigned int timerId = arg[0].convertUnsignedLong();
unsigned int durationMs = arg[1].convertUnsignedLong();
unsigned int seqNumber = arg[2].convertUnsignedLong();
if(durationMs > 0)
{
myUserAgent.startApplicationTimer(timerId, std::chrono::milliseconds(durationMs), seqNumber);
InfoLog( << "Application Timer started for " << durationMs << "ms");
}
else
{
InfoLog( << "Invalid command format: <'starttimer'|'st'> <timerId> <durationMs> <seqNo>");
}
return;
}
if(isEqualNoCase(command, "info") || isEqualNoCase(command, "i"))
{
myConversationManager.displayInfo();
return;
}
if(isEqualNoCase(command, "dns") || isEqualNoCase(command, "ld"))
{
InfoLog( << "DNS cache (at WARNING log level):");
myUserAgent.logDnsCache();
return;
}
if(isEqualNoCase(command, "cleardns") || isEqualNoCase(command, "cd"))
{
myUserAgent.clearDnsCache();
InfoLog( << "DNS cache has been cleared.");
return;
}
#ifdef USE_SSL
Data setSecureMediaMode(" setSecureMediaMode <'securemedia'|'sm'> <'None'|'Srtp'|'SrtpReq'|'SrtpDtls'|'SrtpDtlsReq'>");
Data setNATTraversalMode(" setNATTraversalMode <'natmode'|'nm'> <'None'|'Bind'|'UdpAlloc'|'TcpAlloc'|'TlsAlloc'>" );
#else
Data setSecureMediaMode(" setSecureMediaMode <'securemedia'|'sm'> <'None'|'Srtp'|'SrtpReq'>");
Data setNATTraversalMode(" setNATTraversalMode <'natmode'|'nm'> <'None'|'Bind'|'UdpAlloc'|'TcpAlloc'>" );
#endif
InfoLog( << "Possible commands are: " << endl
<< " createConversation: <'createconv'|'cc'>" << endl
<< " destroyConversation: <'destroyconv'|'dc'> <convHandle>" << endl
<< " joinConversation: <'joinconv'|'jc'> <sourceConvHandle> <destConvHandle>" << endl
<< endl
<< " createLocalParticipant: <'createlocal'|'clp'>" << endl
<< " createRemoteParticipant: <'createremote'|'crp'> <convHandle> <destURI> [<'manual'>] (last arg is fork select mode, 'auto' is default)" << endl
<< " createMediaResourceParticipant: <'createmedia'|'cmp'> <convHandle> <mediaURL> [<durationMs>]" << endl
<< " destroyParticipant: <'destroypart'|'dp'> <parthandle>" << endl
<< endl
<< " addPartcipant: <'addpart'|'ap'> <convHandle> <partHandle>" << endl
<< " removePartcipant: <'removepart'|'rp'> <convHandle> <partHandle>" << endl
<< " moveParticipant: <'movepart'|'mp'> <partHandle> <srcConvHandle> <dstConvHandle>" << endl
<< " modifyParticipantContribution: <'partcontrib'|'pc'> <convHandle> <partHandle> <inputGain> <outputGain> (gain in percentage)" << endl
<< " outputBridgeMatrix: <'bridgematrix'|'bm'>" << endl
<< " alertPartcipant: <'alert'|'al'> <partHandle> [<'noearly'>] (last arg is early flag, enabled by default)" << endl
<< " answerParticipant: <'answer'|'an'> <partHandle>" << endl
<< " rejectParticipant: <'reject'|'rj'> <partHandle> [<statusCode>] (default status code is 486)" << endl
<< " redirectPartcipant: <'redirect'|'rd'> <partHandle> <destURI>" << endl
<< " redirectToPartcipant: <'redirectTo'|'rt'> <partHandle> <destPartHandle>" << endl
<< endl
<< " setSpeakerVolume: <'volume'|'sv'> <volume>" << endl
<< " setMicrophoneGain: <'gain'|'sg'> <gain>" << endl
<< " muteMicrophone: <'mute'|'mm'> <'0'|'1'> (1 to enable/mute)" << endl
<< " enableEchoCancel: <'echocancel'|'aec'> <'0'|'1'> (1 to enable)" << endl
<< " enableAutoGainControl: <'autogain'|'agc'> <'0'|'1'> (1 to enable)" << endl
<< " enableNoiseReduction: <'noisereduction'|'nr'> <'0'|'1'> (1 to enable)" << endl
<< endl
<< " createSubscription: <'subscribe'|'cs'> <eventType> <targetUri> <subTime> <mimeType> <mimeSubType>" << endl
<< " destroySubscription: <'destsub'|'ds'> <subHandle>" << endl
<< endl
<< " setAutoAnswer <'autoans'|'aa'> <'0'|'1'> (1 to enable (default))" << endl
<< " setCodecs <'setcodecs'|'sc'> <codecId>[,<codecId>]+ (comma separated list)" << endl
<< setSecureMediaMode << endl
<< setNATTraversalMode << endl
<< " setNATTraversalServer <'natserver'|'ns'> <server:port>" << endl
<< " setNATUsername <'natuser'|'nu'> <username>" << endl
<< " setNATPassword <'natpwd'|'np'> <password>" << endl
<< " startApplicationTimer: <'starttimer'|'st'> <timerId> <durationMs> <seqNo>" << endl
<< " displayInfo: <'info'|'i'>" << endl
<< " logDnsCache: <'dns'|'ld'>" << endl
<< " clearDnsCache: <'cleardns'|'cd'>" << endl
<< " exitProgram: <'exit'|'quit'|'q'>");
}
#define KBD_BUFFER_SIZE 256
void ReConServerProcess::processKeyboard(char input, MyConversationManager& myConversationManager, MyUserAgent& myUserAgent)
{
static char buffer[KBD_BUFFER_SIZE];
static int bufferpos = 0;
if(input == 13 || input == 10) // enter
{
Data db(buffer,bufferpos);
#ifdef WIN32
cout << endl;
#endif
processCommandLine(db, myConversationManager, myUserAgent);
bufferpos = 0;
}
else if(input == 8 || input == 127) // backspace
{
if(bufferpos > 0)
{
#ifdef WIN32
cout << input << ' ' << input;
#else
// note: This is bit of a hack and may not be portable to all linux terminal types
cout << "\b\b\b \b\b\b";
fflush(stdout);
#endif
bufferpos--;
}
}
else
{
if(bufferpos == KBD_BUFFER_SIZE)
{
cout << endl;
bufferpos = 0;
}
else
{
#ifdef WIN32
cout << input;
#endif
buffer[bufferpos++] = (char)input;
}
}
}
int
ReConServerProcess::main (int argc, char** argv)
{
installSignalHandler();
#if defined(WIN32) && defined(_DEBUG) && defined(LEAK_CHECK)
resip::FindMemoryLeaks fml;
{
#endif
Data defaultConfigFilename("reConServer.config");
ReConServerConfig reConServerConfig;
try
{
reConServerConfig.parseConfig(argc, argv, defaultConfigFilename);
}
catch(std::exception& e)
{
ErrLog(<< "Exception parsing configuration: " << e.what());
return -1;
}
Data pidFile = reConServerConfig.getConfigData("PidFile", "", true);
bool daemonize = reConServerConfig.getConfigBool("Daemonize", false);
mKeyboardInput = reConServerConfig.getConfigBool("KeyboardInput", !daemonize);
if(daemonize && mKeyboardInput)
{
ErrLog(<< "Ignoring KeyboardInput=true setting as we are running as a daemon");
mKeyboardInput = false;
}
setPidFile(pidFile);
// Daemonize if necessary
if(daemonize)
{
ReConServerProcess::daemonize();
}
autoAnswerEnabled = reConServerConfig.getConfigBool("EnableAutoAnswer", false);
bool registrationDisabled = reConServerConfig.getConfigBool("DisableRegistration", false);
bool keepAlivesDisabled = reConServerConfig.getConfigBool("DisableKeepAlives", false);
Data password = reConServerConfig.getConfigData("Password", "", true);
Data dnsServers = reConServerConfig.getConfigData("DNSServers", "", true);;
Data address = reConServerConfig.getConfigData("IPAddress", DnsUtil::getLocalIpAddress(), true);
bool delayedMediaOutboundMode = reConServerConfig.getConfigBool("DelayedMediaDialing", false);
ConversationProfile::MediaEndpointMode mediaEndpointMode = reConServerConfig.getConfigMediaEndpointMode("MediaEndpointMode", ConversationProfile::Base);
ConversationProfile::SecureMediaMode secureMediaMode = reConServerConfig.getConfigSecureMediaMode("SecureMediaMode", ConversationProfile::NoSecureMedia);
bool secureMediaRequired = reConServerConfig.isSecureMediaModeRequired();
ConversationProfile::NatTraversalMode natTraversalMode = reConServerConfig.getConfigNatTraversalMode("NatTraversalMode", ConversationProfile::NoNatTraversal);
bool forceCOMedia = reConServerConfig.getConfigBool("ForceCOMedia", true);
Data natTraversalServerHostname = reConServerConfig.getConfigData("NatTraversalServerHostname", "", true);
unsigned short natTraversalServerPort = reConServerConfig.getConfigUnsignedShort("NatTraversalServerPort", 3478);
Data stunUsername = reConServerConfig.getConfigData("StunUsername", "", true);
Data stunPassword = reConServerConfig.getConfigData("StunPassword", "", true);
bool addViaRport = reConServerConfig.getConfigBool("AddViaRport", true);
unsigned int maxReceiveFifoSize = reConServerConfig.getConfigInt("MaxReceiveFifoSize", 1000);
unsigned short tcpPort = reConServerConfig.getConfigUnsignedShort("TCPPort", 5062);
unsigned short udpPort = reConServerConfig.getConfigUnsignedShort("UDPPort", 5062);
unsigned short wsPort = reConServerConfig.getConfigUnsignedShort("WSPort", 5064);
unsigned short tlsPort = reConServerConfig.getConfigUnsignedShort("TLSPort", 5063);
unsigned short wssPort = reConServerConfig.getConfigUnsignedShort("WSSPort", 5065);
unsigned short mediaPortStart = reConServerConfig.getConfigUnsignedShort("MediaPortStart", 17384);
Data tlsDomain = reConServerConfig.getConfigData("TLSDomain", DnsUtil::getLocalHostName(), true);
NameAddr outboundProxy = reConServerConfig.getConfigNameAddr("OutboundProxyUri", NameAddr(), true);
#ifdef PACKAGE_VERSION
Data serverText = reConServerConfig.getConfigData("ServerText", "reConServer " PACKAGE_VERSION);
#else
Data serverText = reConServerConfig.getConfigData("ServerText", "reConServer");
#endif
uri = reConServerConfig.getConfigNameAddr("SIPUri", uri, true);
Data cdrLogFilename = reConServerConfig.getConfigData("CDRLogFile", "", true);
Data captureHost = reConServerConfig.getConfigData("CaptureHost", "");
int capturePort = reConServerConfig.getConfigInt("CapturePort", 9060);
int captureAgentID = reConServerConfig.getConfigInt("CaptureAgentID", 2002);
bool localAudioEnabled = reConServerConfig.getConfigBool("EnableLocalAudio", !daemonize); // Defaults to false for daemon process
Data runAsUser = reConServerConfig.getConfigData("RunAsUser", "", true);
Data runAsGroup = reConServerConfig.getConfigData("RunAsGroup", "", true);
#ifdef USE_SIPXTAPI
SipXMediaStackAdapter::MediaInterfaceMode mediaInterfaceMode = reConServerConfig.getConfigBool("GlobalMediaInterface", false)
? SipXMediaStackAdapter::sipXGlobalMediaInterfaceMode : SipXMediaStackAdapter::sipXConversationMediaInterfaceMode;
#endif
unsigned int defaultSampleRate = reConServerConfig.getConfigUnsignedLong("DefaultSampleRate", 8000);
unsigned int maximumSampleRate = reConServerConfig.getConfigUnsignedLong("MaximumSampleRate", 8000);
bool enableG722 = reConServerConfig.getConfigBool("EnableG722", false);
bool enableOpus = reConServerConfig.getConfigBool("EnableOpus", false);
unsigned int maximumVideoBandwidth = reConServerConfig.getConfigUnsignedLong("MaximumVideoBandwidth", 0);
Data kurentoUri = reConServerConfig.getConfigData("KurentoURI", "ws://127.0.0.1:8888/kurento");
ReConServerConfig::Application application = reConServerConfig.getConfigApplication("Application", ReConServerConfig::None);
mediaStack = reConServerConfig.getConfigMediaStack("MediaStack", ReConServerConfig::sipXtapi);
// build a list of codecs in priority order
// Used by ConversationManager::buildSessionCapabilities(...) to create
// our local SDP
std::vector<unsigned int> _codecIds;
#ifdef USE_SIPXTAPI
if(enableOpus)
{
_codecIds.push_back(SdpCodec::SDP_CODEC_OPUS); // Opus
}
if(enableG722)
{
_codecIds.push_back(SdpCodec::SDP_CODEC_G722); // 9 - G.722
}
_codecIds.push_back(SdpCodec::SDP_CODEC_ILBC); // 108 - iLBC
_codecIds.push_back(SdpCodec::SDP_CODEC_ILBC_20MS); // 109 - Internet Low Bit Rate Codec, 20ms (RFC3951)
_codecIds.push_back(SdpCodec::SDP_CODEC_SPEEX_24); // 99 - speex NB 24,600bps
_codecIds.push_back(SdpCodec::SDP_CODEC_SPEEX_15); // 98 - speex NB 15,000bps
_codecIds.push_back(SdpCodec::SDP_CODEC_SPEEX); // 96 - speex NB 8,000bps
_codecIds.push_back(SdpCodec::SDP_CODEC_SPEEX_5); // 97 - speex NB 5,950bps
_codecIds.push_back(SdpCodec::SDP_CODEC_GSM); // 3 - GSM
//_codecIds.push_back(SdpCodec::SDP_CODEC_L16_44100_MONO); // PCM 16 bit/sample 44100 samples/sec.
_codecIds.push_back(SdpCodec::SDP_CODEC_PCMU); // 0 - pcmu
_codecIds.push_back(SdpCodec::SDP_CODEC_PCMA); // 8 - pcma
_codecIds.push_back(SdpCodec::SDP_CODEC_G729); // 18 - G.729
_codecIds.push_back(SdpCodec::SDP_CODEC_TONES); // 110 - telephone-event
#endif
Log::initialize(reConServerConfig, argv[0]);
if(mediaStack == ReConServerConfig::sipXtapi)
{
#ifdef USE_SIPXTAPI
// Setup logging for the sipX media stack
// It is bridged to the reSIProcate logger
SipXHelper::setupLoggingBridge("reConServer");
#else
resip_assert(0);
#endif
}
//UserAgent::setLogLevel(Log::Warning, UserAgent::SubsystemAll);
//UserAgent::setLogLevel(Log::Info, UserAgent::SubsystemRecon);
initNetwork();
InfoLog( << "reConServer settings:");
InfoLog( << " No Keepalives = " << (keepAlivesDisabled ? "true" : "false"));
InfoLog( << " Autoanswer = " << (autoAnswerEnabled ? "true" : "false"));
InfoLog( << " Do not register = " << (registrationDisabled ? "true" : "false"));
InfoLog( << " Local IP Address = " << address);
InfoLog( << " SIP URI = " << uri);
InfoLog( << " SIP Password = " << password);
InfoLog( << " Override DNS Servers = " << dnsServers);
InfoLog( << " Secure Media Mode = " << secureMediaMode);
InfoLog( << " NAT Traversal Mode = " << natTraversalMode);
InfoLog( << " NAT Server = " << natTraversalServerHostname << ":" << natTraversalServerPort);
InfoLog( << " Max RTP receive FIFO size = " << maxReceiveFifoSize);
InfoLog( << " STUN/TURN user = " << stunUsername);
InfoLog( << " STUN/TURN password = " << stunPassword);
InfoLog( << " TCP Port = " << tcpPort);
InfoLog( << " UDP Port = " << udpPort);
InfoLog( << " WS Port = " << wsPort);
InfoLog( << " Media Port Range Start = " << mediaPortStart);
#ifdef USE_SSL
InfoLog( << " TLS Port = " << tlsPort);
InfoLog( << " TLS Domain = " << tlsDomain);
InfoLog( << " WSS Port = " << wssPort);
#endif
InfoLog( << " Outbound Proxy = " << outboundProxy);
InfoLog( << " Local Audio Enabled = " << (localAudioEnabled ? "true" : "false"));
#ifdef USE_SIPXTAPI
InfoLog( << " Global Media Interface = " <<
((mediaInterfaceMode == SipXMediaStackAdapter::sipXGlobalMediaInterfaceMode) ? "true" : "false"));
#endif
InfoLog( << " Default sample rate = " << defaultSampleRate);
InfoLog( << " Maximum sample rate = " << maximumSampleRate);
InfoLog( << " Enable G.722 codec = " << (enableG722 ? "true" : "false"));
InfoLog( << " Enable Opus codec = " << (enableOpus ? "true" : "false"));
InfoLog( << " Kurento URI = " << kurentoUri);
InfoLog( << " Daemonize = " << (daemonize ? "true" : "false"));
InfoLog( << " KeyboardInput = " << (mKeyboardInput ? "true" : "false"));
InfoLog( << " PidFile = " << pidFile);
InfoLog( << " Run as user = " << runAsUser);
InfoLog( << " Run as group = " << runAsGroup);
InfoLog( << "type help or '?' for list of accepted commands." << endl);
//////////////////////////////////////////////////////////////////////////////
// Setup UserAgentMasterProfile
//////////////////////////////////////////////////////////////////////////////
const auto profile = std::make_shared<UserAgentMasterProfile>();
Data certPath;
reConServerConfig.getConfigValue("CertificatePath", certPath);
if(!certPath.empty())
{
profile->certPath() = certPath;
}
Data caDir;
reConServerConfig.getConfigValue("CADirectory", caDir);
if(!caDir.empty())
{
profile->rootCertDirectories().push_back(caDir);
}
Data caFile;
reConServerConfig.getConfigValue("CAFile", caFile);
if(!caFile.empty())
{
profile->rootCertBundles().push_back(caFile);
}
if(!captureHost.empty())
{
const auto agent = std::make_shared<HepAgent>(captureHost, capturePort, captureAgentID);
profile->setTransportSipMessageLoggingHandler(std::make_shared<HEPSipMessageLoggingHandler>(agent));
profile->setRTCPEventLoggingHandler(std::make_shared<HEPRTCPEventLoggingHandler>(agent));
}
// Add transports
try
{
bool useEmailAsSIP = reConServerConfig.getConfigBool("TLSUseEmailAsSIP", false);
// Check if advanced transport settings are provided
ConfigParse::NestedConfigMap m = reConServerConfig.getConfigNested("Transport");
DebugLog(<<"Found " << m.size() << " interface(s) defined in the advanced format");
if(!m.empty())
{
// Sample config file format for advanced transport settings
// Transport1Interface = 192.168.1.106:5061
// Transport1Type = TLS
// Transport1TlsDomain = sipdomain.com
// Transport1TlsCertificate = /etc/ssl/crt/sipdomain.com.pem
// Transport1TlsPrivateKey = /etc/ssl/private/sipdomain.com.pem
// Transport1TlsPrivateKeyPassPhrase = <pwd>
// Transport1TlsClientVerification = None
// Transport1RcvBufLen = 2000
const char *anchor;
for(ConfigParse::NestedConfigMap::iterator it = m.begin();
it != m.end();
it++)
{
int idx = it->first;
SipConfigParse tc(it->second);
Data transportPrefix = "Transport" + Data(idx);
DebugLog(<< "checking values for transport: " << idx);
Data interfaceSettings = tc.getConfigData("Interface", Data::Empty, true);
// Parse out interface settings
ParseBuffer pb(interfaceSettings);
anchor = pb.position();
pb.skipToEnd();
pb.skipBackToChar(':'); // For IPv6 the last : should be the port
pb.skipBackChar();
if(!pb.eof())
{
Data ipAddr;
Data portData;
pb.data(ipAddr, anchor);
pb.skipChar();
anchor = pb.position();
pb.skipToEnd();
pb.data(portData, anchor);
if(!DnsUtil::isIpAddress(ipAddr))
{
CritLog(<< "Malformed IP-address found in " << transportPrefix << "Interface setting: " << ipAddr);
}
int port = portData.convertInt();
if(port == 0)
{
CritLog(<< "Invalid port found in " << transportPrefix << " setting: " << port);
}
TransportType tt = Tuple::toTransport(tc.getConfigData("Type", "UDP"));
if(tt == UNKNOWN_TRANSPORT)
{
CritLog(<< "Unknown transport type found in " << transportPrefix << "Type setting: " << tc.getConfigData("Type", "UDP"));
}
Data tlsDomain = tc.getConfigData("TlsDomain", Data::Empty);
Data tlsCertificate = tc.getConfigData("TlsCertificate", Data::Empty);
Data tlsPrivateKey = tc.getConfigData("TlsPrivateKey", Data::Empty);
Data tlsPrivateKeyPassPhrase = tc.getConfigData("TlsPrivateKeyPassPhrase", Data::Empty);
SecurityTypes::TlsClientVerificationMode cvm = tc.getConfigClientVerificationMode("TlsClientVerification", SecurityTypes::None);
SecurityTypes::SSLType sslType = SecurityTypes::NoSSL;
#ifdef USE_SSL
sslType = tc.getConfigSSLType("TlsConnectionMethod", SecurityTypes::SSLv23);
#endif
int rcvBufLen = tc.getConfigInt("RcvBufLen", 0);
profile->addTransport(tt,
port,
DnsUtil::isIpV6Address(ipAddr) ? V6 : V4,
StunEnabled,
ipAddr, // interface to bind to
tlsDomain,
tlsPrivateKeyPassPhrase, // private key passphrase
sslType, // sslType
0, // transport flags
tlsCertificate, tlsPrivateKey,
cvm, // tls client verification mode
useEmailAsSIP, rcvBufLen);
}
else
{
CritLog(<< "Port not specified in " << transportPrefix << " setting: expected format is <IPAddress>:<Port>");
return false;
}
}
}
else
{
DebugLog(<<"Using legacy transport configuration");
if(udpPort)
{
profile->addTransport(UDP, udpPort, V4, StunEnabled, address, Data::Empty, Data::Empty, SecurityTypes::SSLv23, 0, Data::Empty, Data::Empty, SecurityTypes::None, useEmailAsSIP);
}
if(tcpPort)
{
profile->addTransport(TCP, tcpPort, V4, StunEnabled, address, Data::Empty, Data::Empty, SecurityTypes::SSLv23, 0, Data::Empty, Data::Empty, SecurityTypes::None, useEmailAsSIP);
}
if(wsPort)
{
profile->addTransport(WS, wsPort, V4, StunEnabled, address, Data::Empty, Data::Empty, SecurityTypes::SSLv23, 0, Data::Empty, Data::Empty, SecurityTypes::None, useEmailAsSIP);
}
#ifdef USE_SSL
if(tlsPort)
{
profile->addTransport(TLS, tlsPort, V4, StunEnabled, address, tlsDomain, Data::Empty, SecurityTypes::SSLv23, 0, Data::Empty, Data::Empty, SecurityTypes::None, useEmailAsSIP);
}
if(wssPort)
{
profile->addTransport(WSS, wssPort, V4, StunEnabled, address, Data::Empty, Data::Empty, SecurityTypes::SSLv23, 0, Data::Empty, Data::Empty, SecurityTypes::None, useEmailAsSIP);
}
#endif
}
}
catch (BaseException& e)
{
std::cerr << "Likely a port is already in use" << endl;
InfoLog (<< "Caught: " << e);
return false;
}
// The following settings are used to avoid a kernel panic seen on an ARM embedded platform.
// The kernel panic happens when either binding a udp socket to port 0 (OS selected),
// or calling connect without first binding to a specific port. There is code in the
// resip transport selector that uses a utility UDP socket in order to determine
// which interface should be used to route to a particular destination. This code calls
// connect with no bind. By setting a fixed transport interface here that
// code will not be used.
// The following line can be safely removed for other platforms
//profile->setFixedTransportInterface(address);
// Settings
profile->setDefaultRegistrationTime(3600);
profile->setDefaultFrom(uri);
profile->setDigestCredential(uri.uri().host(), uri.uri().user(), password);
// DNS Servers
ParseBuffer pb(dnsServers);
Data dnsServer;
while(!dnsServers.empty() && !pb.eof())
{
pb.skipWhitespace();
const char *start = pb.position();
pb.skipToOneOf(ParseBuffer::Whitespace, ";,"); // allow white space
pb.data(dnsServer, start);
if(DnsUtil::isIpV4Address(dnsServer))
{
InfoLog( << "Adding DNS Server: " << dnsServer);
profile->addAdditionalDnsServer(dnsServer);
}
else
{
ErrLog( << "Tried to add dns server, but invalid format: " << dnsServer);
}
if(!pb.eof())
{
pb.skipChar();
}
}
// Disable Statisitics Manager
profile->statisticsManagerEnabled() = false;
// Add ENUM Suffixes from setting string - use code similar to dns server
//profile->addEnumSuffix(enumSuffix);
if(!keepAlivesDisabled)
{
profile->setKeepAliveTimeForDatagram(30);
profile->setKeepAliveTimeForStream(180);
}
// Support Methods, etc.
profile->validateContentEnabled() = false;
profile->validateContentLanguageEnabled() = false;
profile->validateAcceptEnabled() = false;
profile->clearSupportedLanguages();
profile->addSupportedLanguage(Token("en"));
profile->clearSupportedMimeTypes();
profile->addSupportedMimeType(INVITE, Mime("application", "sdp"));
profile->addSupportedMimeType(INVITE, Mime("multipart", "mixed"));
profile->addSupportedMimeType(INVITE, Mime("multipart", "signed"));
profile->addSupportedMimeType(INVITE, Mime("multipart", "alternative"));
profile->addSupportedMimeType(OPTIONS,Mime("application", "sdp"));
profile->addSupportedMimeType(OPTIONS,Mime("multipart", "mixed"));
profile->addSupportedMimeType(OPTIONS, Mime("multipart", "signed"));
profile->addSupportedMimeType(OPTIONS, Mime("multipart", "alternative"));
profile->addSupportedMimeType(PRACK, Mime("application", "sdp"));
profile->addSupportedMimeType(PRACK, Mime("multipart", "mixed"));
profile->addSupportedMimeType(PRACK, Mime("multipart", "signed"));
profile->addSupportedMimeType(PRACK, Mime("multipart", "alternative"));
profile->addSupportedMimeType(UPDATE, Mime("application", "sdp"));
profile->addSupportedMimeType(UPDATE, Mime("multipart", "mixed"));
profile->addSupportedMimeType(UPDATE, Mime("multipart", "signed"));
profile->addSupportedMimeType(UPDATE, Mime("multipart", "alternative"));
profile->addSupportedMimeType(NOTIFY, Mime("message", "sipfrag"));
profile->addSupportedMimeType(INFO, Mime("application", "dtmf-relay"));
profile->clearSupportedMethods();
profile->addSupportedMethod(INVITE);
profile->addSupportedMethod(ACK);
profile->addSupportedMethod(CANCEL);
profile->addSupportedMethod(OPTIONS);
profile->addSupportedMethod(BYE);
profile->addSupportedMethod(REFER);
profile->addSupportedMethod(NOTIFY);
profile->addSupportedMethod(SUBSCRIBE);
profile->addSupportedMethod(UPDATE);
profile->addSupportedMethod(PRACK);
profile->addSupportedMethod(INFO);
//profile->addSupportedMethod(MESSAGE);
profile->clearSupportedOptionTags();
profile->addSupportedOptionTag(Token(Symbols::Replaces));
profile->addSupportedOptionTag(Token(Symbols::Timer));
profile->addSupportedOptionTag(Token(Symbols::NoReferSub));
profile->addSupportedOptionTag(Token(Symbols::AnswerMode));
profile->addSupportedOptionTag(Token(Symbols::TargetDialog));
//profile->addSupportedOptionTag(Token(Symbols::C100rel)); // Automatically added by calling setUacReliableProvisionalMode
profile->setUacReliableProvisionalMode(MasterProfile::Supported);
profile->clearSupportedSchemes();
profile->addSupportedScheme("sip");
#ifdef USE_SSL
profile->addSupportedScheme("sips");
#endif
// Have stack add Allow/Supported/Accept headers to INVITE dialog establishment messages
profile->clearAdvertisedCapabilities(); // Remove Profile Defaults, then add our preferences
profile->addAdvertisedCapability(Headers::Allow);
//profile->addAdvertisedCapability(Headers::AcceptEncoding); // This can be misleading - it might specify what is expected in response
profile->addAdvertisedCapability(Headers::AcceptLanguage);
profile->addAdvertisedCapability(Headers::Supported);
profile->setMethodsParamEnabled(true);
//profile->setOverrideHostAndPort(mContact);
if(!outboundProxy.uri().host().empty())
{
profile->setOutboundProxy(outboundProxy.uri());
}
profile->setUserAgent(serverText);
profile->rtpPortRangeMin() = mediaPortStart;
profile->rtpPortRangeMax() = mediaPortStart + 101; // Allows 100 media streams
if(natTraversalMode == ConversationProfile::NoNatTraversal)
{
StackLog(<<"NAT traversal features not enabled, "
"adding message decorator for SDP connection address");
profile->setOutboundDecorator(std::make_shared<MyMessageDecorator>());
}
//////////////////////////////////////////////////////////////////////////////
// Setup ConversationProfile
//////////////////////////////////////////////////////////////////////////////
conversationProfile = std::make_shared<ConversationProfile>(profile);
if(uri.uri().user() != "noreg" && !registrationDisabled)
{
conversationProfile->setDefaultRegistrationTime(3600);
}
else
{
conversationProfile->setDefaultRegistrationTime(0);
}
conversationProfile->setDefaultRegistrationRetryTime(120); // 2 mins
conversationProfile->setDefaultFrom(uri);
conversationProfile->setDigestCredential(uri.uri().host(), uri.uri().user(), password);
#if 0 // Now auto-built
// Create Session Capabilities and assign to coversation Profile
// Note: port, sessionId and version will be replaced in actual offer/answer int port = 16384;
// Build s=, o=, t=, and c= lines
SdpContents::Session::Origin origin("-", 0 /* sessionId */, 0 /* version */, SdpContents::IP4, address); // o=
SdpContents::Session session(0, origin, "-" /* s= */);
session.connection() = SdpContents::Session::Connection(SdpContents::IP4, address); // c=
session.addTime(SdpContents::Session::Time(0, 0));
// Build Codecs and media offering
SdpContents::Session::Medium medium("audio", port, 1, "RTP/AVP");
if(enableOpus)
{
// Note: the other constructors (e.g. g722 above) are
// invoked incorrectly, payload format should be the second
// argument or the rate is used as the payload format and
// the desired rate is not used at all
SdpContents::Session::Codec opuscodec("OPUS", 96, 48000);
opuscodec.encodingParameters() = Data("2");
medium.addCodec(opuscodec);
}
if(enableG722)
{
SdpContents::Session::Codec g722codec("G722", 8000);
g722codec.payloadType() = 9; /* RFC3551 */ ;
medium.addCodec(g722codec);
}
SdpContents::Session::Codec speexCodec("SPEEX", 8000);
speexCodec.payloadType() = 110;
speexCodec.parameters() = Data("mode=3");
medium.addCodec(speexCodec);
SdpContents::Session::Codec gsmCodec("GSM", 8000);
gsmCodec.payloadType() = 3; /* RFC3551 */ ;
medium.addCodec(gsmCodec);
SdpContents::Session::Codec g711ucodec("PCMU", 8000);
g711ucodec.payloadType() = 0; /* RFC3551 */ ;
medium.addCodec(g711ucodec);
SdpContents::Session::Codec g711acodec("PCMA", 8000);
g711acodec.payloadType() = 8; /* RFC3551 */ ;
medium.addCodec(g711acodec);
SdpContents::Session::Codec g729codec("G729", 8000);
g729codec.payloadType() = 18; /* RFC3551 */ ;
medium.addCodec(g729codec);
medium.addAttribute("ptime", Data(20)); // 20 ms of speech per frame (note G711 has 10ms samples, so this is 2 samples per frame)
medium.addAttribute("sendrecv");
SdpContents::Session::Codec toneCodec("telephone-event", 8000);
toneCodec.payloadType() = 102;
toneCodec.parameters() = Data("0-15");
medium.addCodec(toneCodec);
session.addMedium(medium);
conversationProfile->sessionCaps().session() = session;
#endif
conversationProfile->maximumVideoBandwidth() = maximumVideoBandwidth;
// Setup NatTraversal Settings
conversationProfile->natTraversalMode() = natTraversalMode;
conversationProfile->forceCOMedia() = forceCOMedia;
conversationProfile->natTraversalServerHostname() = natTraversalServerHostname;
conversationProfile->natTraversalServerPort() = natTraversalServerPort;
conversationProfile->stunUsername() = stunUsername;
conversationProfile->stunPassword() = stunPassword;
InteropHelper::setRportEnabled(addViaRport);
conversationProfile->setRportEnabled(addViaRport);
// FIXME - we may need to do more to support this, alternatively, maybe we
// just do everything from behind a proxy that does it for us
InteropHelper::setOutboundSupported(reConServerConfig.getConfigBool("DisableOutbound", false) ? false : true);
InteropHelper::setRRTokenHackEnabled(reConServerConfig.getConfigBool("EnableFlowTokens", false));
InteropHelper::setAllowInboundFlowTokensForNonDirectClients(reConServerConfig.getConfigBool("AllowInboundFlowTokensForNonDirectClients", false));
InteropHelper::setAssumeFirstHopSupportsOutboundEnabled(reConServerConfig.getConfigBool("AssumeFirstHopSupportsOutbound", false));
InteropHelper::setAssumeFirstHopSupportsFlowTokensEnabled(reConServerConfig.getConfigBool("AssumeFirstHopSupportsFlowTokens", false));
// Delayed media settings
conversationProfile->delayedMediaOutboundMode() = delayedMediaOutboundMode;
// WebRTC settings.
conversationProfile->mediaEndpointMode() = mediaEndpointMode;
// Secure Media Settings
conversationProfile->secureMediaMode() = secureMediaMode;
conversationProfile->secureMediaRequired() = secureMediaRequired;
conversationProfile->secureMediaDefaultCryptoSuite() = ConversationProfile::SRTP_AES_CM_128_HMAC_SHA1_80;
#ifdef USE_SIPXTAPI
Flow::maxReceiveFifoSize = maxReceiveFifoSize;
#endif
//////////////////////////////////////////////////////////////////////////////
// Create ConverationManager and UserAgent
//////////////////////////////////////////////////////////////////////////////
{
B2BCallManager *b2BCallManager = 0;
switch(application)
{
case ReConServerConfig::None:
mConversationManager = std::unique_ptr<MyConversationManager>(new MyConversationManager(reConServerConfig, localAudioEnabled, defaultSampleRate, maximumSampleRate, autoAnswerEnabled));
break;
case ReConServerConfig::B2BUA:
{
if(!cdrLogFilename.empty())
{
mCDRFile = std::make_shared<CDRFile>(cdrLogFilename);
}
b2BCallManager = new B2BCallManager(reConServerConfig, defaultSampleRate, maximumSampleRate, mCDRFile);
mConversationManager.reset(b2BCallManager);
}
break;
case ReConServerConfig::Python:
#ifdef BUILD_PYTHON
mConversationManager = std::unique_ptr<PyConversationManager>(new PyConversationManager(reConServerConfig, localAudioEnabled, defaultSampleRate, maximumSampleRate, autoAnswerEnabled));
#else
CritLog(<<"Not compiled with Python support");
resip_assert(0);
#endif
break;
default:
resip_assert(0);
}
mUserAgent = std::make_shared<MyUserAgent>(reConServerConfig, mConversationManager.get(), profile);
if(mediaStack == ReConServerConfig::sipXtapi)
{
#ifdef USE_SIPXTAPI
SipXMediaStackAdapter& mediaStackAdapter = static_cast<SipXMediaStackAdapter&>(mConversationManager->getMediaStackAdapter());
mediaStackAdapter.buildSessionCapabilities(address, _codecIds, conversationProfile->sessionCaps());
#else
resip_assert(0);
#endif
}
mUserAgent->addConversationProfile(conversationProfile);
if(application == ReConServerConfig::B2BUA)
{
b2BCallManager->init(*mUserAgent);
Data internalMediaAddress;
reConServerConfig.getConfigValue("B2BUAInternalMediaAddress", internalMediaAddress);
if(!internalMediaAddress.empty())
{
auto internalProfile = std::make_shared<ConversationProfile>(conversationProfile);
Data b2BUANextHop = reConServerConfig.getConfigData("B2BUANextHop", "", true);
if(b2BUANextHop.empty())
{
CritLog(<<"Please specify B2BUANextHop");
throw ConfigParse::Exception("Please specify B2BUANextHop", __FILE__, __LINE__);
}
NameAddrs route;
route.push_front(NameAddr(b2BUANextHop));
internalProfile->setServiceRoute(route);
internalProfile->secureMediaMode() = reConServerConfig.getConfigSecureMediaMode("B2BUAInternalSecureMediaMode", secureMediaMode);
internalProfile->setDefaultFrom(uri);
internalProfile->setDigestCredential(uri.uri().host(), uri.uri().user(), password);
if(mediaStack == ReConServerConfig::sipXtapi)
{
#ifdef USE_SIPXTAPI
SipXMediaStackAdapter& mediaStackAdapter = static_cast<SipXMediaStackAdapter&>(mConversationManager->getMediaStackAdapter());
mediaStackAdapter.buildSessionCapabilities(internalMediaAddress, _codecIds, internalProfile->sessionCaps());
#else
resip_assert(0);
#endif
}
mUserAgent->addConversationProfile(internalProfile, false);
}
else
{
WarningLog(<<"B2BUAInternalMediaAddress not specified, using same media address for internal and external zones");
}
}
//////////////////////////////////////////////////////////////////////////////
// Startup and run...
//////////////////////////////////////////////////////////////////////////////
#ifdef BUILD_QPID_PROTON
const Data& protonCommandQueue = reConServerConfig.getConfigData("BrokerURL", "");
const Data& protonEventTopic = reConServerConfig.getConfigData("EventTopicURL", "");
if(!protonCommandQueue.empty() || !protonEventTopic.empty())
{
mProtonCommandThread.reset(new ProtonThreadBase());
if(!protonCommandQueue.empty())
{
mCommandQueue.reset(new ProtonCommandThread(protonCommandQueue));
mProtonCommandThread->addReceiver(mCommandQueue);
}
if(!protonEventTopic.empty())
{
mEventTopic.reset(new ProtonThreadBase::ProtonSenderBase(protonEventTopic.c_str()));
mProtonCommandThread->addSender(mEventTopic);
mConversationManager->setEventListener([this](const Data& event){
mEventTopic->sendMessage(event);
});
}
mProtonCommandThread->run();
}
#endif
mUserAgent->startup();
mConversationManager->startup();
//mUserAgent->createSubscription("message-summary", uri, 120, Mime("application", "simple-message-summary")); // thread safe
// Drop privileges (can do this now that sockets are bound)
if(!runAsUser.empty())
{
InfoLog( << "Trying to drop privileges, configured uid = " << runAsUser << " gid = " << runAsGroup);
dropPrivileges(runAsUser, runAsGroup);
}
mainLoop();
mUserAgent->shutdown();
#ifdef BUILD_QPID_PROTON
if(mProtonCommandThread)
{
mProtonCommandThread->shutdown();
mProtonCommandThread.release();
}
#endif
}
InfoLog(<< "reConServer is shutdown.");
if(mediaStack == ReConServerConfig::sipXtapi)
{
#ifdef USE_SIPXTAPI
OsSysLog::shutdown();
#else
resip_assert(0);
#endif
}
::sleepSeconds(2);
#if defined(WIN32) && defined(_DEBUG) && defined(LEAK_CHECK)
} // end FML scope
#endif
return 0;
}
void
ReConServerProcess::doWait()
{
mUserAgent->process(50);
}
void
ReConServerProcess::onLoop()
{
if(mKeyboardInput)
{
int input;
while(_kbhit() != 0)
{
#ifdef WIN32
input = _getch();
processKeyboard(input, *mConversationManager, *mUserAgent);
#else
input = fgetc(stdin);
fflush(stdin);
//cout << "input: " << input << endl;
processKeyboard(input, *mConversationManager, *mUserAgent);
#endif
}
}
#ifdef BUILD_QPID_PROTON
if(mProtonCommandThread && mConversationManager)
{
mCommandQueue->processQueue(*mConversationManager);
}
#endif
}
void
ReConServerProcess::onReload()
{
StackLog(<<"ReConServerProcess::onReload invoked");
if(mCDRFile)
{
StackLog(<<"ReConServerProcess::onReload: request CDR rotation");
mCDRFile->rotateLog();
}
}
/* ====================================================================
Copyright (C) 2022 Daniel Pocock https://danielpocock.com
Copyright (C) 2022 Software Freedom Institute LLC https://softwarefreedom.institute
Copyright (c) 2007-2008, Plantronics, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Plantronics nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==================================================================== */