From f2caa8048f171715ba7eed990930fa568486f50a Mon Sep 17 00:00:00 2001 From: Paul-Louis Ageneau Date: Wed, 9 Dec 2020 20:06:54 +0100 Subject: [PATCH] Added TURN server test --- CMakeLists.txt | 1 + test/connectivity.cpp | 10 +- test/main.cpp | 10 ++ test/turn_connectivity.cpp | 263 +++++++++++++++++++++++++++++++++++++ 4 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 test/turn_connectivity.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3339e83..df7d81e 100644 --- a/CMakeLists.txt +++ b/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 diff --git a/test/connectivity.cpp b/test/connectivity.cpp index 64d90b0..f3af281 100644 --- a/test/connectivity.cpp +++ b/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(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; diff --git a/test/main.cpp b/test/main.cpp index d61321b..9f81bcb 100644 --- a/test/main.cpp +++ b/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(); diff --git a/test/turn_connectivity.cpp b/test/turn_connectivity.cpp new file mode 100644 index 0000000..de4152e --- /dev/null +++ b/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 +#include +#include +#include +#include + +using namespace rtc; +using namespace std; + +template weak_ptr make_weak_ptr(shared_ptr 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:14018314739877@stun.ageneau.net:3478"); + + auto pc1 = std::make_shared(config1); + + Configuration config2; + // TURN server example + // Please do not use outside of libdatachannel tests + config2.iceServers.emplace_back("turn:datachannel_test:14018314739877@stun.ageneau.net:3478"); + // Port range example + config2.portRangeBegin = 5000; + config2.portRangeEnd = 6000; + + auto pc2 = std::make_shared(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 dc2; + pc2->onDataChannel([&dc2](shared_ptr dc) { + cout << "DataChannel 2: Received with label \"" << dc->label() << "\"" << endl; + if (dc->label() != "test") { + cerr << "Wrong DataChannel label" << endl; + return; + } + + dc->onMessage([](variant message) { + if (holds_alternative(message)) { + cout << "Message 2: " << get(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 &message) { + if (holds_alternative(message)) { + cout << "Message 1: " << get(message) << endl; + } + }); + + // Wait a bit + int attempts = 10; + shared_ptr 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 second2; + pc2->onDataChannel([&second2](shared_ptr 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 message) { + if (holds_alternative(message)) { + cout << "Second Message 2: " << get(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 &message) { + if (holds_alternative(message)) { + cout << "Second Message 1: " << get(message) << endl; + } + }); + + // Wait a bit + attempts = 10; + shared_ptr 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 received = false; + negotiated2->onMessage([&received](const variant &message) { + if (holds_alternative(message)) { + cout << "Second Message 2: " << get(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; +}