[asterisk-scf-commits] asterisk-scf/release/media_transport_udptl.git branch "master" created.
Commits to the Asterisk SCF project code repositories
asterisk-scf-commits at lists.digium.com
Tue Oct 4 08:52:56 CDT 2011
branch "master" has been created
at f81a97fd1a98ad26cf04cf4a5a21b1a1061df926 (commit)
- Log -----------------------------------------------------------------
commit f81a97fd1a98ad26cf04cf4a5a21b1a1061df926
Author: Joshua Colp <jcolp at digium.com>
Date: Mon Oct 3 21:02:23 2011 -0300
Fix comment.
diff --git a/src/UDPTransport.h b/src/UDPTransport.h
index a9c3085..14b12ea 100644
--- a/src/UDPTransport.h
+++ b/src/UDPTransport.h
@@ -48,7 +48,7 @@ public:
bool expectIPv6);
//
- // Primary version.
+ // Replication version.
//
static UDPTransportPtr create(const PJMediaEndpointPtr& ep,
const UDPTLConfigurationPtr& configObject,
commit 848dbf738d83b45a180169aa81b808658a5b7474
Merge: dc3a2a8 7ee97e4
Author: Joshua Colp <jcolp at digium.com>
Date: Mon Oct 3 21:01:28 2011 -0300
Merge branch 'master' of git.asterisk.org:asterisk-scf/integration/mediatransportudptl
commit dc3a2a89b282a8957c49d118b37e8791fcceea3d
Author: Joshua Colp <jcolp at digium.com>
Date: Mon Oct 3 20:59:43 2011 -0300
Remove comment about comparator. There actually is none in here...
diff --git a/src/Component.cpp b/src/Component.cpp
index 0589c3b..a88da51 100644
--- a/src/Component.cpp
+++ b/src/Component.cpp
@@ -460,10 +460,6 @@ void Component::onRegisterPrimaryServices()
void Component::onStart()
{
- // Note: I don't think this is necessary. If we make the
- // comparator computed from a "service" identifier (which could default
- // to "default"), there's nothing replicated here that the standby component
- // couldn't already determine itself.
if (getReplicationContext()->isReplicating() == true)
{
UdptlReplicationContextPtr udptlReplicationContext =
commit 7ee97e4877a0d560373e4e9a190563fc8eeab6bf
Author: Joshua Colp <jcolp at digium.com>
Date: Thu Sep 29 18:00:26 2011 -0300
Windowsify things.
diff --git a/src/UDPTLSink.cpp b/src/UDPTLSink.cpp
index 5170139..319e530 100644
--- a/src/UDPTLSink.cpp
+++ b/src/UDPTLSink.cpp
@@ -119,8 +119,7 @@ void StreamSinkUDPTLImpl::write(const AsteriskSCF::Media::V1::FrameSeq& frames,
for (FrameSeq::const_iterator frame = frames.begin(); frame != frames.end(); ++frame)
{
- const int bufsize = udptl_get_far_max_datagram(mImpl->mUdptl);
- uint8_t buf[bufsize];
+ uint8_t buf[1400];
int len = 0;
ByteSeqPayloadPtr payload = ByteSeqPayloadPtr::dynamicCast((*frame)->payload);
diff --git a/src/udptl.c b/src/udptl.c
index 3b66f5a..ccb9d27 100644
--- a/src/udptl.c
+++ b/src/udptl.c
@@ -48,7 +48,6 @@
* - app_fax.c
*/
-#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
@@ -255,18 +254,18 @@ struct udptl_frame *udptl_rx_packet(struct udptl *s, uint8_t *buf, unsigned int
{
int stat1;
int stat2;
- int i;
- int j;
+ unsigned int i;
+ unsigned int j;
int k;
int l;
- int m;
+ unsigned int m;
int x;
int limit;
int which;
unsigned int ptr;
unsigned int count;
int total_count;
- int seq_no;
+ unsigned int seq_no;
const uint8_t *ifp = NULL;
const uint8_t *data = NULL;
unsigned int ifp_len = 0;
@@ -274,7 +273,7 @@ struct udptl_frame *udptl_rx_packet(struct udptl *s, uint8_t *buf, unsigned int
const uint8_t *bufs[ARRAY_LEN(s->f) - 1];
unsigned int lengths[ARRAY_LEN(s->f) - 1];
int span;
- int entries;
+ unsigned int entries;
int ifp_no;
ptr = 0;
@@ -403,7 +402,7 @@ struct udptl_frame *udptl_rx_packet(struct udptl *s, uint8_t *buf, unsigned int
for (j = 0; j < s->rx[l].fec_len[m]; j++) {
s->rx[which].buf[j] = s->rx[l].fec[m][j];
for (k = (limit - s->rx[l].fec_span * s->rx[l].fec_entries) & UDPTL_BUF_MASK; k != limit; k = (k + s->rx[l].fec_entries) & UDPTL_BUF_MASK)
- s->rx[which].buf[j] ^= (s->rx[k].buf_len > j) ? s->rx[k].buf[j] : 0;
+ s->rx[which].buf[j] ^= (s->rx[k].buf_len > (int)j) ? s->rx[k].buf[j] : 0;
}
s->rx[which].buf_len = s->rx[l].fec_len[m];
repaired[which] = 1;
@@ -450,7 +449,7 @@ int udptl_build_packet(struct udptl *s, uint8_t *buf, unsigned int buflen, uint8
uint8_t fec[LOCAL_FAX_MAX_DATAGRAM * 2];
int i;
int j;
- int seq;
+ unsigned int seq;
int entry;
int entries;
int span;
@@ -592,7 +591,14 @@ static void calculate_local_max_datagram(struct udptl *udptl)
break;
}
/* add 5% extra space for insurance, but no larger than LOCAL_FAX_MAX_DATAGRAM */
- udptl->local_max_datagram = MIN(new_max * 1.05, LOCAL_FAX_MAX_DATAGRAM);
+ if (LOCAL_FAX_MAX_DATAGRAM > (new_max * 1.05))
+ {
+ udptl->local_max_datagram = (int)(new_max * 1.05);
+ }
+ else
+ {
+ udptl->local_max_datagram = LOCAL_FAX_MAX_DATAGRAM;
+ }
}
static void calculate_far_max_ifp(struct udptl *udptl)
@@ -658,7 +664,7 @@ static void calculate_far_max_ifp(struct udptl *udptl)
break;
}
/* subtract 5% of space for insurance */
- udptl->far_max_ifp = new_max * 0.95;
+ udptl->far_max_ifp = (int)(new_max * 0.95);
}
enum t38_ec_modes udptl_get_error_correction_scheme(const struct udptl *udptl)
commit 3b1dd8ecd661bcab230acfd7e6ae3a7e2e759d7a
Author: Joshua Colp <jcolp at digium.com>
Date: Wed Sep 28 15:56:48 2011 -0300
Add test for STUN/ICE support. Working on extending it.
diff --git a/config/test_udptl_ice.conf b/config/test_udptl_ice.conf
new file mode 100644
index 0000000..0d24c2f
--- /dev/null
+++ b/config/test_udptl_ice.conf
@@ -0,0 +1,67 @@
+# This is a configuration file used in conjunction with the MediaTransportUDPTL test driver
+
+#
+# Icebox Configuration
+#
+
+UdptlConfiguration.Name=UdptlStateReplicator
+
+IceBox.InheritProperties=1
+IceBox.LoadOrder=ServiceDiscovery,UdptlStateReplicator,MediaTransportUDPTL,TestMediaTransportUDPTL
+
+Ice.Override.Timeout=5000
+
+# RtpStateReplicator Configuration
+IceBox.Service.UdptlStateReplicator=UdptlStateReplicator:create
+
+# Adapter parameters for this component
+UdptlStateReplicator.Adapter.Endpoints=tcp:udp
+
+# A proxy to the service locator management service
+LocatorServiceManagement.Proxy=LocatorServiceManagement:tcp -p 4412
+
+# A proxy to the service locator service
+LocatorService.Proxy=LocatorService:tcp -p 4411
+
+#
+# MediaTransportUDPTL Configuration
+#
+
+IceBox.Service.MediaTransportUDPTL=MediaTransportUDPTL:create
+
+# Adapter parameters for this component
+MediaTransportUDPTLAdapter.Endpoints=default
+MediaTransportUDPTLAdapterLocal.Endpoints=default
+MediaTransportUDPTLAdapterLogger.Endpoints=default
+
+#
+# TestMediaTransportUDPTL Configuration
+#
+
+IceBox.Service.TestMediaTransportUDPTL=MediaTransportUDPTL_ice_test:create
+
+#
+# Service Locator Configuration
+#
+
+IceBox.Service.ServiceDiscovery=service_locator:create
+
+ServiceDiscovery.IceStorm.InstanceName=ServiceDiscovery
+ServiceDiscovery.IceStorm.TopicManager.Endpoints=default -p 10000
+ServiceDiscovery.IceStorm.Publish.Endpoints=tcp -p 10001:udp -p 10001
+ServiceDiscovery.IceStorm.Trace.TopicManager=2
+ServiceDiscovery.IceStorm.Transient=1
+ServiceDiscovery.IceStorm.Flush.Timeout=2000
+
+UdptlStateReplicator.IceStorm.InstanceName=UdptlStateReplicator
+UdptlStateReplicator.IceStorm.TopicManager.Endpoints=default -p 10005
+UdptlStateReplicator.IceStorm.Publish.Endpoints=default -p 10006
+UdptlStateReplicator.IceStorm.Trace.TopicManager=2
+UdptlStateReplicator.IceStorm.Transient=1
+UdptlStateReplicator.IceStorm.Flush.Timeout=2000
+
+ServiceDiscovery.Management.ServiceAdapter.Endpoints=tcp -p 4412
+ServiceDiscovery.Locator.ServiceAdapter.Endpoints=tcp -p 4411
+ServiceDiscovery.BackplaneAdapter.Endpoints=default
+
+LoggerAdapter.Endpoints=default
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index ac980f6..e12c557 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -14,5 +14,15 @@ astscf_component_add_slice_collection_libraries(MediaTransportUDPTL_test_v6 ASTS
astscf_component_build_icebox(MediaTransportUDPTL_test_v6)
set_property(TARGET MediaTransportUDPTL_test_v6 PROPERTY COMPILE_DEFINITIONS IPV6_TEST)
+astscf_component_init(MediaTransportUDPTL_ice_test)
+astscf_component_add_files(MediaTransportUDPTL_ice_test TestUDPTLICE.cpp)
+astscf_component_add_slices(MediaTransportUDPTL_ice_test PROJECT AsteriskSCF/Replication/UDPTL/UdptlStateReplicationIf.ice)
+astscf_component_add_slices(MediaTransportUDPTL_ice_test PROJECT AsteriskSCF/Configuration/UDPTL/UdptlConfigurationIf.ice)
+astscf_component_add_boost_libraries(MediaTransportUDPTL_ice_test unit_test_framework thread date_time)
+astscf_component_add_slice_collection_libraries(MediaTransportUDPTL_ice_test ASTSCF)
+astscf_component_build_icebox(MediaTransportUDPTL_ice_test)
+pjproject_link(MediaTransportUDPTL_ice_test pjlib)
+
astscf_test_icebox(MediaTransportUDPTL_test config/test_component.config)
astscf_test_icebox(MediaTransportUDPTL_test_v6 config/test_component_v6.config)
+astscf_test_icebox(MediaTransportUDPTL_ice_test config/test_udptl_ice.conf)
diff --git a/test/TestUDPTLICE.cpp b/test/TestUDPTLICE.cpp
new file mode 100644
index 0000000..402a55a
--- /dev/null
+++ b/test/TestUDPTLICE.cpp
@@ -0,0 +1,270 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2011, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+
+#define BOOST_TEST_MODULE UdptlICETestModule
+#define BOOST_TEST_NO_MAIN
+
+#include <AsteriskSCF/Testing/IceBoxBoostTest.h>
+
+#include "UdptlConfigurationIf.h"
+#include "UdptlStateReplicationIf.h"
+
+#include <boost/test/debug.hpp>
+#include <boost/thread/thread.hpp>
+#include <boost/thread/mutex.hpp>
+#include <boost/thread/condition.hpp>
+#include <Ice/Ice.h>
+#include <Ice/BuiltinSequences.h>
+
+#include <AsteriskSCF/Core/Discovery/ServiceLocatorIf.h>
+#include <AsteriskSCF/Media/MediaIf.h>
+#include <AsteriskSCF/Media/UDPTL/MediaUDPTLIf.h>
+
+//
+// An attempt to get some reasonable code coverage and verify that the basic *premise* of the functionality works as
+// expected. HOWEVER, nothing beats live testing. Due the the dependency on external agencies (STUN server, TURN server,
+// NAT firewall, etc) there isn't much in the way of real functional testing that we can drive from a single icebox
+// instance. A HOW_TO_TEST document would be a good idea and is a major TODO.
+//
+
+using namespace std;
+using namespace AsteriskSCF::Core::Discovery::V1;
+using namespace AsteriskSCF::Media::V1;
+using namespace AsteriskSCF::Media::UDPTL::V1;
+using namespace AsteriskSCF::System::Configuration::V1;
+using namespace AsteriskSCF::Replication::UDPTL::V1;
+using namespace AsteriskSCF::Configuration::UDPTL::V1;
+
+namespace
+{
+
+class TestReplicatorListener : public UdptlStateReplicatorListener
+{
+public:
+ void stateRemoved(const Ice::StringSeq&, const Ice::Current&)
+ {
+ }
+
+ void stateSet(const UdptlStateItemSeq&, const Ice::Current&)
+ {
+ }
+
+private:
+
+};
+
+class IceEnvironment
+{
+public:
+ IceEnvironment()
+ {
+ Ice::InitializationData data;
+ data.properties = IceBoxTestEnv.communicator->getProperties();
+ mCommunicator = Ice::initialize(IceBoxTestEnv.argc, IceBoxTestEnv.argv, data);
+ mAdapter = mCommunicator->createObjectAdapterWithEndpoints("TestAdapter", "default");
+
+ //
+ // We will be making calls on proxies instantiated on this adapter, so we should activated here.
+ //
+ mAdapter->activate();
+ }
+
+ ~IceEnvironment()
+ {
+ if (mCommunicator)
+ {
+ mCommunicator->shutdown();
+ }
+ }
+
+ Ice::CommunicatorPtr getCommunicator()
+ {
+ return mCommunicator;
+ }
+
+ Ice::ObjectAdapterPtr getObjectAdapter()
+ {
+ return mAdapter;
+ }
+
+private:
+ Ice::CommunicatorPtr mCommunicator;
+ Ice::ObjectAdapterPtr mAdapter;
+};
+
+class TestFixture
+{
+public:
+ TestFixture()
+ {
+ BOOST_TEST_MESSAGE("Creating test fixture");
+ ::boost::debug::detect_memory_leaks(false);
+ ::boost::unit_test::unit_test_log.set_stream(cout);
+ }
+
+ ~TestFixture()
+ {
+ BOOST_TEST_MESSAGE("Destroying test fixture");
+ }
+};
+
+BOOST_GLOBAL_FIXTURE(TestFixture);
+
+UDPTLMediaServicePrx locateMediaService(const ServiceLocatorPrx& locator)
+{
+ UDPTLOverICEServiceLocatorParamsPtr query = new UDPTLOverICEServiceLocatorParams;
+ query->category = "udptl";
+ query->enableICE = true;
+ query->enableTURN = false;
+ return UDPTLMediaServicePrx::checkedCast(locator->locate(query));
+}
+
+ConfigurationServicePrx locateConfigurationService(const ServiceLocatorPrx& locator)
+{
+ ServiceLocatorParamsPtr query = new ServiceLocatorParams;
+ query->category = ConfigurationDiscoveryCategory;
+ return ConfigurationServicePrx::uncheckedCast(locator->locate(query));
+}
+
+ServiceLocatorPrx getLocator(const Ice::CommunicatorPtr& comm)
+{
+ return ServiceLocatorPrx::checkedCast(comm->propertyToProxy("LocatorService.Proxy"));
+}
+
+BOOST_AUTO_TEST_CASE(UdptlSessionWithICEEnabled)
+{
+ IceEnvironment iceEnv;
+ bool testResult = false;
+ try
+ {
+ ServiceLocatorPrx locator;
+ BOOST_REQUIRE_NO_THROW(locator = getLocator(iceEnv.getCommunicator()));
+ BOOST_REQUIRE(locator != 0);
+
+ //
+ // It's actually pretty handy that this icebox test suite probably
+ // shares the configuration file that hosts the media service!
+ //
+ ConfigurationServicePrx configPrx;
+ BOOST_REQUIRE_NO_THROW(
+ configPrx = locateConfigurationService(locator));
+ BOOST_REQUIRE(configPrx != 0);
+ UDPTLICEConfigurationGroupPtr iceGroup = new UDPTLICEConfigurationGroup;
+
+ STUNServerItemPtr stunServerCfg = new STUNServerItem;
+ stunServerCfg->address = "stun.xten.com";
+ stunServerCfg->port = 3478;
+ iceGroup->configurationItems[STUNServerItemName] = stunServerCfg;
+
+ UDPTLICETransportFlagsItemPtr iceFlags = new UDPTLICETransportFlagsItem;
+ iceFlags->enableICE = true;
+ iceFlags->enableTURN = false;
+ iceGroup->configurationItems[UDPTLICETransportFlagsItemName] = iceFlags;
+
+ ConfigurationGroupSeq s;
+ s.push_back(iceGroup);
+ configPrx->setConfiguration(s);
+
+ UDPTLMediaServicePrx servicePrx;
+ {
+ UDPTLOverICEServiceLocatorParamsPtr query = new UDPTLOverICEServiceLocatorParams;
+ query->category = "udptl";
+ query->enableICE = true;
+ query->enableTURN = false;
+ servicePrx = UDPTLMediaServicePrx::checkedCast(locator->locate(query));
+ }
+ BOOST_REQUIRE(servicePrx);
+ try
+ {
+ UDPTLOverICEServiceLocatorParamsPtr query = new UDPTLOverICEServiceLocatorParams;
+ query->category = "udptl";
+ query->enableICE = true;
+ query->enableTURN = false;
+ UDPTLSessionPrx sessionPrx;
+ BOOST_REQUIRE_NO_THROW(sessionPrx = servicePrx->allocate(query));
+ BOOST_REQUIRE(sessionPrx != 0);
+ sessionPrx->ice_ping(); // To silence unused arg warning.
+ sessionPrx->release();
+ testResult = true;
+ }
+ catch (const SessionAllocationFailure& ex)
+ {
+ BOOST_TEST_MESSAGE(ex.what());
+ }
+ }
+ catch (const Ice::Exception& ex)
+ {
+ BOOST_FAIL(ex.what());
+ }
+ BOOST_CHECK(testResult);
+}
+
+BOOST_AUTO_TEST_CASE(UdptlSessionAllocationFailure)
+{
+ IceEnvironment iceEnv;
+ bool testResult = false;
+ try
+ {
+ ServiceLocatorPrx locator;
+ BOOST_REQUIRE_NO_THROW(locator = getLocator(iceEnv.getCommunicator()));
+ BOOST_REQUIRE(locator != 0);
+ UDPTLMediaServicePrx servicePrx;
+ BOOST_REQUIRE_NO_THROW(servicePrx = locateMediaService(locator));
+ BOOST_REQUIRE(servicePrx);
+
+ //
+ // It's actually pretty handy that this icebox test suite probably
+ // shares the configuration file that hosts the media service!
+ //
+ ConfigurationServicePrx configPrx;
+ BOOST_REQUIRE_NO_THROW(
+ configPrx = locateConfigurationService(locator));
+ UDPTLICEConfigurationGroupPtr iceGroup = new UDPTLICEConfigurationGroup;
+ UDPTLICETransportFlagsItemPtr iceFlags = new UDPTLICETransportFlagsItem;
+ iceFlags->enableICE = false;
+ iceFlags->enableTURN = false;
+ iceGroup->configurationItems[UDPTLICETransportFlagsItemName] = iceFlags;
+ ConfigurationGroupSeq s;
+ s.push_back(iceGroup);
+ BOOST_REQUIRE_NO_THROW(configPrx->setConfiguration(s));
+ try
+ {
+ UDPTLOverICEServiceLocatorParamsPtr query = new UDPTLOverICEServiceLocatorParams;
+ query->category = "udptl";
+ query->enableICE = true;
+ query->enableTURN = true;
+ UDPTLSessionPrx sessionPrx = servicePrx->allocate(query);
+ sessionPrx->ice_ping();
+ }
+ catch (const SessionAllocationFailure& ex)
+ {
+ testResult = true;
+ BOOST_TEST_MESSAGE(ex.what());
+ }
+ }
+ catch (const Ice::Exception& ex)
+ {
+ BOOST_TEST_MESSAGE(ex.what());
+ }
+ BOOST_CHECK(testResult);
+}
+
+}
+
+
+
+
commit 35178480df903a5e326aaade1c8f0c11a1ff7b81
Author: Joshua Colp <jcolp at digium.com>
Date: Tue Sep 27 12:20:37 2011 -0300
Use the ICE transport when needed.
diff --git a/src/UDPTLSession.cpp b/src/UDPTLSession.cpp
index b5d2edb..b0203a5 100644
--- a/src/UDPTLSession.cpp
+++ b/src/UDPTLSession.cpp
@@ -22,6 +22,7 @@
#include "PJMediaTransport.h"
#include "PJMediaEndpoint.h"
#include "UDPTransport.h"
+#include "ICETransport.h"
#include <pjlib.h>
#include <pjmedia.h>
@@ -230,7 +231,22 @@ UDPTLSessionImpl::UDPTLSessionImpl(const Ice::ObjectAdapterPtr& adapter,
mReplicationContext(replicationContext),
mUdptl(udptl_create())
{
- mTransport = UDPTransport::create(mEndpoint, configurationService, params->ipv6);
+ UDPTLOverICEServiceLocatorParamsPtr iceParams(UDPTLOverICEServiceLocatorParamsPtr::dynamicCast(params));
+ if (iceParams && iceParams->enableICE)
+ {
+ if (mEnvironment->natConfig() && mEnvironment->natConfig()->isSTUNEnabled())
+ {
+ mTransport = ICETransport::create(mEndpoint, env);
+ }
+ else
+ {
+ throw SessionAllocationFailure("ICE/NAT features not enabled for this component instance");
+ }
+ }
+ else
+ {
+ mTransport = UDPTransport::create(mEndpoint, configurationService, params->ipv6);
+ }
// Initialize our session state item enough so that the state items for the source and sink can also be initialized.
mSessionStateItem->key = mSessionStateItem->sessionId = IceUtil::generateUUID();
@@ -256,7 +272,14 @@ UDPTLSessionImpl::UDPTLSessionImpl(const Ice::ObjectAdapterPtr& adapter,
mReplicationContext(replicationContext),
mUdptl(udptl_create())
{
- mTransport = UDPTransport::create(mEndpoint, configurationService, port, ipv6);
+ if (mEnvironment->natConfig() && mEnvironment->natConfig()->isSTUNEnabled())
+ {
+ mTransport = ICETransport::create(mEndpoint, env);
+ }
+ else
+ {
+ mTransport = UDPTransport::create(mEndpoint, configurationService, port, ipv6);
+ }
}
/**
commit df6cc81d885647e69b43c44390cece5beeb5f9c7
Author: Joshua Colp <jcolp at digium.com>
Date: Tue Sep 27 12:07:51 2011 -0300
Bring over ICE/STUN transport stuff from RTP to be used with UDPTL.
diff --git a/config/UdptlConfigurator.py b/config/UdptlConfigurator.py
index bde1039..7be2ea0 100755
--- a/config/UdptlConfigurator.py
+++ b/config/UdptlConfigurator.py
@@ -54,6 +54,36 @@ class UdptlSectionVisitors(Configurator.SectionVisitors):
self.groups.append(group)
+ def visit_udptloverice(self, config, section):
+ group = AsteriskSCF.Configuration.UDPTL.V1.UDPTLICEConfigurationGroup()
+ group.configurationItems = { }
+
+ mapper = Configurator.OptionMapper()
+
+ stunServerItem = AsteriskSCF.Configuration.UDPTL.V1.STUNServerItem()
+ mapper.map('stunserverhost', stunServerItem, 'address', AsteriskSCF.Configuration.UDPTL.V1.STUNServerItemName, config.get, None)
+ mapper.map('stunserverport', stunServerItem, 'port', AsteriskSCF.Configuration.UDPTL.V1.STUNServerItemName, config.getint, 3478)
+
+ turnServerItem = AsteriskSCF.Configuration.UDPTL.V1.TURNServerItem()
+ mapper.map('turnserverhost', turnServerItem, 'address', AsteriskSCF.Configuration.UDPTL.V1.TURNServerItemName, config.get, None)
+ mapper.map('turnserverport', turnServerItem, 'port', AsteriskSCF.Configuration.UDPTL.V1.TURNServerItemName, config.getint, 3478)
+
+ udptlOverICEItem = AsteriskSCF.Configuration.UDPTL.V1.UDPTLICETransportFlagsItem()
+ mapper.map('udptlovericeenable', udptlOverICEItem, 'enableICE', AsteriskSCF.Configuration.UDPTL.V1.UDPTLICETransportFlagsItemName, config.get, None)
+ mapper.map('udptlovericewithturn', udptlOverICEItem, 'enableTURN', AsteriskSCF.Configuration.UDPTL.V1.UDPTLICETransportFlagsItemName, config.get, None)
+
+ udptlICELimits = AsteriskSCF.Configuration.UDPTL.V1.UDPTLICETransportLimitsItem()
+ mapper.map('udptlicemaxcandidates', udptlICELimits, 'maxCandidates', AsteriskSCF.Configuration.UDPTL.V1.UDPTLICELimitsItemName, config.getint, 10)
+ mapper.map('udptlicemaxcalls', udptlICELimits, 'maxCalls', AsteriskSCF.Configuration.UDPTL.V1.UDPTLICELimitsItemName, config.getint, 50)
+
+ for option in config.options(section):
+ mapper.execute(group, section, option)
+
+ mapper.finish(group)
+
+ self.groups.append(group)
+
+
# In order to do service locator based lookup we need to pass in a params object
serviceLocatorParams = AsteriskSCF.Core.Discovery.V1.ServiceLocatorParams()
serviceLocatorParams.category = AsteriskSCF.Configuration.UDPTL.V1.ConfigurationDiscoveryCategory
diff --git a/config/test_component.config b/config/test_component.config
index c4bc8a3..241a91c 100644
--- a/config/test_component.config
+++ b/config/test_component.config
@@ -60,5 +60,6 @@ UdptlStateReplicator.IceStorm.Flush.Timeout=2000
ServiceDiscovery.Management.ServiceAdapter.Endpoints=tcp -p 4412
ServiceDiscovery.Locator.ServiceAdapter.Endpoints=tcp -p 4411
+ServiceDiscovery.BackplaneAdapter.Endpoints=default
LoggerAdapter.Endpoints=default
diff --git a/slice/AsteriskSCF/Configuration/UDPTL/UdptlConfigurationIf.ice b/slice/AsteriskSCF/Configuration/UDPTL/UdptlConfigurationIf.ice
index 3b4a9dd..4dd1ccd 100644
--- a/slice/AsteriskSCF/Configuration/UDPTL/UdptlConfigurationIf.ice
+++ b/slice/AsteriskSCF/Configuration/UDPTL/UdptlConfigurationIf.ice
@@ -159,6 +159,86 @@ module V1
int fecEntries;
};
+ /*
+ * Configuration group for ICE enabled UDPTL.
+ */
+ class UDPTLICEConfigurationGroup extends UdptlConfigurationGroup
+ {
+ };
+
+ /**
+ * Name that the STUN server configuration item should be inserted as.
+ */
+ const string STUNServerItemName = "stunServer";
+
+ /**
+ * Hostname for the STUN server.
+ */
+ class STUNServerItem extends UdptlConfigurationItem
+ {
+ string address;
+ int port;
+ };
+
+ /**
+ * Name that the TURN server configuration item should be inserted as.
+ */
+ const string TURNServerItemName = "turnServer";
+
+ /**
+ * Hostname for the TURN server.
+ */
+ class TURNServerItem extends UdptlConfigurationItem
+ {
+ string address;
+ int port;
+ };
+
+ /**
+ * Name that the ICE transport configuration flags item should be inserted as.
+ */
+ const string UDPTLICETransportFlagsItemName = "iceFlags";
+
+ /**
+ * Configuration item with option flags for the ICE transport.
+ */
+ class UDPTLICETransportFlagsItem extends UdptlConfigurationItem
+ {
+ /**
+ * If the configuration option is present, it's most likely
+ * because we want to enable STUN and ICE
+ */
+ bool enableICE = true;
+
+ /**
+ * Using a TURN server as a candidate should be a selectable option
+ * since a TURN server isn't always available. Setting this to true
+ * while enableICE is false has no effect.
+ */
+ bool enableTURN = true;
+ };
+
+ /**
+ * Name that ICE option items should be inserted as.
+ */
+ const string UDPTLICELimitsItemName = "iceLimits";
+
+ /**
+ * Configuration item for configurable limits for the ICE transport.
+ */
+ class UDPTLICETransportLimitsItem extends UdptlConfigurationItem
+ {
+ /**
+ * The maximum number of candidates to gather and publish.
+ */
+ int maxCandidates;
+
+ /**
+ * The maximum number of ICE negotiated flows to allow.
+ */
+ int maxCalls;
+ };
+
}; /* module V1 */
}; /* module UDPTL */
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 348e029..9f196d1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -30,6 +30,14 @@ astscf_component_add_files(MediaTransportUDPTL PJMediaEndpoint.h)
astscf_component_add_files(MediaTransportUDPTL PJUtil.h)
astscf_component_add_files(MediaTransportUDPTL UDPTransport.cpp)
astscf_component_add_files(MediaTransportUDPTL UDPTransport.h)
+astscf_component_add_files(MediaTransportUDPTL ICETransport.cpp)
+astscf_component_add_files(MediaTransportUDPTL ICETransport.h)
+astscf_component_add_files(MediaTransportUDPTL NATModule.cpp)
+astscf_component_add_files(MediaTransportUDPTL NATModule.h)
+astscf_component_add_files(MediaTransportUDPTL NATConfig.cpp)
+astscf_component_add_files(MediaTransportUDPTL NATConfig.h)
+astscf_component_add_files(MediaTransportUDPTL ICEConfiguration.cpp)
+astscf_component_add_files(MediaTransportUDPTL ICEConfiguration.h)
astscf_component_add_slices(MediaTransportUDPTL PROJECT AsteriskSCF/Replication/UDPTL/UdptlStateReplicationIf.ice)
astscf_component_add_slices(MediaTransportUDPTL PROJECT AsteriskSCF/Configuration/UDPTL/UdptlConfigurationIf.ice)
astscf_component_add_boost_libraries(MediaTransportUDPTL core thread)
diff --git a/src/Configuration.h b/src/Configuration.h
index 0bed468..ef179c9 100755
--- a/src/Configuration.h
+++ b/src/Configuration.h
@@ -21,6 +21,8 @@
#include <IceUtil/Shared.h>
#include "PJLibConfiguration.h"
+#include "NATConfig.h"
+#include "ICEConfiguration.h"
namespace AsteriskSCF
{
@@ -37,6 +39,9 @@ class UDPTLConfiguration : public virtual IceUtil::Shared
public:
virtual ~UDPTLConfiguration() {}
+ virtual NATConfigPtr natConfig() const = 0;
+ virtual ICEConfigurationPtr ICEConfig() const = 0;
+
virtual int getStartPort() = 0;
virtual int getEndPort() = 0;
virtual std::string getBindIPv4Address() = 0;
diff --git a/src/ICEConfiguration.cpp b/src/ICEConfiguration.cpp
new file mode 100644
index 0000000..5ed3aed
--- /dev/null
+++ b/src/ICEConfiguration.cpp
@@ -0,0 +1,32 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#include "ICEConfiguration.h"
+#include <Ice/Properties.h>
+
+using namespace std;
+using namespace AsteriskSCF::UDPTL;
+
+ICEConfigurationPtr AsteriskSCF::UDPTL::ICEConfiguration::create(int maxCand, int maxClls)
+{
+ return ICEConfigurationPtr(new ICEConfiguration(maxCand, maxClls));
+}
+
+ICEConfiguration::ICEConfiguration(int maxCand, int maxClls) :
+ mMaxCandidates(maxCand),
+ mMaxCalls(maxClls)
+{
+}
diff --git a/src/ICEConfiguration.h b/src/ICEConfiguration.h
new file mode 100644
index 0000000..597b973
--- /dev/null
+++ b/src/ICEConfiguration.h
@@ -0,0 +1,68 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#pragma once
+
+#include <Ice/PropertiesF.h>
+#include <string>
+#include <boost/shared_ptr.hpp>
+
+namespace AsteriskSCF
+{
+namespace UDPTL
+{
+
+class ICEConfiguration;
+typedef boost::shared_ptr<ICEConfiguration> ICEConfigurationPtr;
+
+/**
+ * ICEConfiguration is fairly minimal at the moment, but may grow in the future. The intent is to reduce code
+ * duplication when dealing with pjproject related configuration.
+ **/
+class ICEConfiguration
+{
+public:
+
+ int maxCandidates() const
+ {
+ return mMaxCandidates;
+ }
+
+ int maxCalls() const
+ {
+ return mMaxCalls;
+ }
+
+ /**
+ * Create configuration instance!
+ **/
+ static ICEConfigurationPtr create(int maxCand, int maxClls);
+
+private:
+ int mMaxCandidates;
+ int mMaxCalls;
+
+ ICEConfiguration(int maxCandidates, int maxCalls);
+
+ //
+ // Hidden and not implemented.
+ //
+ ICEConfiguration(const ICEConfiguration&);
+ void operator=(const ICEConfiguration&);
+};
+
+} /* End of namespace UDPTL */
+} /* End of namespace AsteriskSCF */
diff --git a/src/ICETransport.cpp b/src/ICETransport.cpp
new file mode 100644
index 0000000..7c083f2
--- /dev/null
+++ b/src/ICETransport.cpp
@@ -0,0 +1,803 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#include "ICETransport.h"
+#include "PJUtil.h"
+
+#include <pjmedia.h>
+#include <pjlib.h>
+#include <pjnath.h>
+
+#include <AsteriskSCF/System/ExceptionsIf.h>
+#include <map>
+#include <boost/thread.hpp>
+#include <boost/thread/shared_mutex.hpp>
+
+#include <AsteriskSCF/System/NAT/NATTraversalIf.h>
+#include <Ice/Ice.h>
+#include <sstream>
+#include <AsteriskSCF/logger.h>
+#include <IceUtil/UUID.h>
+
+using namespace AsteriskSCF::UDPTL;
+using namespace AsteriskSCF::System::V1;
+using namespace AsteriskSCF::PJUtil;
+using namespace std;
+using namespace AsteriskSCF::Helpers;
+using namespace AsteriskSCF::System::Logging;
+using namespace AsteriskSCF::System::NAT::V1;
+
+namespace
+{
+Logger logger = getLoggerFactory().getLogger("AsteriskSCF.MediaUDPTL");
+}
+
+namespace
+{
+
+class ICEAgentImpl : public InteractiveConnectionAgent
+{
+public:
+
+ ICEAgentImpl(const Ice::ObjectAdapterPtr& adapter, const Ice::Identity& id, const PJMediaEnvironmentPtr& env,
+ const PJMediaEndpointPtr& ep) :
+ mAdapter(adapter),
+ mId(id),
+ mShuttingDown(false),
+ mNATType(AsteriskSCF::System::NAT::V1::Unknown),
+ mRole(UndefinedRole),
+ mEnv(env),
+ mEndpoint(ep),
+ mTransport(0)
+ {
+ }
+
+ AgentType getAgentType(const Ice::Current&)
+ {
+ return Full;
+ }
+
+ DetectedNATType getNATType(const Ice::Current&)
+ {
+ boost::shared_lock<boost::shared_mutex> lock(mLock);
+ stateCheck();
+ return mNATType;
+ }
+
+ Role getRole(const Ice::Current&)
+ {
+ boost::shared_lock<boost::shared_mutex> lock(mLock);
+ stateCheck();
+ return mRole;
+ }
+
+ void negotiate_async(const AMD_InteractiveConnectionAgent_negotiatePtr& callback,
+ const string& hostname, Ice::Int port, const CandidateSeq& candidates,
+ const Ice::Current&)
+ {
+
+ boost::unique_lock<boost::shared_mutex> lock(mLock);
+ stateCheck();
+ if (mCurrentNegotiation)
+ {
+ pjmedia_transport_media_stop(mTransport);
+ mCurrentNegotiation->ice_exception(NegotiationInterrupted("New negotiate() request"));
+ //
+ // TODO: are we going to support cancellable negotiations.
+ //
+ }
+ mCurrentNegotiation = callback;
+
+ //
+ // So how this works is we create a remote SDP and call pjmedia_transport_start() easy peasy. (Same deal
+ //
+ pjmedia_sdp_session* remoteSDPSession =
+ static_cast<pjmedia_sdp_session*>(pj_pool_zalloc(mEnv->memoryPool(), sizeof(pjmedia_sdp_session)));
+
+
+ //
+ // TODO: I think the ICE transport ignores a lot of this stuff, but I'm going to add it for the time
+ // being anyways.
+ //
+
+ //
+ // Missing details, user, id, version, net type?
+ //
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->name, "ASCFMEDIA");
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->origin.user, "");
+ AddressPtr remoteHost(new Address(hostname, port));
+
+ remoteSDPSession->conn = static_cast<pjmedia_sdp_conn*>(pj_pool_zalloc(mEnv->memoryPool(), sizeof(pjmedia_sdp_conn)));
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->conn->net_type, "IN");
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->origin.net_type, "IN");
+
+ //
+ // TODO: Look at whether the members can point to the same memory without issues.
+ //
+ if (remoteHost->isIPV6())
+ {
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->origin.addr_type, "IP6");
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->conn->addr_type, "IP6");
+ }
+ else
+ {
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->origin.addr_type, "IP4");
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->conn->addr_type, "IP4");
+ }
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->origin.addr, remoteHost->address().c_str());
+ pj_strdup2(mEnv->memoryPool(), &remoteSDPSession->conn->addr, remoteHost->address().c_str());
+ remoteSDPSession->attr_count = 0;
+
+ //
+ // Cut n' paste from current SIP session manager... icky. TODO: sift through and see what of this
+ // can be discarded for this purpose.
+ //
+ remoteSDPSession->media_count = 1;
+ pjmedia_sdp_media* media =
+ static_cast<pjmedia_sdp_media*>(pj_pool_zalloc(mEnv->memoryPool(), sizeof(pjmedia_sdp_media)));
+ remoteSDPSession->media[0] = media;
+ pj_strdup2(mEnv->memoryPool(), &media->desc.media, "audio");
+ media->desc.port = (pj_uint16_t) port; // XXX --- this is not going to be correct here.. we don't actually have this!
+ media->desc.port_count = 1;
+ pj_strdup2(mEnv->memoryPool(), &media->desc.transport, "RTP/AVP");
+
+ // Populate the stream with codec details
+ remoteSDPSession->media[0]->desc.fmt_count = 1;
+ remoteSDPSession->media[0]->attr_count = 0;
+
+ // TODO: We should iterate over the formats to produce this instead of hardcoding
+ pjmedia_sdp_rtpmap rtpmap;
+ pjmedia_sdp_attr *attr;
+
+ // This is hardcoded value for ULAW for now
+ pj_strdup2(mEnv->memoryPool(), &media->desc.fmt[0], "0");
+ rtpmap.pt = media->desc.fmt[0];
+ rtpmap.clock_rate = 8000;
+ pj_strdup2(mEnv->memoryPool(), &rtpmap.enc_name, "PCMU");
+ rtpmap.param.slen = 0;
+ pjmedia_sdp_rtpmap_to_attr(mEnv->memoryPool(), &rtpmap, &attr);
+ remoteSDPSession->media[0]->attr[remoteSDPSession->media[0]->attr_count++] = attr;
+
+ // Might as well add sendrecv
+ attr = static_cast<pjmedia_sdp_attr*>(pj_pool_zalloc(mEnv->memoryPool(), sizeof(pjmedia_sdp_attr)));
+ pj_strdup2(mEnv->memoryPool(), &attr->name, "sendrecv");
+ remoteSDPSession->media[0]->attr[remoteSDPSession->media[0]->attr_count++] = attr;
+
+ //
+ // I was concerned about the fact that for a given SIP session, there might be multiple media
+ // streams and multiple candidates. I'm not sure that its actually too much of an issue even
+ // if multiple media types are muxed on a single ICE negotiated flow, but there will need to be
+ // some redesign to pull in the multiple media streams associated with the session. For the moment
+ // we will operation under the premise that we are dealing with a single media stream.
+ // TODO: the SIP session gateway contains similar code, but from the offer perspective. This stuff
+ // should be refactored into a pjproject utility library.
+ //
+ //
+ pjmedia_sdp_media* currentMedia = remoteSDPSession->media[0];
+ for (CandidateSeq::const_iterator i = candidates.begin(); i != candidates.end(); ++i)
+ {
+ CandidatePtr candidate = *i;
+ ostringstream os;
+ os << "candidate:" << candidate->foundation << ' ' << candidate->componentId << " UDP " <<
+ candidate->priority << ' ' << candidate->mappedAddress << ' ' << candidate->mappedPort << " typ ";
+ string hostType;
+ switch (candidate->type)
+ {
+ case Host:
+ hostType = "host";
+ break;
+ case ServerReflexive:
+ hostType = "srflx";
+ break;
+ case PeerReflexive:
+ hostType = "prflx";
+ break;
+ case Relayed:
+ hostType = "relay";
+ break;
+ }
+ os << hostType;
+ if (candidate->type != Host)
+ {
+ os << " raddr " << candidate->baseAddress << " rport " << candidate->basePort;
+ }
+ string t = os.str();
+ pj_str_t candidateStr = pj_str(const_cast<char*>(t.c_str()));
+ pjmedia_sdp_attr* newAttribute = pjmedia_sdp_attr_create(mEnv->memoryPool(),
+ "candidate", &candidateStr);
+ pjmedia_sdp_attr_add(¤tMedia->attr_count, currentMedia->attr, newAttribute);
+ }
+ pjmedia_sdp_session localSession;
+ pjmedia_transport_encode_sdp(mTransport, mEnv->memoryPool(), &localSession, 0, 0);
+ pjmedia_transport_media_start(mTransport, mEnv->memoryPool(), &localSession, remoteSDPSession, 0);
+ }
+
+ CandidateSeq getCandidates(const Ice::Current&)
+ {
+ boost::shared_lock<boost::shared_mutex> lock(mLock);
+ return mCandidates;
+ }
+
+ void onSetupComplete(pjmedia_transport* transport, pj_status_t status)
+ {
+ if (fail(status))
+ {
+ ostringstream err;
+ err << "Setup/negotiation failed with pj_status_t value of " << status;
+ throw NegotiationError(err.str());
+ }
+
+ pjmedia_transport_info info;
+ pjmedia_transport_info_init(&info);
+ pjmedia_transport_get_info(transport, &info);
+
+ pjmedia_ice_transport_info* iceInfo = 0;
+ for (unsigned i = 0; i < info.specific_info_cnt; ++i)
+ {
+ if (info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_ICE)
+ {
+ iceInfo = (pjmedia_ice_transport_info*)(info.spc_info[i].buffer);
+ }
+ }
+
+ assert(iceInfo != 0);
+
+ //
+ // While we just did the assert, we use an if statement as well to
+ // prevent crashing in release builds.
+ //
+ if (iceInfo)
+ {
+ boost::unique_lock<boost::shared_mutex> lock(mLock);
+ if (mTransport)
+ {
+ //
+ // duplicate call.
+ //
+ return;
+ }
+ mTransport = transport;
+ if (iceInfo->role == PJ_ICE_SESS_ROLE_CONTROLLING)
+ {
+ setRole(Controlling);
+ }
+ else
+ {
+ setRole(Controlled);
+ }
+
+ pjmedia_transport_media_create(mTransport, mEnv->memoryPool(), 0, 0, 0);
+
+ //
+ // Ok, so the pjmedia ice transport won't let use get at the actual
+ // candidate structures, so what we have to do is get the SDP from
+ // the transport and convert what we find there to our Ice structures.
+ //
+ pjmedia_sdp_session* sdpSession;
+ unsigned streamCount = 1; // The 1 doesn't matter, this is just to initialize the basic structure.
+ pjmedia_endpt_create_sdp(mEndpoint->endpoint(), mEnv->memoryPool(), streamCount, &info.sock_info, &sdpSession);
+ pjmedia_transport_encode_sdp(mTransport, mEnv->memoryPool(), sdpSession, 0, 0);
+ for (size_t i = 0; i < sdpSession->media_count; ++i)
+ {
+ const string candidateName("candidate");
+ pjmedia_sdp_media* media = sdpSession->media[i];
+ ostringstream errorMessage;
+ for (size_t j = 0; j < media->attr_count; ++j)
+ {
+ pjmedia_sdp_attr* attr = media->attr[j];
+ if (string(attr->name.ptr, attr->name.slen) == candidateName)
+ {
+ //
+ // Now we get to parse a candidate string!
+ //
+ string value(attr->value.ptr, attr->value.slen);
+ istringstream is(value);
+ string foundation;
+ if (!(is >> foundation))
+ {
+ errorMessage << "Unable to parse ICE candidate value (foundation) : " << value;
+ break;
+ }
+
+ int componentId;
+ if (!(is >> componentId))
+ {
+ errorMessage << "Unable to parse ICE candidate value (component id) : " << value;
+ break;
+ }
+
+ //
+ // We don't care about the transport right now.. we are assuming UDP.
+ //
+ string transportDummy;
+ if (!(is >> transportDummy))
+ {
+ errorMessage << "Unable to parse ICE candidate value (transport) : " << value;
+ break;
+ }
+
+ int priority;
+ if (!(is >> priority))
+ {
+ errorMessage << "Unable to parse ICE candidate value (priority) : " << value;
+ break;
+ }
+
+ string connectionAddress;
+ if (!(is >> connectionAddress))
+ {
+ errorMessage << "Unable to parse ICE candidate value (connection address) : " << value;
+ break;
+ }
+
+ unsigned port;
+ if (!(is >> port))
+ {
+ errorMessage << "Unable to parse ICE candidate value (connection port) : " << value;
+ break;
+ }
+
+ string candidateTypePrefix;
+ if (!(is >> candidateTypePrefix))
+ {
+ errorMessage << "Unable to parse ICE candidate constant (typ) : " << value;
+ break;
+ }
+
+ string candidateType;
+ if (!(is >> candidateType))
+ {
+ errorMessage << "Unable to parse ICE candidate value (candidate type) : " << value;
+ break;
+ }
+
+
+ CandidatePtr candidateObj = new Candidate;
+ candidateObj->sessionId = IceUtil::generateUUID(); // this should be the object id.
+ candidateObj->componentId = 1; // At least until we implement RTCP.
+ candidateObj->priority = priority;
+ candidateObj->baseAddress = connectionAddress;
+ candidateObj->basePort = port;
+ candidateObj->transport = UDP;
+
+ if (candidateType == "host")
+ {
+ candidateObj->type = Host;
+ }
+ else if (candidateType == "srflx")
+ {
+ candidateObj->type = ServerReflexive;
+ }
+ else if (candidateType == "prflx")
+ {
+ candidateObj->type = PeerReflexive;
+ }
+ else if (candidateType == "relay")
+ {
+ candidateObj->type = Relayed;
+ }
+ else
+ {
+ errorMessage << "Unable to determine the candidate type, skipping : " << value;
+ }
+
+ if (candidateType != "host")
+ {
+ string dummy;
+ if (!(is >> dummy))
+ {
+ errorMessage << "Unable to parse ICE candidate constant (raddr) : " << value;
+ break;
+ }
+
+ string baseAddress;
+ if (!(is >> baseAddress))
+ {
+ errorMessage << "Unable to parse ICE candidate value (rel-addr) : " << value;
+ break;
+ }
+
+ if (!(is >> dummy))
+ {
+ errorMessage << "Unable to parse ICE candidate constant (rport) : " << value;
+ break;
+ }
+
+ unsigned basePort;
+ if (!(is >> basePort))
+ {
+ errorMessage << "Unable to parse ICE candidate value (rel-port) : " << value;
+ break;
+ }
+ candidateObj->mappedAddress = candidateObj->baseAddress;
+ candidateObj->mappedPort = candidateObj->basePort;
+ candidateObj->baseAddress = baseAddress;
+ candidateObj->basePort = basePort;
+ }
+ else
+ {
+ candidateObj->mappedAddress = "";
+ candidateObj->mappedPort = 0;
+ }
+ //
+ // And we ignore the rest.
+ //
+ mCandidates.push_back(candidateObj);
+ }
+ string err(errorMessage.str());
+ if (!err.empty())
+ {
+ throw invalid_argument(err);
+ }
+ }
+ }
+ if (mCurrentNegotiation)
+ {
+ if (mCandidates.size() == 0)
+ {
+ logger(Error) << "No candidates after negotiation, looks like fail";
+ mCurrentNegotiation->ice_exception(NoValidCandidates());
+ throw invalid_argument("no valid candidates, did negotiation fail?");
+ }
+ mCurrentNegotiation->ice_response(mCandidates[0]);
+ mCurrentNegotiation = 0;
+ if (mCandidates.size() > 1)
+ {
+ logger(Warning) << "More than one candidate after negotiation!";
+ }
+ }
+ }
+ }
+
+ void shutdown()
+ {
+ {
+ boost::unique_lock<boost::shared_mutex> lock(mLock);
+ if (mShuttingDown)
+ {
+ return;
+ }
+ mShuttingDown = true;
+ if (mCurrentNegotiation)
+ {
+ pjmedia_transport_media_stop(mTransport);
+ mCurrentNegotiation->ice_exception(NegotiationInterrupted("Shutting down."));
+ }
+ }
+ mAdapter->removeFacet(mId, "ICEAgent");
+ }
+
+ void setNATType(DetectedNATType natType)
+ {
+ boost::unique_lock<boost::shared_mutex> lock(mLock);
+ mNATType = natType;
+ }
+
+ void setRole(Role role)
+ {
+ mRole = role;
+ }
+
+private:
+ boost::shared_mutex mLock;
+ Ice::ObjectAdapterPtr mAdapter;
+ Ice::Identity mId;
+ bool mShuttingDown;
+ DetectedNATType mNATType;
+ Role mRole;
+ CandidateSeq mCandidates;
+ PJMediaEnvironmentPtr mEnv;
+ PJMediaEndpointPtr mEndpoint;
+ pjmedia_transport* mTransport;
+ AMD_InteractiveConnectionAgent_negotiatePtr mCurrentNegotiation;
+
+ void stateCheck()
+ {
+ if (mShuttingDown)
+ {
+ throw Ice::ObjectNotExistException(__FILE__, __LINE__);
+ }
+ }
+};
+
+typedef IceUtil::Handle<ICEAgentImpl> ICEAgentImplPtr;
+
+class ICECallbackAdapter
+{
+public:
+ static void addEntry(pjmedia_transport* transport, const ICETransportPtr& callback);
+ static void addAgent(pjmedia_transport* transport, const ICEAgentImplPtr& agent);
+ static void removeEntry(pjmedia_transport* transport);
+ static void onICEComplete(pjmedia_transport* transport, pj_ice_strans_op operation, pj_status_t status);
+
+ struct CallbackRecord
+ {
+ bool connected;
+ ICETransportPtr transport;
+ ICEAgentImplPtr agent;
+
+ CallbackRecord() :
+ connected(false)
+ {
+ }
+ };
+ typedef std::map<unsigned long long, CallbackRecord> TransportMap;
+
+private:
+ static TransportMap mTransportMap;
+ static boost::shared_mutex mLock;
+};
+
+//
+// Static member initializations.
+//
+ICECallbackAdapter::TransportMap ICECallbackAdapter::mTransportMap;
+boost::shared_mutex ICECallbackAdapter::mLock;
+
+//
+// For some reason the ICE media transport doesn't have the concept of associating user data with a transport, so we
+// have to map it "out of band". The problem is, there is a race condition in that the ICE completion callack could be
+// invoked before we get a chance to add the transport. The solution to that is to allow an entry to be created when
+// the ICE completion callback arrives and there isn't a table entry. When the addEntry runs, it will see the entry and
+// simply update the appropriate field.
+//
+void ICECallbackAdapter::addEntry(pjmedia_transport* transport, const ICETransportPtr& callback)
+{
+ bool alreadyKnown = false;
+ {
+ boost::unique_lock<boost::shared_mutex> lock(mLock);
+ TransportMap::iterator i = mTransportMap.find(reinterpret_cast<unsigned long long>(transport));
+ if (i != mTransportMap.end())
+ {
+ i->second.transport = callback;
+ alreadyKnown = true;
+ callback->onSetupComplete(transport, PJ_SUCCESS);
+
+ if (i->second.agent)
+ {
+ i->second.agent->onSetupComplete(transport, PJ_SUCCESS);
+ }
+ }
+ else
+ {
+ CallbackRecord r;
+ r.connected = false;
+ r.transport = callback;
+ mTransportMap.insert(make_pair(reinterpret_cast<unsigned long long>(transport), r));
+ }
+ }
+}
+
+void ICECallbackAdapter::addAgent(pjmedia_transport* transport, const ICEAgentImplPtr& agent)
+{
+ boost::unique_lock<boost::shared_mutex> lock(mLock);
+ TransportMap::iterator i = mTransportMap.find(reinterpret_cast<unsigned long long>(transport));
+
+ if (i != mTransportMap.end())
+ {
+ i->second.agent = agent;
+ if (i->second.connected == true && i->second.transport.get() != 0)
+ {
+ try
+ {
+ agent->onSetupComplete(transport, PJ_SUCCESS);
+ }
+ catch(const invalid_argument& ex)
+ {
+ logger(Error) << "Agent added to map but the setup seems incorrect: " << ex.what();
+ }
+ }
+ }
+ //
+ // The entry really should always be found, but there could be a race
+ // condition if things are shutting down before everything was cleanly
+ // activated.
+ //
+}
+
+void ICECallbackAdapter::removeEntry(pjmedia_transport* t)
+{
+ boost::unique_lock<boost::shared_mutex> lock(mLock);
+ TransportMap::iterator i = mTransportMap.find(reinterpret_cast<unsigned long long>(t));
+ if (i != mTransportMap.end())
+ {
+ if (i->second.agent)
+ {
+ i->second.agent->shutdown();
+ }
+ mTransportMap.erase(i);
+ }
+}
+
+void ICECallbackAdapter::onICEComplete(pjmedia_transport* transport, pj_ice_strans_op operation, pj_status_t status)
+{
+ //
+ // AFAICT, only PJ_ICE_STRANS_OP_NEGOTIATION should get here.
+ //
+ switch (operation)
+ {
+ case PJ_ICE_STRANS_OP_INIT:
+ //
+ // Initialization is complete. At this point we know what candidates we can offer.
+ //
+ {
+ boost::unique_lock<boost::shared_mutex> lock(mLock);
+ TransportMap::iterator i = mTransportMap.find(reinterpret_cast<unsigned long long>(transport));
+ if (i == mTransportMap.end())
+ {
+ CallbackRecord r;
+ r.connected = success(status);
+ mTransportMap.insert(make_pair(reinterpret_cast<unsigned long long>(transport), r));
+ }
+ else
+ {
+ i->second.connected = success(status);
+ i->second.transport->onSetupComplete(transport, status);
+ if (i->second.agent)
+ {
+ i->second.agent->onSetupComplete(transport, status);
+ }
+ }
+ }
+ break;
+ case PJ_ICE_STRANS_OP_NEGOTIATION:
+ //
+ // Negotiation is complete.
+ //
+ {
+ boost::unique_lock<boost::shared_mutex> lock(mLock);
+ TransportMap::iterator i = mTransportMap.find(reinterpret_cast<unsigned long long>(transport));
+ if (i == mTransportMap.end())
+ {
+ assert(false);
+ //
+ // This is a problem, it is very unlikely that this should
+ // happen when things are working as they should.
+ //
+ }
+ else
+ {
+ //
+ // We've negotiated a valid flow with on this leg. We should query every
+ // detail of relevance from the current media session.
+ //
+ if (i->second.agent)
+ {
+ i->second.agent->onSetupComplete(transport, status);
+ }
+ }
+ }
+ break;
+ case PJ_ICE_STRANS_OP_KEEP_ALIVE:
+ //
+ // Keep alive has successfully completed. FWICT this should not get here.
+ //
+ break;
+ };
+}
+
+}
+
+ICETransport::~ICETransport()
+{
+ //
+ // TODO : cleanup ICE transport, the transport itself is closed by the parent class.
+ //
+ ICECallbackAdapter::removeEntry(mTransport);
+}
+
+void ICETransport::onSetupComplete(pjmedia_transport* transport, int status)
+{
+ if (fail(status))
+ {
+ //
+ // TODO!
+ //
+ return;
+ }
+
+ pjmedia_transport_info info;
+ pjmedia_transport_info_init(&info);
+ pjmedia_transport_get_info(transport, &info);
+
+ pjmedia_ice_transport_info* iceInfo = 0;
+ for (unsigned i = 0; i < info.specific_info_cnt; ++i)
+ {
+ if (info.spc_info[i].type == PJMEDIA_TRANSPORT_TYPE_ICE)
+ {
+ iceInfo = (pjmedia_ice_transport_info*)(info.spc_info[i].buffer);
+ }
+ }
+
+ if (iceInfo != 0 && iceInfo->role == PJ_ICE_SESS_ROLE_CONTROLLING)
+ {
+ if (mLastKnownAddr && pj_sockaddr_cmp(&info.sock_info.rtp_addr_name, mLastKnownAddr.get()))
+ {
+ //
+ // Address has changed! We need to let Session listeners know!
+ // TODO!
+ //
+ pj_memcpy(mLastKnownAddr.get(), &info.sock_info.rtp_addr_name, sizeof(pj_sockaddr));
+ }
+ }
+ boost::lock_guard<boost::mutex> lock(mLock);
+ mLocalAddress = fromInfo(info);
+ mMonitor.notify_one();
+}
+
+AddressPtr ICETransport::localAddress()
+{
+ boost::unique_lock<boost::mutex> lock(mLock);
+ if (mLocalAddress)
+ {
+ return mLocalAddress;
+ }
+ for (size_t i = 0; i < 5 && !mLocalAddress; ++i)
+ {
+ mMonitor.wait(lock);
+ }
+ return mLocalAddress;
+}
+
+AddressPtr ICETransport::remoteAddress()
+{
+ boost::unique_lock<boost::mutex> lock(mLock);
+ return mRemoteAddress;
+}
+
+void ICETransport::addFacets(const Ice::ObjectAdapterPtr& adapter, const Ice::Identity& id)
+{
+ ICEAgentImplPtr agent = new ICEAgentImpl(adapter, id, mConfig, mEndpoint);
+ ICECallbackAdapter::addAgent(mTransport, agent);
+ adapter->addFacet(agent, id, InteractiveConnectionAgentFacetName);
+}
+
+ICETransportPtr ICETransport::create(const PJMediaEndpointPtr& ep, const PJMediaEnvironmentPtr& config)
+{
+ ICETransportPtr transport(new ICETransport(ep, config));
+ transport->start();
+
+ //
+ // TODO: I need to temporarily insert a wait-until-fail loop for the ICE steps so the transport information
+ // is available when the transport create call returns. The source/sink's won't have valid information until then.
+ //
+ return transport;
+}
+
+ICETransport::ICETransport(const PJMediaEndpointPtr& ep, const PJMediaEnvironmentPtr& configObject) :
+ PJMediaTransport(0),
+ mEndpoint(ep),
+ mConfig(configObject)
+{
+}
+
+void ICETransport::start()
+{
+ pjmedia_transport* t = 0;
+ PJICECallbackPtr callback(new pjmedia_ice_cb);
+ callback->on_ice_complete = &ICECallbackAdapter::onICEComplete;
+ NATModulePtr natModule = NATModule::create(mConfig, mEndpoint);
+ pj_status_t result = pjmedia_ice_create(mEndpoint->endpoint(), "ASCF_ICE_MEDIA", 1,
+ natModule->configuration(), callback.get(), &t);
+ if (fail(result))
+ {
+ throw InternalInitializationException("Unable to create new ICE media transport");
+ }
+ ICECallbackAdapter::addEntry(t, shared_from_this());
+ mTransport = t;
+ mCallback = callback;
+ mNATModule = natModule;
+}
diff --git a/src/ICETransport.h b/src/ICETransport.h
new file mode 100644
index 0000000..e5e8772
--- /dev/null
+++ b/src/ICETransport.h
@@ -0,0 +1,89 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#pragma once
+
+#include "PJMediaTransport.h"
+#include "PJMediaEndpoint.h"
+#include "PJMediaEnvironment.h"
+#include <Ice/PropertiesF.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include "NATModule.h"
+
+//
+// Forward declarations.
+//
+struct pjmedia_transport;
+union pj_sockaddr;
+struct pjmedia_ice_cb;
+
+namespace AsteriskSCF
+{
+namespace UDPTL
+{
+
+class ICETransport;
+typedef boost::shared_ptr<ICETransport> ICETransportPtr;
+typedef boost::shared_ptr<pjmedia_ice_cb> PJICECallbackPtr;
+typedef boost::shared_ptr<pj_sockaddr> PJSockAddrPtr;
+
+class ICETransport : public boost::enable_shared_from_this<ICETransport>, public PJMediaTransport
+{
+public:
+
+ ~ICETransport();
+ void onSetupComplete(pjmedia_transport* transport, int status);
+
+ //
+ // Overrides of PJMediaTransport
+ //
+ AsteriskSCF::Helpers::AddressPtr localAddress();
+ AsteriskSCF::Helpers::AddressPtr remoteAddress();
+ void addFacets(const Ice::ObjectAdapterPtr& adapter, const Ice::Identity& id);
+
+ /**
+ * The Microsoft VS 2010 C++ compiler doesn't like the forward declaration of ICETransport
+ * before the enable_shared_from_this<> base class/template instantiation.
+ **/
+ static ICETransportPtr
+ create(const PJMediaEndpointPtr& ep, const PJMediaEnvironmentPtr& configObject);
+
+private:
+ boost::mutex mLock;
+ boost::condition_variable mMonitor;
+ AsteriskSCF::Helpers::AddressPtr mLocalAddress;
+ AsteriskSCF::Helpers::AddressPtr mRemoteAddress;
+ PJICECallbackPtr mCallback;
+ PJSockAddrPtr mLastKnownAddr;
+ NATModulePtr mNATModule;
+ PJMediaEndpointPtr mEndpoint;
+ PJMediaEnvironmentPtr mConfig;
+
+ ICETransport(const PJMediaEndpointPtr& ep, const PJMediaEnvironmentPtr& configObject);
+
+ void start();
+
+ //
+ // Hidden and unimplemented.
+ //
+ ICETransport(const ICETransport&);
+ void operator=(const ICETransport&);
+};
+
+
+} /* End of namespace UDPTL */
+} /* End of namespace AsteriskSCF */
diff --git a/src/NATConfig.cpp b/src/NATConfig.cpp
new file mode 100644
index 0000000..5307156
--- /dev/null
+++ b/src/NATConfig.cpp
@@ -0,0 +1,50 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#include "NATConfig.h"
+
+#include <Ice/Properties.h>
+#include <AsteriskSCF/System/ExceptionsIf.h>
+#include <pjnath.h>
+#include <AsteriskSCF/logger.h>
+
+using namespace std;
+using namespace AsteriskSCF::Helpers;
+using namespace AsteriskSCF::System::V1;
+using namespace AsteriskSCF::UDPTL;
+using namespace AsteriskSCF::System::Logging;
+
+namespace
+{
+Logger logger = getLoggerFactory().getLogger("AsteriskSCF.MediaUDPTL");
+}
+
+NATConfigPtr NATConfig::create(const AsteriskSCF::Helpers::AddressPtr& stunSrv, bool enableSTUN,
+ const AsteriskSCF::Helpers::AddressPtr& turnSrv, bool enableTURN)
+{
+ return NATConfigPtr(new NATConfig(stunSrv, turnSrv, enableSTUN, enableTURN));
+}
+
+NATConfig::NATConfig(const AddressPtr& stun, const AddressPtr& turn,
+ bool enableSTUN, bool enableTURN) :
+ mSTUNServer(stun),
+ mTURNServer(turn),
+ mSTUNEnabled(enableSTUN),
+ mTURNEnabled(enableTURN)
+{
+ std::cerr << "creating a NATConfig object with " << (mSTUNServer ? mSTUNServer->toString() : "<no stun>") << " and " <<
+ (mTURNServer ? mTURNServer->toString() : "<no turn>") << std::endl;
+}
diff --git a/src/NATConfig.h b/src/NATConfig.h
new file mode 100644
index 0000000..781dec6
--- /dev/null
+++ b/src/NATConfig.h
@@ -0,0 +1,76 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#pragma once
+
+#include <AsteriskSCF/Helpers/Network.h>
+#include <Ice/PropertiesF.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace AsteriskSCF
+{
+namespace UDPTL
+{
+
+class NATConfig;
+typedef boost::shared_ptr<NATConfig> NATConfigPtr;
+
+class NATConfig
+{
+public:
+
+ AsteriskSCF::Helpers::AddressPtr stunServer() const
+ {
+ return mSTUNServer;
+ }
+
+ bool isSTUNEnabled() const
+ {
+ return mSTUNEnabled;
+ }
+
+ AsteriskSCF::Helpers::AddressPtr turnServer() const
+ {
+ return mTURNServer;
+ }
+
+ bool isTURNEnabled() const
+ {
+ return mTURNEnabled;
+ }
+
+ static NATConfigPtr create(const AsteriskSCF::Helpers::AddressPtr& stunSrv, bool enableSTUN,
+ const AsteriskSCF::Helpers::AddressPtr& turnSrv, bool enableTURN);
+private:
+ AsteriskSCF::Helpers::AddressPtr mSTUNServer;
+ AsteriskSCF::Helpers::AddressPtr mTURNServer;
+ bool mSTUNEnabled;
+ bool mTURNEnabled;
+
+ NATConfig(const AsteriskSCF::Helpers::AddressPtr& stunServer,
+ const AsteriskSCF::Helpers::AddressPtr& turnServer,
+ bool stunEnabled,
+ bool turnEnabled);
+
+ //
+ // Hidden and unimplemented.
+ //
+ NATConfig(const NATConfig&);
+ void operator=(const NATConfig&);
+};
+} /* End of namespace UDPTL */
+} /* End of namespace AsteriskSCF */
diff --git a/src/NATModule.cpp b/src/NATModule.cpp
new file mode 100644
index 0000000..80e6583
--- /dev/null
+++ b/src/NATModule.cpp
@@ -0,0 +1,84 @@
+/*
+ * Asterisk SCF -- An open-source communications framework.
+ *
+ * Copyright (C) 2010, Digium, Inc.
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk SCF project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE.txt file
+ * at the top of the source tree.
+ */
+
+#include "NATModule.h"
+#include <pjmedia.h>
+#include <pjnath.h>
+#include "PJUtil.h"
+#include <AsteriskSCF/System/ExceptionsIf.h>
+
+using namespace AsteriskSCF::UDPTL;
+using namespace AsteriskSCF::System::V1;
+using namespace AsteriskSCF::PJUtil;
+
+NATModulePtr AsteriskSCF::UDPTL::NATModule::create(const PJMediaEnvironmentPtr& env,
+ const PJMediaEndpointPtr& endpoint)
+{
+ boost::shared_ptr<pj_ice_strans_cfg> transcfg(new pj_ice_strans_cfg);
+ pj_ice_strans_cfg_default(transcfg.get());
+ pj_stun_config_init(&transcfg->stun_cfg, env->poolFactory(), 0,
+ pjmedia_endpt_get_ioqueue(endpoint->endpoint()), 0);
+ pj_status_t result = pj_timer_heap_create(env->memoryPool(), env->libConfig()->timerHeapSize(),
+ &transcfg->stun_cfg.timer_heap);
+ if (fail(result))
+ {
+ throw InternalInitializationException("Unable to initialize tier heap.");
+ }
+
+ pj_strdup2(env->memoryPool(), &transcfg->stun.server, env->natConfig()->stunServer()->hostname().c_str());
+ transcfg->stun.port = static_cast<pj_uint16_t>(env->natConfig()->stunServer()->port());
+ ICEConfigurationPtr iceConfig = env->ICEConfig();
+ if (iceConfig)
+ {
+ transcfg->stun.max_host_cands = env->ICEConfig()->maxCandidates();
+ }
+ else
+ {
+ transcfg->stun.max_host_cands = 5; // XX arbitrary.
+ }
+ if (env->natConfig()->isTURNEnabled())
+ {
... 8604 lines suppressed ...
--
asterisk-scf/release/media_transport_udptl.git
More information about the asterisk-scf-commits
mailing list