Compare commits

...

56 Commits

Author SHA1 Message Date
dc065add0b Bumped version to 0.4.5 2020-02-27 14:06:02 +01:00
e64d4049a6 Updated libjuice to v0.2.5 2020-02-27 14:05:33 +01:00
cb3bc85474 Fixed && instead of || when EWOULDBLOCK != EAGAIN #38 2020-02-26 14:45:39 +01:00
7af3da7872 Revised handling of path MTU discovery to exclude Mac OS 2020-02-26 09:15:42 +01:00
3c77d717d2 Explicitely added COMP-NULL to GnuTLS priorities 2020-02-25 00:17:06 +01:00
6f399945fe Updated libjuice 2020-02-25 00:02:05 +01:00
c8b14b1262 Change state to failed if a transport initialization fails 2020-02-24 23:53:10 +01:00
35d4455c4f Cleaned up tests and fixed SDP reading from console 2020-02-24 11:45:36 +01:00
7d21b4b42b Guess the description type from the context (useful for tests) 2020-02-24 11:39:11 +01:00
24e9e06c5a Bumped version to 0.4.4 2020-02-23 17:30:27 +01:00
443a19d8e7 Updated libjuice to v0.2.4 with better host candidates gathering 2020-02-23 17:24:47 +01:00
83de743924 Bumped version to 0.4.3 2020-02-21 12:39:27 +01:00
1dc1de4b86 Added platforms to Readme 2020-02-21 12:39:27 +01:00
8ca7722d48 Updated libjuice to v0.2.3 with Windows compilation support 2020-02-21 12:39:25 +01:00
3079072e63 For Win32, define _WIN32_WINNT to 0x0601 (Windows 7) if undefined 2020-02-21 00:04:57 +01:00
982d1c10e1 Merge pull request #35 from murat-dogan/master
Compile support on Windows with mingw-w64
2020-02-20 22:53:15 +00:00
50b22bbf3c delete win32 directive 2020-02-20 21:06:54 +03:00
93e153398f Compile support on Windows with mingw-w64 2020-02-20 14:55:21 +03:00
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
23 changed files with 987 additions and 438 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,12 +1,27 @@
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.5
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)
if(WIN32)
if (MSYS OR MINGW)
add_definitions(-DSCTP_STDINT_INCLUDE=<stdint.h>)
endif()
endif()
set(LIBDATACHANNEL_SOURCES set(LIBDATACHANNEL_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/candidate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/certificate.cpp
@ -33,38 +48,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,11 +64,16 @@ 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 )
if(WIN32)
target_link_libraries(datachannel
"wsock32" #winsock2
"ws2_32" #winsock2
) )
endif()
add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES}) add_library(datachannel-static STATIC EXCLUDE_FROM_ALL ${LIBDATACHANNEL_SOURCES})
set_target_properties(datachannel-static PROPERTIES set_target_properties(datachannel-static PROPERTIES
@ -91,9 +86,14 @@ 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(WIN32)
target_link_libraries(datachannel-static
"wsock32" #winsock2
"ws2_32" #winsock2
)
endif()
if (USE_GNUTLS) if (USE_GNUTLS)
find_package(GnuTLS REQUIRED) find_package(GnuTLS REQUIRED)
@ -117,29 +117,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 answerer)
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

@ -1,33 +1,46 @@
# libdatachannel - C/C++ WebRTC DataChannels # libdatachannel - C/C++ WebRTC DataChannels
libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC API, in order to ease the design of cross-environment applications. libdatachannel is a standalone implementation of WebRTC DataChannels in C++17 with C bindings for POSIX platforms and Microsoft Windows. It enables direct connectivity between native applications and web browsers without the pain of importing the entire WebRTC stack. Its API is modelled as a simplified version of the JavaScript WebRTC API, in order to ease the design of cross-environment applications.
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 856475ebd1

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

@ -35,6 +35,7 @@ public:
enum class Role { ActPass = 0, Passive = 1, Active = 2 }; enum class Role { ActPass = 0, Passive = 1, Active = 2 };
Description(const string &sdp, const string &typeString = ""); Description(const string &sdp, const string &typeString = "");
Description(const string &sdp, Type type);
Description(const string &sdp, Type type, Role role); Description(const string &sdp, Type type, Role role);
Type type() const; Type type() const;
@ -47,6 +48,7 @@ public:
std::optional<size_t> maxMessageSize() const; std::optional<size_t> maxMessageSize() const;
bool trickleEnabled() const; bool trickleEnabled() const;
void hintType(Type type);
void setFingerprint(string fingerprint); void setFingerprint(string fingerprint);
void setSctpPort(uint16_t port); void setSctpPort(uint16_t port);
void setMaxMessageSize(size_t size); void setMaxMessageSize(size_t size);
@ -57,6 +59,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

@ -19,6 +19,12 @@
#ifndef RTC_INCLUDE_H #ifndef RTC_INCLUDE_H
#define RTC_INCLUDE_H #define RTC_INCLUDE_H
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0602
#endif
#endif
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <memory> #include <memory>
@ -44,6 +50,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 +59,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 +71,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

@ -22,8 +22,14 @@
#include <array> #include <array>
#include <sstream> #include <sstream>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#elif __linux__
#include <netdb.h> #include <netdb.h>
#include <sys/socket.h> #include <sys/socket.h>
#endif
#include <sys/types.h> #include <sys/types.h>
using std::array; using std::array;
@ -54,7 +60,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

@ -45,12 +45,13 @@ inline void trim_end(string &str) {
namespace rtc { namespace rtc {
Description::Description(const string &sdp, const string &typeString) Description::Description(const string &sdp, const string &typeString)
: Description(sdp, stringToType(typeString), Description::Role::ActPass) {} : Description(sdp, stringToType(typeString)) {}
Description::Description(const string &sdp, Type type) : Description(sdp, type, Role::ActPass) {}
Description::Description(const string &sdp, Type type, Role role) Description::Description(const string &sdp, Type type, Role role)
: mType(type), mRole(role), mMid("0"), mIceUfrag("0"), mIcePwd("0"), mTrickle(true) { : mType(Type::Unspec), mRole(role), mMid("0"), mIceUfrag(""), mIcePwd(""), mTrickle(true) {
if (mType == Type::Answer && mRole == Role::ActPass) hintType(type);
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
auto seed = std::chrono::system_clock::now().time_since_epoch().count(); auto seed = std::chrono::system_clock::now().time_since_epoch().count();
std::default_random_engine generator(seed); std::default_random_engine generator(seed);
@ -109,6 +110,14 @@ std::optional<size_t> Description::maxMessageSize() const { return mMaxMessageSi
bool Description::trickleEnabled() const { return mTrickle; } bool Description::trickleEnabled() const { return mTrickle; }
void Description::hintType(Type type) {
if (mType == Type::Unspec) {
mType = type;
if (mType == Type::Answer && mRole == Role::ActPass)
mRole = Role::Passive; // ActPass is illegal for an answer, so default to passive
}
}
void Description::setFingerprint(string fingerprint) { void Description::setFingerprint(string fingerprint) {
mFingerprint.emplace(std::move(fingerprint)); mFingerprint.emplace(std::move(fingerprint));
} }
@ -130,37 +139,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>
@ -72,7 +73,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
const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL"; const char *priorities = "SECURE128:-VERS-SSL3.0:-ARCFOUR-128:-COMP-ALL:+COMP-NULL";
const char *err_pos = NULL; const char *err_pos = NULL;
check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos), check_gnutls(gnutls_priority_set_direct(mSession, priorities, &err_pos),
"Unable to set TLS priorities"); "Unable to set TLS priorities");
@ -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,33 +17,258 @@
*/ */
#include "icetransport.hpp" #include "icetransport.hpp"
#include "configuration.hpp"
#ifdef _WIN32
#include <winsock2.h>
#elif __linux__
#include <netdb.h> #include <netdb.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/socket.h> #include <sys/socket.h>
#endif
#include <sys/types.h> #include <sys/types.h>
#include <iostream> #include <iostream>
#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();
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 +280,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 +290,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 +339,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 +357,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 +378,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 +441,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 +457,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 +499,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 +533,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 +643,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

@ -79,9 +79,10 @@ std::optional<Description> PeerConnection::remoteDescription() const {
} }
void PeerConnection::setRemoteDescription(Description description) { void PeerConnection::setRemoteDescription(Description description) {
std::lock_guard lock(mRemoteDescriptionMutex); description.hintType(localDescription() ? Description::Type::Answer : Description::Type::Offer);
auto remoteCandidates = description.extractCandidates(); auto remoteCandidates = description.extractCandidates();
std::lock_guard lock(mRemoteDescriptionMutex);
mRemoteDescription.emplace(std::move(description)); mRemoteDescription.emplace(std::move(description));
auto iceTransport = std::atomic_load(&mIceTransport); auto iceTransport = std::atomic_load(&mIceTransport);
@ -209,108 +210,126 @@ void PeerConnection::onGatheringStateChange(std::function<void(GatheringState st
} }
shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) { shared_ptr<IceTransport> PeerConnection::initIceTransport(Description::Role role) {
std::lock_guard lock(mInitMutex); try {
if (auto transport = std::atomic_load(&mIceTransport)) std::lock_guard lock(mInitMutex);
return transport; if (auto transport = std::atomic_load(&mIceTransport))
return transport;
auto transport = std::make_shared<IceTransport>( auto transport = std::make_shared<IceTransport>(
mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1), mConfig, role, std::bind(&PeerConnection::processLocalCandidate, this, _1),
[this](IceTransport::State state) { [this](IceTransport::State state) {
switch (state) { switch (state) {
case IceTransport::State::Connecting: case IceTransport::State::Connecting:
changeState(State::Connecting); changeState(State::Connecting);
break; break;
case IceTransport::State::Failed: case IceTransport::State::Failed:
changeState(State::Failed); changeState(State::Failed);
break; break;
case IceTransport::State::Connected: case IceTransport::State::Connected:
initDtlsTransport(); initDtlsTransport();
break; break;
case IceTransport::State::Disconnected: case IceTransport::State::Disconnected:
changeState(State::Disconnected); changeState(State::Disconnected);
break; break;
default: default:
// Ignore // Ignore
break; break;
} }
}, },
[this](IceTransport::GatheringState state) { [this](IceTransport::GatheringState state) {
switch (state) { switch (state) {
case IceTransport::GatheringState::InProgress: case IceTransport::GatheringState::InProgress:
changeGatheringState(GatheringState::InProgress); changeGatheringState(GatheringState::InProgress);
break; break;
case IceTransport::GatheringState::Complete: case IceTransport::GatheringState::Complete:
endLocalCandidates(); endLocalCandidates();
changeGatheringState(GatheringState::Complete); changeGatheringState(GatheringState::Complete);
break; break;
default: default:
// Ignore // Ignore
break; break;
} }
}); });
std::atomic_store(&mIceTransport, transport); std::atomic_store(&mIceTransport, transport);
return transport; return transport;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
changeState(State::Failed);
throw std::runtime_error("ICE transport initialization failed");
}
} }
shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() { shared_ptr<DtlsTransport> PeerConnection::initDtlsTransport() {
std::lock_guard lock(mInitMutex); try {
if (auto transport = std::atomic_load(&mDtlsTransport)) std::lock_guard lock(mInitMutex);
return transport; if (auto transport = std::atomic_load(&mDtlsTransport))
return transport;
auto lower = std::atomic_load(&mIceTransport); auto lower = std::atomic_load(&mIceTransport);
auto transport = std::make_shared<DtlsTransport>( auto transport = std::make_shared<DtlsTransport>(
lower, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1), lower, mCertificate, std::bind(&PeerConnection::checkFingerprint, this, _1),
[this](DtlsTransport::State state) { [this](DtlsTransport::State state) {
switch (state) { switch (state) {
case DtlsTransport::State::Connected: case DtlsTransport::State::Connected:
initSctpTransport(); initSctpTransport();
break; break;
case DtlsTransport::State::Failed: case DtlsTransport::State::Failed:
changeState(State::Failed); changeState(State::Failed);
break; break;
case DtlsTransport::State::Disconnected: case DtlsTransport::State::Disconnected:
changeState(State::Disconnected); changeState(State::Disconnected);
break; break;
default: default:
// Ignore // Ignore
break; break;
} }
}); });
std::atomic_store(&mDtlsTransport, transport); std::atomic_store(&mDtlsTransport, transport);
return transport; return transport;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
changeState(State::Failed);
throw std::runtime_error("DTLS transport initialization failed");
}
} }
shared_ptr<SctpTransport> PeerConnection::initSctpTransport() { shared_ptr<SctpTransport> PeerConnection::initSctpTransport() {
std::lock_guard lock(mInitMutex); try {
if (auto transport = std::atomic_load(&mSctpTransport)) std::lock_guard lock(mInitMutex);
return transport; if (auto transport = std::atomic_load(&mSctpTransport))
return transport;
uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT); uint16_t sctpPort = remoteDescription()->sctpPort().value_or(DEFAULT_SCTP_PORT);
auto lower = std::atomic_load(&mDtlsTransport); auto lower = std::atomic_load(&mDtlsTransport);
auto transport = std::make_shared<SctpTransport>( auto transport = std::make_shared<SctpTransport>(
lower, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1), lower, sctpPort, std::bind(&PeerConnection::forwardMessage, this, _1),
std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2), std::bind(&PeerConnection::forwardBufferedAmount, this, _1, _2),
[this](SctpTransport::State state) { [this](SctpTransport::State state) {
switch (state) { switch (state) {
case SctpTransport::State::Connected: case SctpTransport::State::Connected:
changeState(State::Connected); changeState(State::Connected);
openDataChannels(); openDataChannels();
break; break;
case SctpTransport::State::Failed: case SctpTransport::State::Failed:
remoteCloseDataChannels(); remoteCloseDataChannels();
changeState(State::Failed); changeState(State::Failed);
break; break;
case SctpTransport::State::Disconnected: case SctpTransport::State::Disconnected:
remoteCloseDataChannels(); remoteCloseDataChannels();
changeState(State::Disconnected); changeState(State::Disconnected);
break; break;
default: default:
// Ignore // Ignore
break; break;
} }
}); });
std::atomic_store(&mSctpTransport, transport); std::atomic_store(&mSctpTransport, transport);
return transport; return transport;
} catch (const std::exception &e) {
PLOG_ERROR << e.what();
changeState(State::Failed);
throw std::runtime_error("SCTP transport initialization failed");
}
} }
void PeerConnection::endLocalCandidates() { void PeerConnection::endLocalCandidates() {

View File

@ -23,7 +23,28 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#ifdef __linux__
#include <arpa/inet.h> #include <arpa/inet.h>
#endif
#ifdef USE_JUICE
#ifndef __APPLE__
// libjuice enables Linux path MTU discovery or sets the DF flag
#define USE_PMTUD 1
#else
// Setting the DF flag is not available on Mac OS
#define USE_PMTUD 0
#endif
#else
#ifdef __linux__
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE)
// It should be safe to enable discovery for SCTP.
#define USE_PMTUD 1
#else
// Otherwise assume fragmentation
#define USE_PMTUD 0
#endif
#endif
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace std::chrono; using namespace std::chrono;
@ -117,12 +138,11 @@ SctpTransport::SctpTransport(std::shared_ptr<Transport> lower, uint16_t port,
std::to_string(errno)); std::to_string(errno));
struct sctp_paddrparams spp = {}; struct sctp_paddrparams spp = {};
#ifdef __linux__ #if USE_PMTUD
// Linux UDP does path MTU discovery by default (setting DF and returning EMSGSIZE). // Enabled SCTP path MTU discovery
// It should be safe to enable discovery for SCTP.
spp.spp_flags = SPP_PMTUD_ENABLE; spp.spp_flags = SPP_PMTUD_ENABLE;
#else #else
// Otherwise, fall back to a safe MTU value. // Fall back to a safe MTU value.
spp.spp_flags = SPP_PMTUD_DISABLE; spp.spp_flags = SPP_PMTUD_DISABLE;
spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261 spp.spp_pathmtu = 1200; // Max safe value recommended by RFC 8261
// See https://tools.ietf.org/html/rfc8261#section-5 // See https://tools.ietf.org/html/rfc8261#section-5
@ -353,7 +373,7 @@ bool SctpTransport::trySendMessage(message_ptr message) {
if (ret >= 0) { if (ret >= 0) {
PLOG_VERBOSE << "SCTP sent size=" << message->size(); PLOG_VERBOSE << "SCTP sent size=" << message->size();
return true; return true;
} else if (errno == EWOULDBLOCK && errno == EAGAIN) { } else if (errno == EWOULDBLOCK || errno == EAGAIN) {
PLOG_VERBOSE << "SCTP sending not possible"; PLOG_VERBOSE << "SCTP sending not possible";
return false; return false;
} else { } else {

View File

@ -29,7 +29,12 @@
#include <map> #include <map>
#include <mutex> #include <mutex>
#ifdef _WIN32
#include <winsock2.h>
#elif __linux__
#include <sys/socket.h> #include <sys/socket.h>
#endif
#include <sys/types.h> #include <sys/types.h>
#include "usrsctp.h" #include "usrsctp.h"

View File

@ -23,22 +23,29 @@
#include <memory> #include <memory>
#include <thread> #include <thread>
#ifdef _WIN32
#include <winsock2.h>
#endif
using namespace rtc; using namespace rtc;
using namespace std; 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);
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
#endif
Configuration config; Configuration config;
// config.iceServers.emplace_back("stun:stun.l.google.com:19302");
// config.iceServers.emplace_back("stun.l.google.com:19302"); // config.iceServers.emplace_back(IceServer("TURN_SERVER_URL", "PORT", "USERNAME", "PASSWORD",
// config.enableIceTcp = true; // IceServer::RelayType::TurnUdp)); // libnice only
// config.enableIceTcp = true; // libnice only
// Add TURN Server Example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnTls);
// config.iceServers.push_back(turnServer);
auto pc1 = std::make_shared<PeerConnection>(config); auto pc1 = std::make_shared<PeerConnection>(config);
auto pc2 = std::make_shared<PeerConnection>(config); auto pc2 = std::make_shared<PeerConnection>(config);
@ -113,14 +120,27 @@ int main(int argc, char **argv) {
this_thread::sleep_for(3s); this_thread::sleep_for(3s);
if (dc1->isOpen() && dc2->isOpen()) { 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;
bool success;
if ((success = dc1->isOpen() && dc2->isOpen())) {
pc1->close(); pc1->close();
pc2->close(); pc2->close();
cout << "Success" << endl; cout << "Success" << endl;
return 0;
} else { } else {
cout << "Failure" << endl; cout << "Failure" << endl;
return 1;
} }
#ifdef _WIN32
WSACleanup();
#endif
return success ? 0 : 1;
} }

View File

@ -21,6 +21,11 @@
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <thread>
#ifdef _WIN32
#include <winsock2.h>
#endif
using namespace rtc; using namespace rtc;
using namespace std; using namespace std;
@ -28,44 +33,46 @@ 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::Debug);
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
#endif
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;
// Add TURN Server Example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnTls);
// config.iceServers.push_back(turnServer);
auto pc = std::make_shared<PeerConnection>(config); auto pc = std::make_shared<PeerConnection>(config);
pc->onLocalDescription([](const Description &sdp) { pc->onLocalDescription([](const Description &description) {
std::string s(sdp); cout << "Local Description (Paste this to the other peer):" << endl;
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94)); cout << string(description) << endl;
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
}); });
pc->onLocalCandidate([](const Candidate &candidate) { pc->onLocalCandidate([](const Candidate &candidate) {
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl; cout << "Local Candidate (Paste this to the other peer after the local description):"
<< endl;
cout << string(candidate) << endl << endl;
}); });
pc->onStateChange( pc->onStateChange(
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; }); [](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
pc->onGatheringStateChange([](PeerConnection::GatheringState state) { pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "[ Gathering State: " << state << " ]" << endl; cout << "[Gathering State: " << state << "]" << endl;
}); });
shared_ptr<DataChannel> dc = nullptr; shared_ptr<DataChannel> dc = nullptr;
pc->onDataChannel([&](shared_ptr<DataChannel> _dc) { pc->onDataChannel([&](shared_ptr<DataChannel> _dc) {
cout << "[ Got a DataChannel with label: " << _dc->label() << " ]" << endl; cout << "[Got a DataChannel with label: " << _dc->label() << "]" << endl;
dc = _dc; dc = _dc;
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; }); dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
dc->onMessage([](const variant<binary, string> &message) { dc->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) { if (holds_alternative<string>(message)) {
cout << "[ Received: " << get<string>(message) << " ]" << endl; cout << "[Received message: " << get<string>(message) << "]" << endl;
} }
}); });
}); });
@ -73,71 +80,69 @@ int main(int argc, char **argv) {
bool exit = false; bool exit = false;
while (!exit) { while (!exit) {
cout << endl cout << endl
<< "**********************************************************************************"
"*****"
<< endl << endl
<< "*************************************************************************" << endl
<< "* 0: Exit /" << "* 0: Exit /"
<< " 1: Enter Description /" << " 1: Enter remote description /"
<< " 2: Enter Candidate /" << " 2: Enter remote candidate /"
<< " 3: Send Message *" << endl << " 3: Send message *" << endl
<< " [Command]: "; << "[Command]: ";
int command; int command = -1;
std::string sdp, candidate, message;
const char *a;
std::unique_ptr<Candidate> candidatePtr;
std::unique_ptr<Description> descPtr;
cin >> command; cin >> command;
cin.ignore();
switch (command) { switch (command) {
case 0: case 0: {
exit = true; exit = true;
break; break;
}
case 1: case 1: {
// Parse Description // Parse Description
cout << "[SDP]: "; cout << "[Description]: ";
sdp = ""; string sdp, line;
while (sdp.length() == 0) while (getline(cin, line) && !line.empty()) {
getline(cin, sdp); sdp += line;
sdp += "\r\n";
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n'); }
descPtr = std::make_unique<Description>(sdp, Description::Type::Offer, std::cout << sdp;
Description::Role::Passive); pc->setRemoteDescription(sdp);
pc->setRemoteDescription(*descPtr);
break; break;
}
case 2: case 2: {
// Parse Candidate // Parse Candidate
cout << "[Candidate]: "; cout << "[Candidate]: ";
candidate = ""; string candidate;
while (candidate.length() == 0) getline(cin, candidate);
getline(cin, candidate); pc->addRemoteCandidate(candidate);
candidatePtr = std::make_unique<Candidate>(candidate);
pc->addRemoteCandidate(*candidatePtr);
break; break;
}
case 3: case 3: {
// Send Message // Send Message
if (!dc || !dc->isOpen()) { if (!dc || !dc->isOpen()) {
cout << "** Channel is not Open ** "; cout << "** Channel is not Open ** ";
break; break;
} }
cout << "[Message]: "; cout << "[Message]: ";
message = ""; string message;
while (message.length() == 0) getline(cin, message);
getline(cin, message);
dc->send(message); dc->send(message);
break; break;
}
default: default: {
cout << "** Invalid Command ** "; cout << "** Invalid Command ** ";
break; break;
} }
}
} }
if (dc) if (dc)
dc->close(); dc->close();
if (pc) if (pc)
pc->close(); pc->close();
#ifdef _WIN32
WSACleanup();
#endif
} }

View File

@ -21,6 +21,11 @@
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <thread>
#ifdef _WIN32
#include <winsock2.h>
#endif
using namespace rtc; using namespace rtc;
using namespace std; using namespace std;
@ -28,113 +33,116 @@ 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);
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData))
throw std::runtime_error("WSAStartup failed, error=" + std::to_string(WSAGetLastError()));
#endif
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;
// Add TURN Server Example
// IceServer turnServer("TURN_SERVER_URL", "PORT_NO", "USERNAME", "PASSWORD",
// IceServer::RelayType::TurnTls);
// config.iceServers.push_back(turnServer);
auto pc = std::make_shared<PeerConnection>(config); auto pc = std::make_shared<PeerConnection>(config);
pc->onLocalDescription([](const Description &sdp) { pc->onLocalDescription([](const Description &description) {
std::string s(sdp); cout << "Local Description (Paste this to the other peer):" << endl;
std::replace(s.begin(), s.end(), '\n', static_cast<char>(94)); cout << string(description) << endl;
cout << "Local Description (Paste this to other peer):" << endl << s << endl << endl;
}); });
pc->onLocalCandidate([](const Candidate &candidate) { pc->onLocalCandidate([](const Candidate &candidate) {
cout << "Local Candidate (Paste this to other peer):" << endl << candidate << endl << endl; cout << "Local Candidate (Paste this to the other peer after the local description):"
<< endl;
cout << string(candidate) << endl << endl;
}); });
pc->onStateChange( pc->onStateChange(
[](PeerConnection::State state) { cout << "[ State: " << state << " ]" << endl; }); [](PeerConnection::State state) { cout << "[State: " << state << "]" << endl; });
pc->onGatheringStateChange([](PeerConnection::GatheringState state) { pc->onGatheringStateChange([](PeerConnection::GatheringState state) {
cout << "[ Gathering State: " << state << " ]" << endl; cout << "[Gathering State: " << state << "]" << endl;
}); });
auto dc = pc->createDataChannel("test"); auto dc = pc->createDataChannel("test"); // this is the offerer, so create a data channel
dc->onOpen([&]() { cout << "[ DataChannel open: " << dc->label() << " ]" << endl; });
dc->onClosed([&]() { cout << "[ DataChannel closed: " << dc->label() << " ]" << endl; }); dc->onOpen([&]() { cout << "[DataChannel open: " << dc->label() << "]" << endl; });
dc->onClosed([&]() { cout << "[DataChannel closed: " << dc->label() << "]" << endl; });
dc->onMessage([](const variant<binary, string> &message) { dc->onMessage([](const variant<binary, string> &message) {
if (holds_alternative<string>(message)) { if (holds_alternative<string>(message)) {
cout << "[ Received: " << get<string>(message) << " ]" << endl; cout << "[Received: " << get<string>(message) << "]" << endl;
} }
}); });
this_thread::sleep_for(1s);
bool exit = false; bool exit = false;
while (!exit) { while (!exit) {
cout << endl cout << endl
<< "**********************************************************************************"
"*****"
<< endl << endl
<< "*************************************************************************" << endl
<< "* 0: Exit /" << "* 0: Exit /"
<< " 1: Enter Description /" << " 1: Enter remote description /"
<< " 2: Enter Candidate /" << " 2: Enter remote candidate /"
<< " 3: Send Message *" << endl << " 3: Send message *" << endl
<< " [Command]: "; << "[Command]: ";
int command; int command = -1;
std::string sdp, candidate, message;
const char *a;
std::unique_ptr<Candidate> candidatePtr;
std::unique_ptr<Description> descPtr;
cin >> command; cin >> command;
cin.ignore();
switch (command) { switch (command) {
case 0: case 0: {
exit = true; exit = true;
break; break;
}
case 1: case 1: {
// Parse Description // Parse Description
cout << "[SDP]: "; cout << "[Description]: ";
sdp = ""; string sdp, line;
while (sdp.length() == 0) while (getline(cin, line) && !line.empty()) {
getline(cin, sdp); sdp += line;
sdp += "\r\n";
std::replace(sdp.begin(), sdp.end(), static_cast<char>(94), '\n'); }
descPtr = std::make_unique<Description>(sdp); pc->setRemoteDescription(sdp);
pc->setRemoteDescription(*descPtr);
break; break;
}
case 2: case 2: {
// Parse Candidate // Parse Candidate
cout << "[Candidate]: "; cout << "[Candidate]: ";
candidate = ""; string candidate;
while (candidate.length() == 0) getline(cin, candidate);
getline(cin, candidate); pc->addRemoteCandidate(candidate);
candidatePtr = std::make_unique<Candidate>(candidate);
pc->addRemoteCandidate(*candidatePtr);
break; break;
}
case 3: case 3: {
// Send Message // Send Message
if (!dc->isOpen()) { if (!dc->isOpen()) {
cout << "** Channel is not Open ** "; cout << "** Channel is not Open ** ";
break; break;
} }
cout << "[Message]: "; cout << "[Message]: ";
message = ""; string message;
while (message.length() == 0) getline(cin, message);
getline(cin, message);
dc->send(message); dc->send(message);
break; break;
}
default: default: {
cout << "** Invalid Command ** "; cout << "** Invalid Command ** ";
break; break;
} }
}
} }
if (dc) if (dc)
dc->close(); dc->close();
if (pc) if (pc)
pc->close(); pc->close();
#ifdef _WIN32
WSACleanup();
#endif
} }