Compare commits

...

38 Commits

Author SHA1 Message Date
be6470d8bc Version 0.4.2 2020-02-06 11:42:03 +01:00
8a92c97058 Updated libjuice to v0.2.2 2020-02-06 11:36:01 +01:00
93da605230 Changed usrsctp submodule origin to sctplab/usrsctp 2020-01-29 11:24:11 +01:00
ff0f409d80 Added instructions to build with Makefile 2020-01-22 11:12:46 +01:00
a483e8135b Correct URL for libnice 2020-01-22 10:55:17 +01:00
36e4fdce1e Fixed and updated usrsctp then removed the usrsctp cmake hack 2020-01-22 10:54:21 +01:00
ea636d1f29 Changed version to 0.4.0 2020-01-21 15:39:30 +01:00
472d480978 Updated libjuice to v0.2.0 2020-01-21 15:31:22 +01:00
486fc373b2 Added details about dependencies 2020-01-21 15:30:27 +01:00
2a6c10269e Use nettle for libjuice only if using GnuTLS 2020-01-21 13:51:02 +01:00
ed68ba5402 Merge pull request #34 from paullouisageneau/libjuice
Libjuice as alternative to Libnice
2020-01-21 11:48:15 +00:00
5f91c0c1e3 Updated libjuice 2020-01-21 12:37:34 +01:00
d4c618ae38 Fixed OpenSSL MTU handling with a custom BIO for writing 2020-01-21 10:56:11 +01:00
3f52aa3d56 Fixed local and remote address getters with libjuice 2020-01-20 18:25:42 +01:00
c16ff99d83 Updated libjuice 2020-01-20 17:58:26 +01:00
e14b53f348 Include src headers from tests 2020-01-19 13:45:54 +01:00
ec8847cbf8 Updated libjuice 2020-01-19 11:54:00 +01:00
6c4e8f0d46 Fixed STUN server for libjuice 2020-01-18 17:31:29 +01:00
e2a5d5e1fe Updated libjuice and added logging for STUN server 2020-01-18 17:16:35 +01:00
fa8fda25c8 Updated Jamfile 2020-01-18 16:19:02 +01:00
47c29f0ec1 Set libjuice logging properly 2020-01-18 16:08:31 +01:00
a92438e94d Generated SDP description now uses CRLF instead of LF 2020-01-18 15:37:30 +01:00
0729ab28fd Replaced usrsctp-static by its alias Usrsctp::UsrsctpStatic 2020-01-18 12:50:14 +01:00
fa64b67006 Removed extern C around includes 2020-01-18 12:37:13 +01:00
b4d99158c6 Updated Readme 2020-01-18 00:33:06 +01:00
0ded19992c Integrated libjuice for ICE transport 2020-01-18 00:28:10 +01:00
2dece6afff Updated Makefile and CMakeLists for libjuice 2020-01-18 00:28:04 +01:00
0b554f988e Added submodule libjuice 2020-01-17 11:41:16 +01:00
15d29fc038 Merge pull request #33 from paullouisageneau/rfc8445
Updated to RFC 8445 for ICE
2020-01-14 08:27:16 +00:00
bb2c6c157d Updated to RFC 8445 for ICE 2020-01-14 09:26:05 +01:00
fd0f237a59 Merge pull request #32 from murat-dogan/master
Create stream before turn server addition
2019-12-26 09:05:57 +00:00
f09006f3ef Create stream before turn server addition 2019-12-26 09:48:55 +03:00
06369f1f14 Merge pull request #31 from paullouisageneau/turn-ipv6
TURN server URL and IPv6 support
2019-12-25 15:55:11 +00:00
61354b7101 Added comment 2019-12-25 16:55:01 +01:00
f34791b450 Removed useless include 2019-12-25 16:55:01 +01:00
c3f2f6bc63 Fixed TURN-TLS fallback port 2019-12-25 16:55:01 +01:00
a683b76a21 Extended STUN server URL parsing to TURN server URLs 2019-12-25 16:54:59 +01:00
e11de119be Added TURN server IPv6 support 2019-12-24 12:28:30 +01:00
20 changed files with 661 additions and 221 deletions

9
.gitmodules vendored
View File

@ -1,6 +1,9 @@
[submodule "usrsctp"]
path = deps/usrsctp
url = https://github.com/sctplab/usrsctp.git
[submodule "deps/plog"] [submodule "deps/plog"]
path = deps/plog path = deps/plog
url = https://github.com/SergiusTheBest/plog url = https://github.com/SergiusTheBest/plog
[submodule "usrsctp"]
path = deps/usrsctp
url = https://github.com/sctplab/usrsctp.git
[submodule "deps/libjuice"]
path = deps/libjuice
url = https://github.com/paullouisageneau/libjuice

View File

@ -1,9 +1,18 @@
cmake_minimum_required (VERSION 3.7) cmake_minimum_required (VERSION 3.7)
project (libdatachannel project (libdatachannel
DESCRIPTION "WebRTC Data Channels Library" DESCRIPTION "WebRTC DataChannels Library"
VERSION 0.2.1 VERSION 0.4.2
LANGUAGES CXX) LANGUAGES CXX)
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
option(USE_JUICE "Use libjuice instead of libnice" OFF)
if(USE_GNUTLS)
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" ON)
else()
option(USE_NETTLE "Use Nettle instead of OpenSSL in libjuice" OFF)
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/Modules)
@ -33,38 +42,13 @@ set(TESTS_ANSWERER_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/p2p/answerer.cpp
) )
# Hack because usrsctp uses CMAKE_SOURCE_DIR instead of CMAKE_CURRENT_SOURCE_DIR
set(CMAKE_REQUIRED_FLAGS "-I${CMAKE_CURRENT_SOURCE_DIR}/deps/usrsctp/usrsctplib")
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
# Set include directory and custom options to make usrsctp compile with recent g++
target_include_directories(usrsctp-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/usrsctp/usrsctplib)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# using regular Clang or AppleClang: Needed since they don't have -Wno-error=format-truncation
target_compile_options(usrsctp-static PRIVATE -Wno-error=address-of-packed-member)
else()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0")
# GCC version below 9.0 does not support option address-of-packed-member
target_compile_options(usrsctp-static PRIVATE -Wno-error=format-truncation)
else()
target_compile_options(usrsctp-static PRIVATE -Wno-error=address-of-packed-member -Wno-error=format-truncation)
endif()
else()
# all other compilers
target_compile_options(usrsctp-static PRIVATE -Wno-error=address-of-packed-member -Wno-error=format-truncation)
endif()
endif()
option(USE_GNUTLS "Use GnuTLS instead of OpenSSL" OFF)
find_package(LibNice REQUIRED)
set(THREADS_PREFER_PTHREAD_FLAG ON) set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
add_subdirectory(deps/usrsctp EXCLUDE_FROM_ALL)
add_library(Usrsctp::Usrsctp ALIAS usrsctp)
add_library(Usrsctp::UsrsctpStatic ALIAS usrsctp-static)
add_library(datachannel SHARED ${LIBDATACHANNEL_SOURCES}) add_library(datachannel SHARED ${LIBDATACHANNEL_SOURCES})
set_target_properties(datachannel PROPERTIES set_target_properties(datachannel PROPERTIES
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
@ -74,10 +58,9 @@ target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/includ
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc) target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/rtc)
target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_include_directories(datachannel PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include) target_include_directories(datachannel PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
target_link_libraries(datachannel target_link_libraries(datachannel
Threads::Threads Threads::Threads
usrsctp-static Usrsctp::UsrsctpStatic
LibNice::LibNice
) )
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES}) add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
@ -91,8 +74,7 @@ target_include_directories(datachannel-static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR
target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include) target_include_directories(datachannel-static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/plog/include)
target_link_libraries(datachannel-static target_link_libraries(datachannel-static
Threads::Threads Threads::Threads
usrsctp-static Usrsctp::UsrsctpStatic
LibNice::LibNice
) )
if (USE_GNUTLS) if (USE_GNUTLS)
@ -117,29 +99,45 @@ else()
target_link_libraries(datachannel-static OpenSSL::SSL) target_link_libraries(datachannel-static OpenSSL::SSL)
endif() endif()
if (USE_JUICE)
add_subdirectory(deps/libjuice EXCLUDE_FROM_ALL)
target_compile_definitions(datachannel PRIVATE USE_JUICE=1)
target_link_libraries(datachannel LibJuice::LibJuiceStatic)
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=1)
target_link_libraries(datachannel-static LibJuice::LibJuiceStatic)
else()
find_package(LibNice REQUIRED)
target_compile_definitions(datachannel PRIVATE USE_JUICE=0)
target_link_libraries(datachannel LibNice::LibNice)
target_compile_definitions(datachannel-static PRIVATE USE_JUICE=0)
target_link_libraries(datachannel-static LibNice::LibNice)
endif()
add_library(LibDataChannel::LibDataChannel ALIAS datachannel) add_library(LibDataChannel::LibDataChannel ALIAS datachannel)
add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static) add_library(LibDataChannel::LibDataChannelStatic ALIAS datachannel-static)
# Main Test # Main Test
add_executable(tests ${TESTS_SOURCES}) add_executable(datachannel-tests ${TESTS_SOURCES})
set_target_properties(tests PROPERTIES set_target_properties(datachannel-tests PROPERTIES
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
CXX_STANDARD 17) CXX_STANDARD 17)
set_target_properties(datachannel-tests PROPERTIES OUTPUT_NAME tests)
target_link_libraries(tests datachannel) target_include_directories(datachannel-tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(datachannel-tests datachannel)
# P2P Test: offerer # P2P Test: offerer
add_executable(offerer ${TESTS_OFFERER_SOURCES}) add_executable(datachannel-offerer ${TESTS_OFFERER_SOURCES})
set_target_properties(offerer PROPERTIES set_target_properties(datachannel-offerer PROPERTIES
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
CXX_STANDARD 17) CXX_STANDARD 17)
set_target_properties(datachannel-offerer PROPERTIES OUTPUT_NAME offerer)
target_link_libraries(offerer datachannel) target_link_libraries(datachannel-offerer datachannel)
# P2P Test: answerer # P2P Test: answerer
add_executable(answerer ${TESTS_ANSWERER_SOURCES}) add_executable(datachannel-answerer ${TESTS_ANSWERER_SOURCES})
set_target_properties(answerer PROPERTIES set_target_properties(datachannel-answerer PROPERTIES
VERSION ${PROJECT_VERSION} VERSION ${PROJECT_VERSION}
CXX_STANDARD 17) CXX_STANDARD 17)
set_target_properties(datachannel-answerer PROPERTIES OUTPUT_NAME datachannel)
target_link_libraries(datachannel-answerer datachannel)
target_link_libraries(answerer datachannel)

22
Jamfile
View File

@ -7,15 +7,17 @@ lib libdatachannel
: # requirements : # requirements
<include>./include/rtc <include>./include/rtc
<define>USE_GNUTLS=0 <define>USE_GNUTLS=0
<cxxflags>"`pkg-config --cflags openssl glib-2.0 gobject-2.0 nice`" <define>USE_JUICE=1
<cxxflags>"`pkg-config --cflags openssl`"
<library>/libdatachannel//usrsctp <library>/libdatachannel//usrsctp
<library>/libdatachannel//juice
: # default build : # default build
<link>static <link>static
: # usage requirements : # usage requirements
<include>./include <include>./include
<library>/libdatachannel//plog <library>/libdatachannel//plog
<cxxflags>-pthread <cxxflags>-pthread
<linkflags>"`pkg-config --libs openssl glib-2.0 gobject-2.0 nice`" <linkflags>"`pkg-config --libs openssl`"
; ;
alias plog alias plog
@ -35,6 +37,15 @@ alias usrsctp
<library>libusrsctp.a <library>libusrsctp.a
; ;
alias juice
: # no sources
: # no build requirements
: # no default build
: # usage requirements
<include>./deps/libjuice/include
<library>libjuice.a
;
make libusrsctp.a : : @make_libusrsctp ; make libusrsctp.a : : @make_libusrsctp ;
actions make_libusrsctp actions make_libusrsctp
{ {
@ -45,3 +56,10 @@ actions make_libusrsctp
cp $(CWD)/deps/usrsctp/usrsctplib/.libs/libusrsctp.a $(<) cp $(CWD)/deps/usrsctp/usrsctplib/.libs/libusrsctp.a $(<)
} }
make libjuice.a : : @make_libjuice ;
actions make_libjuice
{
(cd $(CWD)/deps/libjuice && make USE_NETTLE=0)
cp $(CWD)/deps/libjuice/libjuice.a $(<)
}

View File

@ -4,24 +4,42 @@ NAME=libdatachannel
CXX=$(CROSS)g++ CXX=$(CROSS)g++
AR=$(CROSS)ar AR=$(CROSS)ar
RM=rm -f RM=rm -f
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
CXXFLAGS=-std=c++17 CXXFLAGS=-std=c++17
CPPFLAGS=-O2 -pthread -fPIC -Wall -Wno-address-of-packed-member
LDFLAGS=-pthread LDFLAGS=-pthread
LIBS=glib-2.0 gobject-2.0 nice LIBS=
LOCALLIBS=libusrsctp.a
USRSCTP_DIR=deps/usrsctp USRSCTP_DIR=deps/usrsctp
JUICE_DIR=deps/libjuice
PLOG_DIR=deps/plog PLOG_DIR=deps/plog
INCLUDES=-Iinclude/rtc -I$(PLOG_DIR)/include -I$(USRSCTP_DIR)/usrsctplib
LDLIBS=
USE_GNUTLS ?= 0 USE_GNUTLS ?= 0
ifneq ($(USE_GNUTLS), 0) ifneq ($(USE_GNUTLS), 0)
CPPFLAGS+= -DUSE_GNUTLS=1 CPPFLAGS+=-DUSE_GNUTLS=1
LIBS+= gnutls LIBS+=gnutls
else else
CPPFLAGS+= -DUSE_GNUTLS=0 CPPFLAGS+=-DUSE_GNUTLS=0
LIBS+= openssl LIBS+=openssl
endif endif
LDLIBS= $(shell pkg-config --libs $(LIBS)) USE_JUICE ?= 0
INCLUDES=-Iinclude/rtc -I$(PLOG_DIR)/include -I$(USRSCTP_DIR)/usrsctplib $(shell pkg-config --cflags $(LIBS)) ifneq ($(USE_JUICE), 0)
CPPFLAGS+=-DUSE_JUICE=1
INCLUDES+=-I$(JUICE_DIR)/include
LOCALLIBS+=libjuice.a
ifneq ($(USE_GNUTLS), 0)
LIBS+=nettle
endif
else
CPPFLAGS+=-DUSE_JUICE=0
LIBS+=glib-2.0 gobject-2.0 nice
endif
INCLUDES+=$(shell pkg-config --cflags $(LIBS))
LDLIBS+=$(LOCALLIBS) $(shell pkg-config --libs $(LIBS))
SRCS=$(shell printf "%s " src/*.cpp) SRCS=$(shell printf "%s " src/*.cpp)
OBJS=$(subst .cpp,.o,$(SRCS)) OBJS=$(subst .cpp,.o,$(SRCS))
@ -32,18 +50,18 @@ src/%.o: src/%.cpp
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) -MMD -MP -o $@ -c $< $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) -MMD -MP -o $@ -c $<
test/%.o: test/%.cpp test/%.o: test/%.cpp
$(CXX) $(CXXFLAGS) $(CPPFLAGS) -Iinclude -I$(PLOG_DIR)/include -MMD -MP -o $@ -c $< $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(INCLUDES) -Iinclude -Isrc -MMD -MP -o $@ -c $<
-include $(subst .cpp,.d,$(SRCS)) -include $(subst .cpp,.d,$(SRCS))
$(NAME).a: $(OBJS) $(NAME).a: $(OBJS)
$(AR) crf $@ $(OBJS) $(AR) crf $@ $(OBJS)
$(NAME).so: libusrsctp.a $(OBJS) $(NAME).so: $(LOCALLIBS) $(OBJS)
$(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS) libusrsctp.a $(CXX) $(LDFLAGS) -shared -o $@ $(OBJS) $(LDLIBS)
tests: $(NAME).a test/main.o tests: $(NAME).a test/main.o
$(CXX) $(LDFLAGS) -o $@ test/main.o $(LDLIBS) $(NAME).a libusrsctp.a $(CXX) $(LDFLAGS) -o $@ test/main.o $(NAME).a $(LDLIBS)
clean: clean:
-$(RM) include/rtc/*.d *.d -$(RM) include/rtc/*.d *.d
@ -54,11 +72,13 @@ dist-clean: clean
-$(RM) $(NAME).a -$(RM) $(NAME).a
-$(RM) $(NAME).so -$(RM) $(NAME).so
-$(RM) libusrsctp.a -$(RM) libusrsctp.a
-$(RM) libjuice.a
-$(RM) tests -$(RM) tests
-$(RM) include/*~ -$(RM) include/*~
-$(RM) src/*~ -$(RM) src/*~
-$(RM) test/*~ -$(RM) test/*~
-cd $(USRSCTP_DIR) && make clean -cd $(USRSCTP_DIR) && make clean
-cd $(JUICE_DIR) && make clean
libusrsctp.a: libusrsctp.a:
cd $(USRSCTP_DIR) && \ cd $(USRSCTP_DIR) && \
@ -67,3 +87,11 @@ libusrsctp.a:
make make
cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a . cp $(USRSCTP_DIR)/usrsctplib/.libs/libusrsctp.a .
libjuice.a:
ifneq ($(USE_GNUTLS), 0)
cd $(JUICE_DIR) && make USE_NETTLE=1
else
cd $(JUICE_DIR) && make USE_NETTLE=0
endif
cp $(JUICE_DIR)/libjuice.a .

View File

@ -4,30 +4,43 @@ libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 wi
This projet is originally inspired by [librtcdcpp](https://github.com/chadnickbok/librtcdcpp), however it is a complete rewrite from scratch, because the messy architecture of librtcdcpp made solving its implementation issues difficult. This projet is originally inspired by [librtcdcpp](https://github.com/chadnickbok/librtcdcpp), however it is a complete rewrite from scratch, because the messy architecture of librtcdcpp made solving its implementation issues difficult.
The connectivity can be provided through my ad-hoc ICE library [libjuice](https://github.com/paullouisageneau/libjuice) as submodule or through [libnice](https://github.com/libnice/libnice). The security layer can be provided through [GnuTLS](https://www.gnutls.org/) or [OpenSSL](https://www.openssl.org/).
Licensed under LGPLv2, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE). Licensed under LGPLv2, see [LICENSE](https://github.com/paullouisageneau/libdatachannel/blob/master/LICENSE).
## Compatibility ## Compatibility
The library aims at fully implementing SCTP DataChannels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13)) over DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261)) and has been tested to be compatible with Firefox and Chromium. It supports IPv6 and Multicast DNS candidates resolution ([draft-ietf-rtcweb-mdns-ice-candidates-03](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03)) provided the operating system also supports it. The library aims at fully implementing WebRTC SCTP DataChannels ([draft-ietf-rtcweb-data-channel-13](https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13)) over DTLS/UDP ([RFC7350](https://tools.ietf.org/html/rfc7350) and [RFC8261](https://tools.ietf.org/html/rfc8261)) with ICE ([RFC8445](https://tools.ietf.org/html/rfc8445)). It has been tested to be compatible with Firefox and Chromium. It supports IPv6 and Multicast DNS candidates resolution ([draft-ietf-rtcweb-mdns-ice-candidates-03](https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03)) provided the operating system also supports it.
## Dependencies ## Dependencies
- libnice: https://github.com/libnice/libnice
- GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/ - GnuTLS: https://www.gnutls.org/ or OpenSSL: https://www.openssl.org/
Optional:
- libnice: https://nice.freedesktop.org/ (substituable with libjuice)
Submodules: Submodules:
- usrsctp: https://github.com/sctplab/usrsctp - usrsctp: https://github.com/sctplab/usrsctp
- libjuice: https://github.com/paullouisageneau/libjuice
## Building ## Building
### Building with CMake (preferred)
```bash ```bash
$ git submodule update --init --recursive $ git submodule update --init --recursive
$ mkdir build $ mkdir build
$ cd build $ cd build
$ cmake -DUSE_GNUTLS=1 .. $ cmake -DUSE_JUICE=1 -DUSE_GNUTLS=1 ..
$ make $ make
``` ```
### Building directly with Make
```bash
$ git submodule update --init --recursive
$ make USE_JUICE=1 USE_GNUTLS=1
```
## Example ## Example
In the following example, note the callbacks are called in another thread. In the following example, note the callbacks are called in another thread.

1
deps/libjuice vendored Submodule

Submodule deps/libjuice added at 65f492dbff

2
deps/usrsctp vendored

View File

@ -28,15 +28,20 @@ namespace rtc {
struct IceServer { struct IceServer {
enum class Type { Stun, Turn }; enum class Type { Stun, Turn };
// Don' Change It! It should be same order as enum NiceRelayType
enum class RelayType { TurnUdp, TurnTcp, TurnTls }; enum class RelayType { TurnUdp, TurnTcp, TurnTls };
IceServer(const string &host_); // Any type
IceServer(const string &hostname_, uint16_t port_); IceServer(const string &url);
IceServer(const string &hostname_, const string &service_);
IceServer(const string &hostname_, const string &service_, string username_, string password_, // STUN
RelayType relayType_); IceServer(string hostname_, uint16_t port_);
IceServer(string hostname_, string service_);
// TURN
IceServer(string hostname_, uint16_t port, string username_, string password_,
RelayType relayType_ = RelayType::TurnUdp);
IceServer(string hostname_, string service_, string username_, string password_,
RelayType relayType_ = RelayType::TurnUdp);
string hostname; string hostname;
string service; string service;

View File

@ -57,6 +57,8 @@ public:
operator string() const; operator string() const;
string generateSdp(const string &eol) const;
private: private:
Type mType; Type mType;
Role mRole; Role mRole;

View File

@ -44,6 +44,8 @@ using std::uint32_t;
using std::uint64_t; using std::uint64_t;
using std::uint8_t; using std::uint8_t;
// Constants
const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length const size_t MAX_NUMERICNODE_LEN = 48; // Max IPv6 string representation length
const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length const size_t MAX_NUMERICSERV_LEN = 6; // Max port string representation length
@ -51,16 +53,9 @@ const uint16_t DEFAULT_SCTP_PORT = 5000; // SCTP port to use by default
const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP const size_t DEFAULT_MAX_MESSAGE_SIZE = 65536; // Remote max message size if not specified in SDP
const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size const size_t LOCAL_MAX_MESSAGE_SIZE = 256 * 1024; // Local max message size
inline void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr) { // Log
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
if (!appender)
appender = &consoleAppender;
plog::init(severity, appender);
PLOG_DEBUG << "Logger initialized";
}
// Don't change, it must match plog severity enum class LogLevel { // Don't change, it must match plog severity
enum class LogLevel {
None = 0, None = 0,
Fatal = 1, Fatal = 1,
Error = 2, Error = 2,
@ -70,8 +65,18 @@ enum class LogLevel {
Verbose = 6 Verbose = 6
}; };
inline void InitLogger(plog::Severity severity, plog::IAppender *appender = nullptr) {
static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;
if (!appender)
appender = &consoleAppender;
plog::init(severity, appender);
PLOG_DEBUG << "Logger initialized";
}
inline void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); } inline void InitLogger(LogLevel level) { InitLogger(static_cast<plog::Severity>(level)); }
// Utils
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>; template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;

View File

@ -54,7 +54,7 @@ bool Candidate::resolve(ResolveMode mode) {
if (mIsResolved) if (mIsResolved)
return true; return true;
// See RFC 5245 for format // See RFC 8445 for format
std::stringstream ss(mCandidate); std::stringstream ss(mCandidate);
int component{0}, priority{0}; int component{0}, priority{0};
string foundation, transport, node, service, typ_, type; string foundation, transport, node, service, typ_, type;

View File

@ -18,29 +18,70 @@
#include "configuration.hpp" #include "configuration.hpp"
#include <regex>
namespace rtc { namespace rtc {
using std::to_string; IceServer::IceServer(const string &url) {
// Modified regex from RFC 3986, see https://tools.ietf.org/html/rfc3986#appendix-B
static const char *rs =
R"(^(([^:.@/?#]+):)?(/{0,2}((([^:@]*)(:([^@]*))?)@)?(([^:/?#]*)(:([^/?#]*))?))?([^?#]*)(\?([^#]*))?(#(.*))?)";
static const std::regex r(rs, std::regex::extended);
IceServer::IceServer(const string &host) : type(Type::Stun) { std::smatch m;
if (size_t pos = host.rfind(':'); pos != string::npos) { if (!std::regex_match(url, m, r) || m[10].length() == 0)
hostname = host.substr(0, pos); throw std::invalid_argument("Invalid ICE server URL: " + url);
service = host.substr(pos + 1);
} else { std::vector<std::optional<string>> opt(m.size());
hostname = host; std::transform(m.begin(), m.end(), opt.begin(), [](const auto &sm) {
service = "3478"; // STUN UDP port return sm.length() > 0 ? std::make_optional(string(sm)) : nullopt;
});
string scheme = opt[2].value_or("stun");
if (scheme == "stun" || scheme == "STUN")
type = Type::Stun;
else if (scheme == "turn" || scheme == "TURN")
type = Type::Turn;
else if (scheme == "turns" || scheme == "TURNS")
type = Type::Turn;
else
throw std::invalid_argument("Unknown ICE server protocol: " + scheme);
relayType = RelayType::TurnUdp;
if (auto &query = opt[15]) {
if (query->find("transport=udp") != string::npos)
relayType = RelayType::TurnUdp;
if (query->find("transport=tcp") != string::npos)
relayType = RelayType::TurnTcp;
if (query->find("transport=tls") != string::npos)
relayType = RelayType::TurnTls;
} }
username = opt[6].value_or("");
password = opt[8].value_or("");
hostname = opt[10].value();
service = opt[12].value_or(relayType == RelayType::TurnTls ? "5349" : "3478");
while (!hostname.empty() && hostname.front() == '[')
hostname.erase(hostname.begin());
while (!hostname.empty() && hostname.back() == ']')
hostname.pop_back();
} }
IceServer::IceServer(const string &hostname_, uint16_t port_) IceServer::IceServer(string hostname_, uint16_t port_)
: IceServer(hostname_, to_string(port_)) {} : IceServer(std::move(hostname_), std::to_string(port_)) {}
IceServer::IceServer(const string &hostname_, const string &service_) IceServer::IceServer(string hostname_, string service_)
: hostname(hostname_), service(service_), type(Type::Stun) {} : hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Stun) {}
IceServer::IceServer(const string &hostname_, const string &service_, string username_, IceServer::IceServer(string hostname_, uint16_t port_, string username_, string password_,
string password_, RelayType relayType_) RelayType relayType_)
: hostname(hostname_), service(service_), type(Type::Turn), username(username_), : IceServer(hostname_, std::to_string(port_), std::move(username_), std::move(password_),
password(password_), relayType(relayType_) {} relayType_) {}
IceServer::IceServer(string hostname_, string service_, string username_, string password_,
RelayType relayType_)
: hostname(std::move(hostname_)), service(std::move(service_)), type(Type::Turn),
username(std::move(username_)), password(std::move(password_)), relayType(relayType_) {}
} // namespace rtc } // namespace rtc

View File

@ -130,37 +130,39 @@ std::vector<Candidate> Description::extractCandidates() {
return result; return result;
} }
Description::operator string() const { Description::operator string() const { return generateSdp("\r\n"); }
string Description::generateSdp(const string &eol) const {
if (!mFingerprint) if (!mFingerprint)
throw std::logic_error("Fingerprint must be set to generate a SDP"); throw std::logic_error("Fingerprint must be set to generate an SDP string");
std::ostringstream sdp; std::ostringstream sdp;
sdp << "v=0\n"; sdp << "v=0" << eol;
sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1\n"; sdp << "o=- " << mSessionId << " 0 IN IP4 127.0.0.1" << eol;
sdp << "s=-\n"; sdp << "s=-" << eol;
sdp << "t=0 0\n"; sdp << "t=0 0" << eol;
sdp << "a=group:BUNDLE 0\n"; sdp << "a=group:BUNDLE 0" << eol;
sdp << "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\n"; sdp << "m=application 9 UDP/DTLS/SCTP webrtc-datachannel" << eol;
sdp << "c=IN IP4 0.0.0.0\n"; sdp << "c=IN IP4 0.0.0.0" << eol;
sdp << "a=ice-ufrag:" << mIceUfrag << "\n"; sdp << "a=ice-ufrag:" << mIceUfrag << eol;
sdp << "a=ice-pwd:" << mIcePwd << "\n"; sdp << "a=ice-pwd:" << mIcePwd << eol;
if (mTrickle) if (mTrickle)
sdp << "a=ice-options:trickle\n"; sdp << "a=ice-options:trickle" << eol;
sdp << "a=mid:" << mMid << "\n"; sdp << "a=mid:" << mMid << eol;
sdp << "a=setup:" << roleToString(mRole) << "\n"; sdp << "a=setup:" << roleToString(mRole) << eol;
sdp << "a=dtls-id:1\n"; sdp << "a=dtls-id:1" << eol;
if (mFingerprint) if (mFingerprint)
sdp << "a=fingerprint:sha-256 " << *mFingerprint << "\n"; sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
if (mSctpPort) if (mSctpPort)
sdp << "a=sctp-port:" << *mSctpPort << "\n"; sdp << "a=sctp-port:" << *mSctpPort << eol;
if (mMaxMessageSize) if (mMaxMessageSize)
sdp << "a=max-message-size:" << *mMaxMessageSize << "\n"; sdp << "a=max-message-size:" << *mMaxMessageSize << eol;
for (const auto &candidate : mCandidates) { for (const auto &candidate : mCandidates) {
sdp << string(candidate) << "\n"; sdp << string(candidate) << eol;
} }
if (!mTrickle) if (!mTrickle)
sdp << "a=end-of-candidates\n"; sdp << "a=end-of-candidates" << eol;
return sdp.str(); return sdp.str();
} }

View File

@ -18,6 +18,7 @@
#include "dtlstransport.hpp" #include "dtlstransport.hpp"
#include "icetransport.hpp" #include "icetransport.hpp"
#include "message.hpp"
#include <cassert> #include <cassert>
#include <chrono> #include <chrono>
@ -270,7 +271,7 @@ int DtlsTransport::TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms)
} // namespace rtc } // namespace rtc
#else #else // USE_GNUTLS==0
#include <openssl/bio.h> #include <openssl/bio.h>
#include <openssl/ec.h> #include <openssl/ec.h>
@ -318,11 +319,21 @@ bool check_openssl_ret(SSL *ssl, int ret, const string &message = "OpenSSL error
namespace rtc { namespace rtc {
BIO_METHOD *DtlsTransport::BioMethods = NULL;
int DtlsTransport::TransportExIndex = -1; int DtlsTransport::TransportExIndex = -1;
std::mutex DtlsTransport::GlobalMutex; std::mutex DtlsTransport::GlobalMutex;
void DtlsTransport::GlobalInit() { void DtlsTransport::GlobalInit() {
std::lock_guard lock(GlobalMutex); std::lock_guard lock(GlobalMutex);
if (!BioMethods) {
BioMethods = BIO_meth_new(BIO_TYPE_BIO, "DTLS writer");
if (!BioMethods)
throw std::runtime_error("Unable to BIO methods for DTLS writer");
BIO_meth_set_create(BioMethods, BioMethodNew);
BIO_meth_set_destroy(BioMethods, BioMethodFree);
BIO_meth_set_write(BioMethods, BioMethodWrite);
BIO_meth_set_ctrl(BioMethods, BioMethodCtrl);
}
if (TransportExIndex < 0) { if (TransportExIndex < 0) {
TransportExIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); TransportExIndex = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
} }
@ -346,7 +357,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
// RFC 8261: SCTP performs segmentation and reassembly based on the path MTU. // RFC 8261: SCTP performs segmentation and reassembly based on the path MTU.
// Therefore, the DTLS layer MUST NOT use any compression algorithm. // Therefore, the DTLS layer MUST NOT use any compression algorithm.
// See https://tools.ietf.org/html/rfc8261#section-5 // See https://tools.ietf.org/html/rfc8261#section-5
SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION); SSL_CTX_set_options(mCtx, SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_NO_QUERY_MTU);
SSL_CTX_set_min_proto_version(mCtx, DTLS1_VERSION); SSL_CTX_set_min_proto_version(mCtx, DTLS1_VERSION);
SSL_CTX_set_read_ahead(mCtx, 1); SSL_CTX_set_read_ahead(mCtx, 1);
SSL_CTX_set_quiet_shutdown(mCtx, 1); SSL_CTX_set_quiet_shutdown(mCtx, 1);
@ -362,7 +373,7 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
check_openssl(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed"); check_openssl(SSL_CTX_check_private_key(mCtx), "SSL local private key check failed");
if (!(mSsl = SSL_new(mCtx))) if (!(mSsl = SSL_new(mCtx)))
throw std::runtime_error("Unable to create SSL instance"); throw std::runtime_error("Unable to create SSL instance");
SSL_set_ex_data(mSsl, TransportExIndex, this); SSL_set_ex_data(mSsl, TransportExIndex, this);
SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6 SSL_set_mtu(mSsl, 1280 - 40 - 8); // min MTU over UDP/IPv6
@ -372,11 +383,11 @@ DtlsTransport::DtlsTransport(shared_ptr<IceTransport> lower, shared_ptr<Certific
else else
SSL_set_accept_state(mSsl); SSL_set_accept_state(mSsl);
if (!(mInBio = BIO_new(BIO_s_mem())) || !(mOutBio = BIO_new(BIO_s_mem()))) if (!(mInBio = BIO_new(BIO_s_mem())) || !(mOutBio = BIO_new(BioMethods)))
throw std::runtime_error("Unable to create BIO"); throw std::runtime_error("Unable to create BIO");
BIO_set_mem_eof_return(mInBio, BIO_EOF); BIO_set_mem_eof_return(mInBio, BIO_EOF);
BIO_set_mem_eof_return(mOutBio, BIO_EOF); BIO_set_data(mOutBio, this);
SSL_set_bio(mSsl, mInBio, mOutBio); SSL_set_bio(mSsl, mInBio, mOutBio);
auto ecdh = unique_ptr<EC_KEY, decltype(&EC_KEY_free)>( auto ecdh = unique_ptr<EC_KEY, decltype(&EC_KEY_free)>(
@ -403,7 +414,6 @@ void DtlsTransport::stop() {
mRecvThread.join(); mRecvThread.join();
SSL_shutdown(mSsl); SSL_shutdown(mSsl);
writePending();
} }
} }
@ -416,11 +426,8 @@ bool DtlsTransport::send(message_ptr message) {
PLOG_VERBOSE << "Send size=" << message->size(); PLOG_VERBOSE << "Send size=" << message->size();
int ret = SSL_write(mSsl, message->data(), message->size()); int ret = SSL_write(mSsl, message->data(), message->size());
if (!check_openssl_ret(mSsl, ret)) { if (!check_openssl_ret(mSsl, ret))
return false; return false;
}
writePending();
return true; return true;
} }
@ -437,15 +444,14 @@ void DtlsTransport::changeState(State state) {
} }
void DtlsTransport::runRecvLoop() { void DtlsTransport::runRecvLoop() {
const size_t bufferSize = 4096; const size_t maxMtu = 4096;
byte buffer[bufferSize];
try { try {
changeState(State::Connecting); changeState(State::Connecting);
SSL_do_handshake(mSsl); SSL_do_handshake(mSsl);
writePending();
const size_t bufferSize = maxMtu;
byte buffer[bufferSize];
while (auto next = mIncomingQueue.pop()) { while (auto next = mIncomingQueue.pop()) {
auto message = *next; auto message = *next;
BIO_write(mInBio, message->data(), message->size()); BIO_write(mInBio, message->data(), message->size());
@ -459,9 +465,13 @@ void DtlsTransport::runRecvLoop() {
if (unsigned long err = ERR_get_error()) if (unsigned long err = ERR_get_error())
throw std::runtime_error("handshake failed: " + openssl_error_string(err)); throw std::runtime_error("handshake failed: " + openssl_error_string(err));
writePending(); if (SSL_is_init_finished(mSsl)) {
if (SSL_is_init_finished(mSsl))
changeState(State::Connected); changeState(State::Connected);
// RFC 8261: DTLS MUST support sending messages larger than the current path MTU
// See https://tools.ietf.org/html/rfc8261#section-5
SSL_set_mtu(mSsl, maxMtu + 1);
}
} }
if (decrypted) if (decrypted)
@ -481,16 +491,6 @@ void DtlsTransport::runRecvLoop() {
} }
} }
void DtlsTransport::writePending() {
const size_t bufferSize = 4096;
byte buffer[bufferSize];
while (BIO_ctrl_pending(mOutBio) > 0) {
int ret = BIO_read(mOutBio, buffer, bufferSize);
if (check_openssl_ret(mSsl, ret) && ret > 0)
outgoing(make_message(buffer, buffer + ret));
}
}
int DtlsTransport::CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx) { int DtlsTransport::CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx) {
SSL *ssl = SSL *ssl =
static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
@ -515,6 +515,45 @@ void DtlsTransport::InfoCallback(const SSL *ssl, int where, int ret) {
} }
} }
int DtlsTransport::BioMethodNew(BIO *bio) {
BIO_set_init(bio, 1);
BIO_set_data(bio, NULL);
BIO_set_shutdown(bio, 0);
return 1;
}
int DtlsTransport::BioMethodFree(BIO *bio) {
if (!bio)
return 0;
BIO_set_data(bio, NULL);
return 1;
}
int DtlsTransport::BioMethodWrite(BIO *bio, const char *in, int inl) {
if (inl <= 0)
return inl;
auto transport = reinterpret_cast<DtlsTransport *>(BIO_get_data(bio));
if (!transport)
return -1;
auto b = reinterpret_cast<const byte *>(in);
return transport->outgoing(make_message(b, b + inl)) ? inl : 0;
}
long DtlsTransport::BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr) {
switch (cmd) {
case BIO_CTRL_FLUSH:
return 1;
case BIO_CTRL_DGRAM_QUERY_MTU:
return 0; // SSL_OP_NO_QUERY_MTU must be set
case BIO_CTRL_WPENDING:
case BIO_CTRL_PENDING:
return 0;
default:
break;
}
return 0;
}
} // namespace rtc } // namespace rtc
#endif #endif

View File

@ -79,18 +79,22 @@ private:
static ssize_t ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen); static ssize_t ReadCallback(gnutls_transport_ptr_t ptr, void *data, size_t maxlen);
static int TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms); static int TimeoutCallback(gnutls_transport_ptr_t ptr, unsigned int ms);
#else #else
void writePending();
SSL_CTX *mCtx; SSL_CTX *mCtx;
SSL *mSsl; SSL *mSsl;
BIO *mInBio, *mOutBio; BIO *mInBio, *mOutBio;
static BIO_METHOD *BioMethods;
static int TransportExIndex; static int TransportExIndex;
static std::mutex GlobalMutex; static std::mutex GlobalMutex;
static void GlobalInit(); static void GlobalInit();
static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx); static int CertificateCallback(int preverify_ok, X509_STORE_CTX *ctx);
static void InfoCallback(const SSL *ssl, int where, int ret); static void InfoCallback(const SSL *ssl, int where, int ret);
static int BioMethodNew(BIO *bio);
static int BioMethodFree(BIO *bio);
static int BioMethodWrite(BIO *bio, const char *in, int inl);
static long BioMethodCtrl(BIO *bio, int cmd, long num, void *ptr);
#endif #endif
}; };

View File

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2019 Paul-Louis Ageneau * Copyright (c) 2019-2020 Paul-Louis Ageneau
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -17,6 +17,7 @@
*/ */
#include "icetransport.hpp" #include "icetransport.hpp"
#include "configuration.hpp"
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
@ -27,23 +28,245 @@
#include <random> #include <random>
#include <sstream> #include <sstream>
namespace rtc {
using namespace std::chrono_literals; using namespace std::chrono_literals;
using std::shared_ptr; using std::shared_ptr;
using std::weak_ptr; using std::weak_ptr;
#if USE_JUICE
namespace rtc {
IceTransport::IceTransport(const Configuration &config, Description::Role role, IceTransport::IceTransport(const Configuration &config, Description::Role role,
candidate_callback candidateCallback, state_callback stateChangeCallback, candidate_callback candidateCallback, state_callback stateChangeCallback,
gathering_state_callback gatheringStateChangeCallback) gathering_state_callback gatheringStateChangeCallback)
: mRole(role), mMid("0"), mState(State::Disconnected), mGatheringState(GatheringState::New), : mRole(role), mMid("0"), mState(State::Disconnected), mGatheringState(GatheringState::New),
mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr),
mCandidateCallback(std::move(candidateCallback)), mCandidateCallback(std::move(candidateCallback)),
mStateChangeCallback(std::move(stateChangeCallback)), mStateChangeCallback(std::move(stateChangeCallback)),
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)) { mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
mAgent(nullptr, nullptr) {
PLOG_DEBUG << "Initializing ICE transport"; PLOG_DEBUG << "Initializing ICE transport (libjuice)";
if (config.enableIceTcp) {
PLOG_WARNING << "ICE-TCP is not supported with libjuice";
}
juice_set_log_handler(IceTransport::LogCallback);
juice_set_log_level(JUICE_LOG_LEVEL_VERBOSE);
juice_config_t jconfig = {};
jconfig.cb_state_changed = IceTransport::StateChangeCallback;
jconfig.cb_candidate = IceTransport::CandidateCallback;
jconfig.cb_gathering_done = IceTransport::GatheringDoneCallback;
jconfig.cb_recv = IceTransport::RecvCallback;
jconfig.user_ptr = this;
// Randomize servers order
std::vector<IceServer> servers = config.iceServers;
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
// Pick a STUN server
for (auto &server : servers) {
if (!server.hostname.empty() && server.type == IceServer::Type::Stun) {
if (server.service.empty())
server.service = "3478"; // STUN UDP port
PLOG_DEBUG << "Using STUN server \"" << server.hostname << ":" << server.service
<< "\"";
mStunHostname = server.hostname;
mStunService = server.service;
jconfig.stun_server_host = mStunHostname.c_str();
jconfig.stun_server_port = std::stoul(mStunService);
}
}
// TURN support is not implemented yet
// Create agent
mAgent = decltype(mAgent)(juice_create(&jconfig), juice_destroy);
if (!mAgent)
throw std::runtime_error("Failed to create the ICE agent");
}
IceTransport::~IceTransport() { stop(); }
void IceTransport::stop() {
// Nothing to do
}
Description::Role IceTransport::role() const { return mRole; }
IceTransport::State IceTransport::state() const { return mState; }
Description IceTransport::getLocalDescription(Description::Type type) const {
char sdp[JUICE_MAX_SDP_STRING_LEN];
if (juice_get_local_description(mAgent.get(), sdp, JUICE_MAX_SDP_STRING_LEN) < 0)
throw std::runtime_error("Failed to generate local SDP");
return Description(string(sdp), type, mRole);
}
void IceTransport::setRemoteDescription(const Description &description) {
mRole = description.role() == Description::Role::Active ? Description::Role::Passive
: Description::Role::Active;
mMid = description.mid();
// TODO
// mTrickleTimeout = description.trickleEnabled() ? 30s : 0s;
if (juice_set_remote_description(mAgent.get(), string(description).c_str()) < 0)
throw std::runtime_error("Failed to parse remote SDP");
}
bool IceTransport::addRemoteCandidate(const Candidate &candidate) {
// Don't try to pass unresolved candidates for more safety
if (!candidate.isResolved())
return false;
return juice_add_remote_candidate(mAgent.get(), string(candidate).c_str()) >= 0;
}
void IceTransport::gatherLocalCandidates() {
// Change state now as candidates calls can be synchronous
changeGatheringState(GatheringState::InProgress);
if (juice_gather_candidates(mAgent.get()) < 0) {
throw std::runtime_error("Failed to gather local ICE candidates");
}
}
std::optional<string> IceTransport::getLocalAddress() const {
char str[JUICE_MAX_ADDRESS_STRING_LEN];
if (juice_get_selected_addresses(mAgent.get(), str, JUICE_MAX_ADDRESS_STRING_LEN, NULL, 0) ==
0) {
return std::make_optional(string(str));
}
return nullopt;
}
std::optional<string> IceTransport::getRemoteAddress() const {
char str[JUICE_MAX_ADDRESS_STRING_LEN];
if (juice_get_selected_addresses(mAgent.get(), NULL, 0, str, JUICE_MAX_ADDRESS_STRING_LEN) ==
0) {
return std::make_optional(string(str));
}
return nullopt;
}
bool IceTransport::send(message_ptr message) {
if (!message || (mState != State::Connected && mState != State::Completed))
return false;
PLOG_VERBOSE << "Send size=" << message->size();
return outgoing(message);
}
void IceTransport::incoming(message_ptr message) { recv(message); }
void IceTransport::incoming(const byte *data, int size) {
incoming(make_message(data, data + size));
}
bool IceTransport::outgoing(message_ptr message) {
return juice_send(mAgent.get(), reinterpret_cast<const char *>(message->data()),
message->size()) >= 0;
}
void IceTransport::changeState(State state) {
if (mState.exchange(state) != state)
mStateChangeCallback(mState);
}
void IceTransport::changeGatheringState(GatheringState state) {
if (mGatheringState.exchange(state) != state)
mGatheringStateChangeCallback(mGatheringState);
}
void IceTransport::processStateChange(unsigned int state) {
changeState(static_cast<State>(state));
}
void IceTransport::processCandidate(const string &candidate) {
mCandidateCallback(Candidate(candidate, mMid));
}
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
void IceTransport::StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try {
iceTransport->processStateChange(static_cast<unsigned int>(state));
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
void IceTransport::CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try {
iceTransport->processCandidate(sdp);
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
void IceTransport::GatheringDoneCallback(juice_agent_t *agent, void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try {
iceTransport->processGatheringDone();
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
void IceTransport::RecvCallback(juice_agent_t *agent, const char *data, size_t size,
void *user_ptr) {
auto iceTransport = static_cast<rtc::IceTransport *>(user_ptr);
try {
iceTransport->incoming(reinterpret_cast<const byte *>(data), size);
} catch (const std::exception &e) {
PLOG_WARNING << e.what();
}
}
void IceTransport::LogCallback(juice_log_level_t level, const char *message) {
plog::Severity severity;
switch (level) {
case JUICE_LOG_LEVEL_FATAL:
severity = plog::fatal;
break;
case JUICE_LOG_LEVEL_ERROR:
severity = plog::error;
break;
case JUICE_LOG_LEVEL_WARN:
severity = plog::warning;
break;
case JUICE_LOG_LEVEL_INFO:
severity = plog::info;
break;
case JUICE_LOG_LEVEL_DEBUG:
severity = plog::debug;
break;
default:
severity = plog::verbose;
break;
}
PLOG(severity) << "juice: " << message;
}
} // namespace rtc
#else // USE_JUICE == 0
namespace rtc {
IceTransport::IceTransport(const Configuration &config, Description::Role role,
candidate_callback candidateCallback, state_callback stateChangeCallback,
gathering_state_callback gatheringStateChangeCallback)
: mRole(role), mMid("0"), mState(State::Disconnected), mGatheringState(GatheringState::New),
mCandidateCallback(std::move(candidateCallback)),
mStateChangeCallback(std::move(stateChangeCallback)),
mGatheringStateChangeCallback(std::move(gatheringStateChangeCallback)),
mNiceAgent(nullptr, nullptr), mMainLoop(nullptr, nullptr) {
PLOG_DEBUG << "Initializing ICE transport (libnice)";
g_log_set_handler("libnice", G_LOG_LEVEL_MASK, LogCallback, this); g_log_set_handler("libnice", G_LOG_LEVEL_MASK, LogCallback, this);
@ -55,6 +278,7 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
if (!mMainLoop) if (!mMainLoop)
std::runtime_error("Failed to create the main loop"); std::runtime_error("Failed to create the main loop");
// RFC 5245 was obsoleted by RFC 8445 but this should be OK.
mNiceAgent = decltype(mNiceAgent)( mNiceAgent = decltype(mNiceAgent)(
nice_agent_new(g_main_loop_get_context(mMainLoop.get()), NICE_COMPATIBILITY_RFC5245), nice_agent_new(g_main_loop_get_context(mMainLoop.get()), NICE_COMPATIBILITY_RFC5245),
g_object_unref); g_object_unref);
@ -64,25 +288,37 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
mMainLoopThread = std::thread(g_main_loop_run, mMainLoop.get()); mMainLoopThread = std::thread(g_main_loop_run, mMainLoop.get());
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", TRUE, nullptr); mStreamId = nice_agent_add_stream(mNiceAgent.get(), 1);
if (!mStreamId)
throw std::runtime_error("Failed to add a stream");
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", TRUE, nullptr); // decided later
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-udp", TRUE, nullptr); g_object_set(G_OBJECT(mNiceAgent.get()), "ice-udp", TRUE, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "ice-tcp", config.enableIceTcp ? TRUE : FALSE, g_object_set(G_OBJECT(mNiceAgent.get()), "ice-tcp", config.enableIceTcp ? TRUE : FALSE,
nullptr); nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-initial-timeout", 200, nullptr);
// RFC 8445: Agents MUST NOT use an RTO value smaller than 500 ms.
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-initial-timeout", 500, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-max-retransmissions", 3, nullptr); g_object_set(G_OBJECT(mNiceAgent.get()), "stun-max-retransmissions", 3, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-pacing-timer", 20, nullptr);
// RFC 8445: ICE agents SHOULD use a default Ta value, 50 ms, but MAY use another value based on
// the characteristics of the associated data.
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-pacing-timer", 25, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr); g_object_set(G_OBJECT(mNiceAgent.get()), "upnp", FALSE, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr); g_object_set(G_OBJECT(mNiceAgent.get()), "upnp-timeout", 200, nullptr);
// Randomize order
std::vector<IceServer> servers = config.iceServers; std::vector<IceServer> servers = config.iceServers;
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed)); std::shuffle(servers.begin(), servers.end(), std::default_random_engine(seed));
// Add one STUN server
bool success = false; bool success = false;
for (auto &server : servers) { for (auto &server : servers) {
if (server.hostname.empty()) if (server.hostname.empty())
continue; continue;
if (server.type == IceServer::Type::Turn) if (server.type != IceServer::Type::Stun)
continue; continue;
if (server.service.empty()) if (server.service.empty())
server.service = "3478"; // STUN UDP port server.service = "3478"; // STUN UDP port
@ -101,9 +337,10 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
char nodebuffer[MAX_NUMERICNODE_LEN]; char nodebuffer[MAX_NUMERICNODE_LEN];
char servbuffer[MAX_NUMERICSERV_LEN]; char servbuffer[MAX_NUMERICSERV_LEN];
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN, if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
servbuffer, MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICNODE_LEN,
NI_NUMERICHOST | NI_NUMERICSERV) == 0) { NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
PLOG_DEBUG << "Using STUN server \"" << server.hostname << ":" << server.service
<< "\"";
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server", nodebuffer, nullptr); g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server", nodebuffer, nullptr);
g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server-port", g_object_set(G_OBJECT(mNiceAgent.get()), "stun-server-port",
std::stoul(servbuffer), nullptr); std::stoul(servbuffer), nullptr);
@ -118,35 +355,17 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
break; break;
} }
g_signal_connect(G_OBJECT(mNiceAgent.get()), "component-state-changed", // Add TURN servers
G_CALLBACK(StateChangeCallback), this);
g_signal_connect(G_OBJECT(mNiceAgent.get()), "new-candidate-full",
G_CALLBACK(CandidateCallback), this);
g_signal_connect(G_OBJECT(mNiceAgent.get()), "candidate-gathering-done",
G_CALLBACK(GatheringDoneCallback), this);
mStreamId = nice_agent_add_stream(mNiceAgent.get(), 1);
if (!mStreamId)
throw std::runtime_error("Failed to add a stream");
nice_agent_set_stream_name(mNiceAgent.get(), mStreamId, "application");
nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1, config.portRangeBegin,
config.portRangeEnd);
nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(mMainLoop.get()),
RecvCallback, this);
// Add TURN Servers
for (auto &server : servers) { for (auto &server : servers) {
if (server.hostname.empty()) if (server.hostname.empty())
continue; continue;
if (server.type == IceServer::Type::Stun) if (server.type != IceServer::Type::Turn)
continue; continue;
if (server.service.empty()) if (server.service.empty())
server.service = "3478"; // TURN UDP port server.service = server.relayType == IceServer::RelayType::TurnTls ? "5349" : "3478";
struct addrinfo hints = {}; struct addrinfo hints = {};
hints.ai_family = AF_INET; // IPv4 hints.ai_family = AF_UNSPEC;
hints.ai_socktype = hints.ai_socktype =
server.relayType == IceServer::RelayType::TurnUdp ? SOCK_DGRAM : SOCK_STREAM; server.relayType == IceServer::RelayType::TurnUdp ? SOCK_DGRAM : SOCK_STREAM;
hints.ai_protocol = hints.ai_protocol =
@ -157,24 +376,48 @@ IceTransport::IceTransport(const Configuration &config, Description::Role role,
continue; continue;
for (auto p = result; p; p = p->ai_next) { for (auto p = result; p; p = p->ai_next) {
if (p->ai_family == AF_INET) { if (p->ai_family == AF_INET || p->ai_family == AF_INET6) {
char nodebuffer[MAX_NUMERICNODE_LEN]; char nodebuffer[MAX_NUMERICNODE_LEN];
char servbuffer[MAX_NUMERICSERV_LEN]; char servbuffer[MAX_NUMERICSERV_LEN];
if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN, if (getnameinfo(p->ai_addr, p->ai_addrlen, nodebuffer, MAX_NUMERICNODE_LEN,
servbuffer, MAX_NUMERICNODE_LEN, servbuffer, MAX_NUMERICNODE_LEN,
NI_NUMERICHOST | NI_NUMERICSERV) == 0) { NI_NUMERICHOST | NI_NUMERICSERV) == 0) {
NiceRelayType niceRelayType;
switch (server.relayType) {
case IceServer::RelayType::TurnTcp:
niceRelayType = NICE_RELAY_TYPE_TURN_TCP;
break;
case IceServer::RelayType::TurnTls:
niceRelayType = NICE_RELAY_TYPE_TURN_TLS;
break;
default:
niceRelayType = NICE_RELAY_TYPE_TURN_UDP;
break;
}
nice_agent_set_relay_info(mNiceAgent.get(), mStreamId, 1, nodebuffer, nice_agent_set_relay_info(mNiceAgent.get(), mStreamId, 1, nodebuffer,
std::stoul(servbuffer), server.username.c_str(), std::stoul(servbuffer), server.username.c_str(),
server.password.c_str(), server.password.c_str(), niceRelayType);
static_cast<NiceRelayType>(server.relayType));
break;
} }
} }
} }
freeaddrinfo(result); freeaddrinfo(result);
} }
g_signal_connect(G_OBJECT(mNiceAgent.get()), "component-state-changed",
G_CALLBACK(StateChangeCallback), this);
g_signal_connect(G_OBJECT(mNiceAgent.get()), "new-candidate-full",
G_CALLBACK(CandidateCallback), this);
g_signal_connect(G_OBJECT(mNiceAgent.get()), "candidate-gathering-done",
G_CALLBACK(GatheringDoneCallback), this);
nice_agent_set_stream_name(mNiceAgent.get(), mStreamId, "application");
nice_agent_set_port_range(mNiceAgent.get(), mStreamId, 1, config.portRangeBegin,
config.portRangeEnd);
nice_agent_attach_recv(mNiceAgent.get(), mStreamId, 1, g_main_loop_get_context(mMainLoop.get()),
RecvCallback, this);
} }
IceTransport::~IceTransport() { stop(); } IceTransport::~IceTransport() { stop(); }
@ -196,8 +439,8 @@ Description::Role IceTransport::role() const { return mRole; }
IceTransport::State IceTransport::state() const { return mState; } IceTransport::State IceTransport::state() const { return mState; }
Description IceTransport::getLocalDescription(Description::Type type) const { Description IceTransport::getLocalDescription(Description::Type type) const {
// RFC 5245: The agent that generated the offer which started the ICE processing MUST take the // RFC 8445: The initiating agent that started the ICE processing MUST take the controlling
// controlling role, and the other MUST take the controlled role. // role, and the other MUST take the controlled role.
g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode", g_object_set(G_OBJECT(mNiceAgent.get()), "controlling-mode",
type == Description::Type::Offer ? TRUE : FALSE, nullptr); type == Description::Type::Offer ? TRUE : FALSE, nullptr);
@ -212,7 +455,8 @@ void IceTransport::setRemoteDescription(const Description &description) {
mMid = description.mid(); mMid = description.mid();
mTrickleTimeout = description.trickleEnabled() ? 30s : 0s; mTrickleTimeout = description.trickleEnabled() ? 30s : 0s;
if (nice_agent_parse_remote_sdp(mNiceAgent.get(), string(description).c_str()) < 0) // Warning: libnice expects "\n" as end of line
if (nice_agent_parse_remote_sdp(mNiceAgent.get(), description.generateSdp("\n").c_str()) < 0)
throw std::runtime_error("Failed to parse remote SDP"); throw std::runtime_error("Failed to parse remote SDP");
} }
@ -253,6 +497,7 @@ std::optional<string> IceTransport::getLocalAddress() const {
} }
return nullopt; return nullopt;
} }
std::optional<string> IceTransport::getRemoteAddress() const { std::optional<string> IceTransport::getRemoteAddress() const {
NiceCandidate *local = nullptr; NiceCandidate *local = nullptr;
NiceCandidate *remote = nullptr; NiceCandidate *remote = nullptr;
@ -286,24 +531,24 @@ void IceTransport::changeState(State state) {
mStateChangeCallback(mState); mStateChangeCallback(mState);
} }
void IceTransport::changeGatheringState(GatheringState state) {
if (mGatheringState.exchange(state) != state)
mGatheringStateChangeCallback(mGatheringState);
}
void IceTransport::processTimeout() { void IceTransport::processTimeout() {
PLOG_WARNING << "ICE timeout"; PLOG_WARNING << "ICE timeout";
mTimeoutId = 0; mTimeoutId = 0;
changeState(State::Failed); changeState(State::Failed);
} }
void IceTransport::changeGatheringState(GatheringState state) {
mGatheringState = state;
mGatheringStateChangeCallback(mGatheringState);
}
void IceTransport::processCandidate(const string &candidate) { void IceTransport::processCandidate(const string &candidate) {
mCandidateCallback(Candidate(candidate, mMid)); mCandidateCallback(Candidate(candidate, mMid));
} }
void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); } void IceTransport::processGatheringDone() { changeGatheringState(GatheringState::Complete); }
void IceTransport::processStateChange(uint32_t state) { void IceTransport::processStateChange(unsigned int state) {
if (state == NICE_COMPONENT_STATE_FAILED && mTrickleTimeout.count() > 0) { if (state == NICE_COMPONENT_STATE_FAILED && mTrickleTimeout.count() > 0) {
if (mTimeoutId) if (mTimeoutId)
g_source_remove(mTimeoutId); g_source_remove(mTimeoutId);
@ -396,7 +641,9 @@ void IceTransport::LogCallback(const gchar *logDomain, GLogLevelFlags logLevel,
else else
severity = plog::verbose; // libnice debug as verbose severity = plog::verbose; // libnice debug as verbose
PLOG(severity) << message; PLOG(severity) << "nice: " << message;
} }
} // namespace rtc } // namespace rtc
#endif

View File

@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2019 Paul-Louis Ageneau * Copyright (c) 2019-2020 Paul-Louis Ageneau
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public * modify it under the terms of the GNU Lesser General Public
@ -26,9 +26,11 @@
#include "peerconnection.hpp" #include "peerconnection.hpp"
#include "transport.hpp" #include "transport.hpp"
extern "C" { #if USE_JUICE
#include <juice/juice.h>
#else
#include <nice/agent.h> #include <nice/agent.h>
} #endif
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
@ -38,14 +40,23 @@ namespace rtc {
class IceTransport : public Transport { class IceTransport : public Transport {
public: public:
enum class State : uint32_t { #if USE_JUICE
enum class State : unsigned int{
Disconnected = JUICE_STATE_DISCONNECTED,
Connecting = JUICE_STATE_CONNECTING,
Connected = JUICE_STATE_CONNECTED,
Completed = JUICE_STATE_COMPLETED,
Failed = JUICE_STATE_FAILED,
};
#else
enum class State : unsigned int {
Disconnected = NICE_COMPONENT_STATE_DISCONNECTED, Disconnected = NICE_COMPONENT_STATE_DISCONNECTED,
Connecting = NICE_COMPONENT_STATE_CONNECTING, Connecting = NICE_COMPONENT_STATE_CONNECTING,
Connected = NICE_COMPONENT_STATE_CONNECTED, Connected = NICE_COMPONENT_STATE_CONNECTED,
Completed = NICE_COMPONENT_STATE_READY, Completed = NICE_COMPONENT_STATE_READY,
Failed = NICE_COMPONENT_STATE_FAILED Failed = NICE_COMPONENT_STATE_FAILED,
}; };
#endif
enum class GatheringState { New = 0, InProgress = 1, Complete = 2 }; enum class GatheringState { New = 0, InProgress = 1, Complete = 2 };
using candidate_callback = std::function<void(const Candidate &candidate)>; using candidate_callback = std::function<void(const Candidate &candidate)>;
@ -79,9 +90,9 @@ private:
void changeState(State state); void changeState(State state);
void changeGatheringState(GatheringState state); void changeGatheringState(GatheringState state);
void processStateChange(unsigned int state);
void processCandidate(const string &candidate); void processCandidate(const string &candidate);
void processGatheringDone(); void processGatheringDone();
void processStateChange(uint32_t state);
void processTimeout(); void processTimeout();
Description::Role mRole; Description::Role mRole;
@ -90,27 +101,39 @@ private:
std::atomic<State> mState; std::atomic<State> mState;
std::atomic<GatheringState> mGatheringState; std::atomic<GatheringState> mGatheringState;
candidate_callback mCandidateCallback;
state_callback mStateChangeCallback;
gathering_state_callback mGatheringStateChangeCallback;
#if USE_JUICE
std::unique_ptr<juice_agent_t, void (*)(juice_agent_t *)> mAgent;
string mStunHostname;
string mStunService;
static void StateChangeCallback(juice_agent_t *agent, juice_state_t state, void *user_ptr);
static void CandidateCallback(juice_agent_t *agent, const char *sdp, void *user_ptr);
static void GatheringDoneCallback(juice_agent_t *agent, void *user_ptr);
static void RecvCallback(juice_agent_t *agent, const char *data, size_t size, void *user_ptr);
static void LogCallback(juice_log_level_t level, const char *message);
#else
uint32_t mStreamId = 0; uint32_t mStreamId = 0;
std::unique_ptr<NiceAgent, void (*)(gpointer)> mNiceAgent; std::unique_ptr<NiceAgent, void (*)(gpointer)> mNiceAgent;
std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop; std::unique_ptr<GMainLoop, void (*)(GMainLoop *)> mMainLoop;
std::thread mMainLoopThread; std::thread mMainLoopThread;
guint mTimeoutId = 0; guint mTimeoutId = 0;
candidate_callback mCandidateCallback;
state_callback mStateChangeCallback;
gathering_state_callback mGatheringStateChangeCallback;
static string AddressToString(const NiceAddress &addr); static string AddressToString(const NiceAddress &addr);
static void CandidateCallback(NiceAgent *agent, NiceCandidate *candidate, gpointer userData); static void CandidateCallback(NiceAgent *agent, NiceCandidate *candidate, gpointer userData);
static void GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData); static void GatheringDoneCallback(NiceAgent *agent, guint streamId, gpointer userData);
static void StateChangeCallback(NiceAgent *agent, guint streamId, guint componentId, static void StateChangeCallback(NiceAgent *agent, guint streamId, guint componentId,
guint state, gpointer userData); guint state, gpointer userData);
static void RecvCallback(NiceAgent *agent, guint stream_id, guint component_id, guint len, static void RecvCallback(NiceAgent *agent, guint stream_id, guint component_id, guint len,
gchar *buf, gpointer userData); gchar *buf, gpointer userData);
static gboolean TimeoutCallback(gpointer userData); static gboolean TimeoutCallback(gpointer userData);
static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, static void LogCallback(const gchar *log_domain, GLogLevelFlags log_level, const gchar *message,
gpointer user_data); gpointer user_data);
#endif
}; };
} // namespace rtc } // namespace rtc

View File

@ -29,15 +29,15 @@ using namespace std;
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; } template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
int main(int argc, char **argv) { int main(int argc, char **argv) {
// InitLogger(LogLevel::Debug); InitLogger(LogLevel::Warning);
Configuration config;
// config.iceServers.emplace_back("stun.l.google.com:19302"); Configuration config;
// config.iceServers.emplace_back("stun:stun.l.google.com:19302");
// config.enableIceTcp = true; // config.enableIceTcp = true;
// Add TURN Server Example // TURN server example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD", // IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnTls); // IceServer::RelayType::TurnUdp);
// config.iceServers.push_back(turnServer); // config.iceServers.push_back(turnServer);
auto pc1 = std::make_shared<PeerConnection>(config); auto pc1 = std::make_shared<PeerConnection>(config);
@ -113,6 +113,15 @@ int main(int argc, char **argv) {
this_thread::sleep_for(3s); this_thread::sleep_for(3s);
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;
if (dc1->isOpen() && dc2->isOpen()) { if (dc1->isOpen() && dc2->isOpen()) {
pc1->close(); pc1->close();
pc2->close(); pc2->close();

View File

@ -28,14 +28,15 @@ using namespace std;
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; } template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
int main(int argc, char **argv) { int main(int argc, char **argv) {
// InitLogger(LogLevel::Debug); InitLogger(LogLevel::Warning);
Configuration config; Configuration config;
// config.iceServers.emplace_back("stun.l.google.com:19302"); // config.iceServers.emplace_back("stun.l.google.com:19302");
// config.enableIceTcp = true; // config.enableIceTcp = true;
// Add TURN Server Example // TURN Server example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD", // IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnTls); // IceServer::RelayType::TurnUdp);
// config.iceServers.push_back(turnServer); // config.iceServers.push_back(turnServer);
auto pc = std::make_shared<PeerConnection>(config); auto pc = std::make_shared<PeerConnection>(config);

View File

@ -28,14 +28,15 @@ using namespace std;
template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; } template <class T> weak_ptr<T> make_weak_ptr(shared_ptr<T> ptr) { return ptr; }
int main(int argc, char **argv) { int main(int argc, char **argv) {
// InitLogger(LogLevel::Debug); InitLogger(LogLevel::Warning);
Configuration config; Configuration config;
// config.iceServers.emplace_back("stun.l.google.com:19302"); // config.iceServers.emplace_back("stun.l.google.com:19302");
// config.enableIceTcp = true; // config.enableIceTcp = true;
// Add TURN Server Example // TURN server example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD", // IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnTls); // IceServer::RelayType::TurnUdp);
// config.iceServers.push_back(turnServer); // config.iceServers.push_back(turnServer);
auto pc = std::make_shared<PeerConnection>(config); auto pc = std::make_shared<PeerConnection>(config);