Browse Source

Added TURN server test

Paul-Louis Ageneau 4 years ago
parent
commit
f2caa8048f
4 changed files with 280 additions and 4 deletions
  1. 1 0
      CMakeLists.txt
  2. 6 4
      test/connectivity.cpp
  3. 10 0
      test/main.cpp
  4. 263 0
      test/turn_connectivity.cpp

+ 1 - 0
CMakeLists.txt

@@ -100,6 +100,7 @@ set(LIBDATACHANNEL_HEADERS
 set(TESTS_SOURCES
     ${CMAKE_CURRENT_SOURCE_DIR}/test/main.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/connectivity.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/test/turn_connectivity.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/track.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/capi_connectivity.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/test/capi_track.cpp

+ 6 - 4
test/connectivity.cpp

@@ -33,14 +33,16 @@ void test_connectivity() {
 	InitLogger(LogLevel::Debug);
 
 	Configuration config1;
-	// STUN server example
-	// config1.iceServers.emplace_back("stun:stun.l.google.com:19302");
+	// STUN server example (not necessary to connect locally)
+	// Please do not use outside of libdatachannel tests
+	config1.iceServers.emplace_back("stun:stun.ageneau.net:3478");
 
 	auto pc1 = std::make_shared<PeerConnection>(config1);
 
 	Configuration config2;
-	// STUN server example
-	// config2.iceServers.emplace_back("stun:stun.l.google.com:19302");
+	// STUN server example (not necessary to connect locally)
+	// Please do not use outside of libdatachannel tests
+	config2.iceServers.emplace_back("stun:stun.ageneau.net:3478");
 	// Port range example
 	config2.portRangeBegin = 5000;
 	config2.portRangeEnd = 6000;

+ 10 - 0
test/main.cpp

@@ -24,6 +24,7 @@ using namespace std;
 using namespace chrono_literals;
 
 void test_connectivity();
+void test_turn_connectivity();
 void test_track();
 void test_capi_connectivity();
 void test_capi_track();
@@ -51,6 +52,15 @@ int main(int argc, char **argv) {
 		return -1;
 	}
 	this_thread::sleep_for(1s);
+	try {
+		cout << endl << "*** Running WebRTC TURN connectivity test..." << endl;
+		test_turn_connectivity();
+		cout << "*** Finished WebRTC TURN connectivity test" << endl;
+	} catch (const exception &e) {
+		cerr << "WebRTC TURN connectivity test failed: " << e.what() << endl;
+		return -1;
+	}
+	this_thread::sleep_for(1s);
 	try {
 		cout << endl << "*** Running WebRTC C API connectivity test..." << endl;
 		test_capi_connectivity();

+ 263 - 0
test/turn_connectivity.cpp

@@ -0,0 +1,263 @@
+/**
+ * Copyright (c) 2019 Paul-Louis Ageneau
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "rtc/rtc.hpp"
+
+#include <atomic>
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <thread>
+
+using namespace rtc;
+using namespace std;
+
+template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
+
+void test_turn_connectivity() {
+	InitLogger(LogLevel::Debug);
+
+	Configuration config1;
+	// TURN server example
+	// Please do not use outside of libdatachannel tests
+	config1.iceServers.emplace_back("turn:datachannel_test:[email protected]:3478");
+
+	auto pc1 = std::make_shared<PeerConnection>(config1);
+
+	Configuration config2;
+	// TURN server example
+	// Please do not use outside of libdatachannel tests
+	config2.iceServers.emplace_back("turn:datachannel_test:[email protected]:3478");
+	// Port range example
+	config2.portRangeBegin = 5000;
+	config2.portRangeEnd = 6000;
+
+	auto pc2 = std::make_shared<PeerConnection>(config2);
+
+	pc1->onLocalDescription([wpc2 = make_weak_ptr(pc2)](Description sdp) {
+		auto pc2 = wpc2.lock();
+		if (!pc2)
+			return;
+		cout << "Description 1: " << sdp << endl;
+		pc2->setRemoteDescription(string(sdp));
+	});
+
+	pc1->onLocalCandidate([wpc2 = make_weak_ptr(pc2)](Candidate candidate) {
+		auto pc2 = wpc2.lock();
+		if (!pc2)
+			return;
+		// For this test, filter out non-relay candidates to force TURN
+		string str(candidate);
+		if(str.find("relay") != string::npos) {
+			cout << "Candidate 1: " << str << endl;
+			pc2->addRemoteCandidate(str);
+		}
+	});
+
+	pc1->onStateChange([](PeerConnection::State state) { cout << "State 1: " << state << endl; });
+
+	pc1->onGatheringStateChange([](PeerConnection::GatheringState state) {
+		cout << "Gathering state 1: " << state << endl;
+	});
+
+	pc1->onSignalingStateChange([](PeerConnection::SignalingState state) {
+		cout << "Signaling state 1: " << state << endl;
+	});
+
+	pc2->onLocalDescription([wpc1 = make_weak_ptr(pc1)](Description sdp) {
+		auto pc1 = wpc1.lock();
+		if (!pc1)
+			return;
+		cout << "Description 2: " << sdp << endl;
+		pc1->setRemoteDescription(string(sdp));
+	});
+
+	pc2->onLocalCandidate([wpc1 = make_weak_ptr(pc1)](Candidate candidate) {
+		auto pc1 = wpc1.lock();
+		if (!pc1)
+			return;
+		// For this test, filter out non-relay candidates to force TURN
+		string str(candidate);
+		if(str.find("relay") != string::npos) {
+			cout << "Candidate 1: " << str << endl;
+			pc1->addRemoteCandidate(str);
+		}
+	});
+
+	pc2->onStateChange([](PeerConnection::State state) { cout << "State 2: " << state << endl; });
+
+	pc2->onGatheringStateChange([](PeerConnection::GatheringState state) {
+		cout << "Gathering state 2: " << state << endl;
+	});
+
+	pc2->onSignalingStateChange([](PeerConnection::SignalingState state) {
+		cout << "Signaling state 2: " << state << endl;
+	});
+
+	shared_ptr<DataChannel> dc2;
+	pc2->onDataChannel([&dc2](shared_ptr<DataChannel> dc) {
+		cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
+		if (dc->label() != "test") {
+			cerr << "Wrong DataChannel label" << endl;
+			return;
+		}
+
+		dc->onMessage([](variant<binary, string> message) {
+			if (holds_alternative<string>(message)) {
+				cout << "Message 2: " << get<string>(message) << endl;
+			}
+		});
+
+		dc->send("Hello from 2");
+
+		std::atomic_store(&dc2, dc);
+	});
+
+	auto dc1 = pc1->createDataChannel("test");
+	dc1->onOpen([wdc1 = make_weak_ptr(dc1)]() {
+		auto dc1 = wdc1.lock();
+		if (!dc1)
+			return;
+
+		cout << "DataChannel 1: Open" << endl;
+		dc1->send("Hello from 1");
+	});
+	dc1->onMessage([](const variant<binary, string> &message) {
+		if (holds_alternative<string>(message)) {
+			cout << "Message 1: " << get<string>(message) << endl;
+		}
+	});
+
+	// Wait a bit
+	int attempts = 10;
+	shared_ptr<DataChannel> adc2;
+	while ((!(adc2 = std::atomic_load(&dc2)) || !adc2->isOpen() || !dc1->isOpen()) && attempts--)
+		this_thread::sleep_for(1s);
+
+	if (pc1->state() != PeerConnection::State::Connected &&
+	    pc2->state() != PeerConnection::State::Connected)
+		throw runtime_error("PeerConnection is not connected");
+
+	if (!adc2 || !adc2->isOpen() || !dc1->isOpen())
+		throw runtime_error("DataChannel is not open");
+
+	if (auto addr = pc1->localAddress())
+		cout << "Local address 1:  " << *addr << endl;
+	if (auto addr = pc1->remoteAddress())
+		cout << "Remote address 1: " << *addr << endl;
+	if (auto addr = pc2->localAddress())
+		cout << "Local address 2:  " << *addr << endl;
+	if (auto addr = pc2->remoteAddress())
+		cout << "Remote address 2: " << *addr << endl;
+
+	Candidate local, remote;
+	if (pc1->getSelectedCandidatePair(&local, &remote)) {
+		cout << "Local candidate 1:  " << local << endl;
+		cout << "Remote candidate 1: " << remote << endl;
+	}
+	if (pc2->getSelectedCandidatePair(&local, &remote)) {
+		cout << "Local candidate 2:  " << local << endl;
+		cout << "Remote candidate 2: " << remote << endl;
+	}
+
+	// Try to open a second data channel with another label
+	shared_ptr<DataChannel> second2;
+	pc2->onDataChannel([&second2](shared_ptr<DataChannel> dc) {
+		cout << "Second DataChannel 2: Received with label \"" << dc->label() << "\"" << endl;
+		if (dc->label() != "second") {
+			cerr << "Wrong second DataChannel label" << endl;
+			return;
+		}
+
+		dc->onMessage([](variant<binary, string> message) {
+			if (holds_alternative<string>(message)) {
+				cout << "Second Message 2: " << get<string>(message) << endl;
+			}
+		});
+
+		dc->send("Send hello from 2");
+
+		std::atomic_store(&second2, dc);
+	});
+
+	auto second1 = pc1->createDataChannel("second");
+	second1->onOpen([wsecond1 = make_weak_ptr(dc1)]() {
+		auto second1 = wsecond1.lock();
+		if (!second1)
+			return;
+
+		cout << "Second DataChannel 1: Open" << endl;
+		second1->send("Second hello from 1");
+	});
+	dc1->onMessage([](const variant<binary, string> &message) {
+		if (holds_alternative<string>(message)) {
+			cout << "Second Message 1: " << get<string>(message) << endl;
+		}
+	});
+
+	// Wait a bit
+	attempts = 10;
+	shared_ptr<DataChannel> asecond2;
+	while (
+	    (!(asecond2 = std::atomic_load(&second2)) || !asecond2->isOpen() || !second1->isOpen()) &&
+	    attempts--)
+		this_thread::sleep_for(1s);
+
+	if (!asecond2 || !asecond2->isOpen() || !second1->isOpen())
+		throw runtime_error("Second DataChannel is not open");
+
+	// Try to open a negotiated channel
+	DataChannelInit init;
+	init.negotiated = true;
+	init.id = 42;
+	auto negotiated1 = pc1->createDataChannel("negotiated", init);
+	auto negotiated2 = pc2->createDataChannel("negoctated", init);
+
+	if (!negotiated1->isOpen() || !negotiated2->isOpen())
+		throw runtime_error("Negociated DataChannel is not open");
+
+	std::atomic<bool> received = false;
+	negotiated2->onMessage([&received](const variant<binary, string> &message) {
+		if (holds_alternative<string>(message)) {
+			cout << "Second Message 2: " << get<string>(message) << endl;
+			received = true;
+		}
+	});
+
+	negotiated1->send("Hello from negotiated channel");
+
+	// Wait a bit
+	attempts = 5;
+	while (!received && attempts--)
+		this_thread::sleep_for(1s);
+
+	if (!received)
+		throw runtime_error("Negociated DataChannel failed");
+
+	// Delay close of peer 2 to check closing works properly
+	pc1->close();
+	this_thread::sleep_for(1s);
+	pc2->close();
+	this_thread::sleep_for(1s);
+
+	// You may call rtc::Cleanup() when finished to free static resources
+	rtc::Cleanup();
+	this_thread::sleep_for(1s);
+
+	cout << "Success" << endl;
+}