diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
index f12ac283..2cd38dd3 100644
--- a/.github/workflows/macos.yml
+++ b/.github/workflows/macos.yml
@@ -18,10 +18,15 @@ jobs:
with:
vcpkgDirectory: '${{github.workspace}}/vcpkg'
vcpkgTriplet: arm64-osx
- # 2024.06.01
- vcpkgGitCommitId: '47364fbc300756f64f7876b549d9422d5f3ec0d3'
+ # 2025.07.11
+ vcpkgGitCommitId: 'efcfaaf60d7ec57a159fc3110403d939bfb69729'
vcpkgArguments: 'openssl libsrtp[openssl] usrsctp'
+ - name: 安装指定 CMake
+ uses: jwlawson/actions-setup-cmake@v2
+ with:
+ cmake-version: '3.30.5'
+
- name: 编译
uses: lukka/run-cmake@v3
with:
diff --git a/3rdpart/jsoncpp b/3rdpart/jsoncpp
index 69098a18..ca98c984 160000
--- a/3rdpart/jsoncpp
+++ b/3rdpart/jsoncpp
@@ -1 +1 @@
-Subproject commit 69098a18b9af0c47549d9a271c054d13ca92b006
+Subproject commit ca98c98457b1163cca1f7d8db62827c115fec6d1
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8190bd83..1416357e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -472,6 +472,17 @@ if(ENABLE_SRT)
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_SRT)
endif()
+if(ENABLE_WEBRTC)
+ # 查找 srtp 是否安装
+ find_package(SRTP QUIET)
+ if(SRTP_FOUND AND ENABLE_OPENSSL)
+ message(STATUS "found library: ${SRTP_LIBRARIES}, ENABLE_WEBRTC defined")
+ update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_WEBRTC)
+ else()
+ set(ENABLE_WEBRTC OFF)
+ message(WARNING "srtp 未找到, WebRTC 相关功能打开失败")
+ endif()
+endif()
# ----------------------------------------------------------------------------
# Solution folders:
# ----------------------------------------------------------------------------
diff --git a/README.md b/README.md
index 96957c50..d11a54af 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@
## 功能清单
### 功能一览
-
+
- RTSP[S]
- RTSP[S] 服务器,支持RTMP/MP4/HLS转RTSP[S],支持亚马逊echo show这样的设备
@@ -131,6 +131,8 @@
- 支持webrtc over tcp模式
- 优秀的nack、jitter buffer算法, 抗丢包能力卓越
- 支持whip/whep协议
+ - [支持ice-full,支持作为webrtc客户端拉流、推流以及p2p模式](./webrtc/USAGE.md)
+
- [SRT支持](./srt/srt.md)
- 其他
- 支持丰富的restful api以及web hook事件
diff --git a/README_en.md b/README_en.md
index 15a2448d..d9b68db4 100644
--- a/README_en.md
+++ b/README_en.md
@@ -45,7 +45,7 @@
## Feature List
### Overview of Features
-
+
- RTSP[S]
- RTSP[S] server, supports RTMP/MP4/HLS to RTSP[S] conversion, supports devices such as Amazon Echo Show
@@ -124,6 +124,8 @@
- Supports WebRTC over TCP mode
- Excellent NACK and jitter buffer algorithms with outstanding packet loss resistance
- Supports WHIP/WHEP protocols
+ - [Supports ice-full, works as a WebRTC client for pulling streams, pushing streams, and P2P mode](./webrtc/USAGE.md)
+
- [SRT support](./srt/srt.md)
- Others
- Supports rich RESTful APIs and webhook events
diff --git a/conf/config.ini b/conf/config.ini
index 80455f4e..6823b14f 100644
--- a/conf/config.ini
+++ b/conf/config.ini
@@ -346,11 +346,30 @@ udp_recv_socket_buffer=4194304
merge_frame=1
[rtc]
+#webrtc 信令服务器端口
+signalingPort=3000
+signalingSslPort=3001
+#STUN/TURN服务器端口
+icePort=3478
+iceTcpPort=3478
+#STUN/TURN端口是否使能TURN服务
+enableTurn=1
+#ICE传输策略:0=不限制(默认),1=仅支持Relay转发,2=仅支持P2P直连
+iceTransportPolicy=0
+#STUN/TURN 服务Ice密码
+iceUfrag=ZLMediaKit
+icePwd=ZLMediaKit
+#TURN服务分配端口池
+portRange=50000-65000
#rtc播放推流、播放超时时间
timeoutSec=15
#本机对rtc客户端的可见ip,作为服务器时一般为公网ip,可有多个,用','分开,当置空时,会自动获取网卡ip
#同时支持环境变量,以$开头,如"$EXTERN_IP"; 请参考:https://github.com/ZLMediaKit/ZLMediaKit/pull/1786
externIP=
+#当指定了interfaces,ICE服务器会使用指定网卡bind socket
+#以解决公网IP使用弹性公网IP配置实现(部署机器无法bind该公网ip的问题)
+#支持环境变量,以$开头,如"$PRIVATE_IP"
+interfaces=
#rtc udp服务器监听端口号,所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据,
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
#需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致
@@ -358,7 +377,7 @@ port=8000
#rtc tcp服务器监听端口号,在udp 不通的情况下,会使用tcp传输数据
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
#需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致
-tcpPort = 8000
+tcpPort=8000
#设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质
#目前已经实现twcc自动调整码率,关闭remb根据真实网络状况调整码率
rembBitRate=0
diff --git a/postman/ZLMediaKit.postman_collection.json b/postman/ZLMediaKit.postman_collection.json
index 5a6a61ae..953c81b4 100644
--- a/postman/ZLMediaKit.postman_collection.json
+++ b/postman/ZLMediaKit.postman_collection.json
@@ -2732,6 +2732,154 @@
}
},
"response": []
+ },
+ {
+ "name": "WebRTC-注册到信令服务器(addWebrtcRoomKeeper)",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "{{ZLMediaKit_URL}}/index/api/addWebrtcRoomKeeper?secret={{ZLMediaKit_secret}}&server_host=127.0.0.1&server_port=3000&room_id=peer_1",
+ "host": [
+ "{{ZLMediaKit_URL}}"
+ ],
+ "path": [
+ "index",
+ "api",
+ "addWebrtcRoomKeeper"
+ ],
+ "query": [
+ {
+ "key": "secret",
+ "value": "{{ZLMediaKit_secret}}"
+ },
+ {
+ "key": "server_host",
+ "value": "127.0.0.1",
+ "description": "要注册到的信令服务器地址"
+ },
+ {
+ "key": "server_port",
+ "value": "3000",
+ "description": "要注册到的信令服务器端口"
+ },
+ {
+ "key": "room_id",
+ "value": "peer_1",
+ "description": "要注册到的roomid"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "WebRTC-从信令服务器注销(delWebrtcRoomKeeper)",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "{{ZLMediaKit_URL}}/index/api/delWebrtcRoomKeeper?secret={{ZLMediaKit_secret}}&room_key=",
+ "host": [
+ "{{ZLMediaKit_URL}}"
+ ],
+ "path": [
+ "index",
+ "api",
+ "delWebrtcRoomKeeper"
+ ],
+ "query": [
+ {
+ "key": "secret",
+ "value": "{{ZLMediaKit_secret}}"
+ },
+ {
+ "key": "room_key",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "WebRTC-Peer查看注册信息(listWebrtcRoomKeepers)",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "{{ZLMediaKit_URL}}/index/api/listWebrtcRoomKeepers?secret={{ZLMediaKit_secret}}",
+ "host": [
+ "{{ZLMediaKit_URL}}"
+ ],
+ "path": [
+ "index",
+ "api",
+ "listWebrtcRoomKeepers"
+ ],
+ "query": [
+ {
+ "key": "secret",
+ "value": "{{ZLMediaKit_secret}}"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "WebRTC-信令服务器查看注册信息(listWebrtcRooms)",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "{{ZLMediaKit_URL}}/index/api/listWebrtcRooms?secret={{ZLMediaKit_secret}}",
+ "host": [
+ "{{ZLMediaKit_URL}}"
+ ],
+ "path": [
+ "index",
+ "api",
+ "listWebrtcRooms"
+ ],
+ "query": [
+ {
+ "key": "secret",
+ "value": "{{ZLMediaKit_secret}}"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "WebRTC-查看WebRTCProxyPlayer连接信息(getWebrtcProxyPlayerInfo)",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "{{ZLMediaKit_URL}}/index/api/getWebrtcProxyPlayerInfo?secret={{ZLMediaKit_secret}}&key=__defaultVhost__/live/test",
+ "host": [
+ "{{ZLMediaKit_URL}}"
+ ],
+ "path": [
+ "index",
+ "api",
+ "getWebrtcProxyPlayerInfo"
+ ],
+ "query": [
+ {
+ "key": "secret",
+ "value": "{{ZLMediaKit_secret}}"
+ },
+ {
+ "key": "key",
+ "value": "__defaultVhost__/live/test"
+ }
+ ]
+ }
+ },
+ "response": []
}
],
"event": [
diff --git a/server/WebApi.cpp b/server/WebApi.cpp
index 6ff38fad..7d0dda9a 100755
--- a/server/WebApi.cpp
+++ b/server/WebApi.cpp
@@ -57,6 +57,10 @@
#include "../webrtc/WebRtcPlayer.h"
#include "../webrtc/WebRtcPusher.h"
#include "../webrtc/WebRtcEchoTest.h"
+#include "../webrtc/WebRtcSignalingPeer.h"
+#include "../webrtc/WebRtcSignalingSession.h"
+#include "../webrtc/WebRtcProxyPlayer.h"
+#include "../webrtc/WebRtcProxyPlayerImp.h"
#endif
#if defined(ENABLE_VERSION)
@@ -314,84 +318,6 @@ static inline void addHttpListener(){
});
}
-template
-class ServiceController {
-public:
- using Pointer = std::shared_ptr;
-
- void clear() {
- decltype(_map) copy;
- {
- std::lock_guard lck(_mtx);
- copy.swap(_map);
- }
- }
-
- size_t erase(const std::string &key) {
- Pointer erase_ptr;
- {
- std::lock_guard lck(_mtx);
- auto itr = _map.find(key);
- if (itr != _map.end()) {
- erase_ptr = itr->second;
- _map.erase(itr);
- return 1;
- }
- }
- return 0;
- }
-
- size_t size() {
- std::lock_guard lck(_mtx);
- return _map.size();
- }
-
- Pointer find(const std::string &key) const {
- std::lock_guard lck(_mtx);
- auto it = _map.find(key);
- if (it == _map.end()) {
- return nullptr;
- }
- return it->second;
- }
-
- void for_each(const std::function& cb) {
- std::lock_guard lck(_mtx);
- auto it = _map.begin();
- while (it != _map.end()) {
- cb(it->first, it->second);
- it++;
- }
- }
-
- template
- Pointer make(const std::string &key, _Args&& ...__args) {
- // assert(!find(key));
-
- auto server = std::make_shared(std::forward<_Args>(__args)...);
- std::lock_guard lck(_mtx);
- auto it = _map.emplace(key, server);
- assert(it.second);
- return server;
- }
-
- template
- Pointer makeWithAction(const std::string &key, function action, _Args&& ...__args) {
- // assert(!find(key));
-
- auto server = std::make_shared(std::forward<_Args>(__args)...);
- action(server);
- std::lock_guard lck(_mtx);
- auto it = _map.emplace(key, server);
- assert(it.second);
- return server;
- }
-
-private:
- std::unordered_map _map;
- mutable std::recursive_mutex _mtx;
-};
-
// 拉流代理器列表 [AUTO-TRANSLATED:6dcfb11f]
// Pull stream proxy list
static ServiceController s_player_proxy;
@@ -2127,35 +2053,6 @@ void installWebApi() {
});
#ifdef ENABLE_WEBRTC
- class WebRtcArgsImp : public WebRtcArgs {
- public:
- WebRtcArgsImp(const ArgsString &args, std::string session_id)
- : _args(args)
- , _session_id(std::move(session_id)) {}
- ~WebRtcArgsImp() override = default;
-
- toolkit::variant operator[](const string &key) const override {
- if (key == "url") {
- return getUrl();
- }
- return _args[key];
- }
-
- private:
- string getUrl() const{
- auto &allArgs = _args;
- CHECK_ARGS("app", "stream");
-
- string auth = _args["Authorization"]; // Authorization Bearer
- return StrPrinter << "rtc://" << _args["Host"] << "/" << _args["app"] << "/" << _args["stream"] << "?"
- << _args.parser.params() + "&session=" + _session_id + (auth.empty() ? "" : ("&Authorization=" + auth));
- }
-
- private:
- ArgsString _args;
- std::string _session_id;
- };
-
api_regist("/index/api/webrtc",[](API_ARGS_STRING_ASYNC){
CHECK_ARGS("type");
auto type = allArgs["type"];
@@ -2163,7 +2060,7 @@ void installWebApi() {
CHECK(!offer.empty(), "http body(webrtc offer sdp) is empty");
auto &session = static_cast(sender);
- auto args = std::make_shared(allArgs, sender.getIdentifier());
+ auto args = std::make_shared>(allArgs, sender.getIdentifier());
WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, val, offer, headerOut](const WebRtcInterface &exchanger) mutable {
auto &handler = const_cast(exchanger);
try {
@@ -2186,7 +2083,7 @@ void installWebApi() {
auto &session = static_cast(sender);
auto location = std::string(session.overSsl() ? "https://" : "http://") + allArgs["host"] + delete_webrtc_url;
- auto args = std::make_shared(allArgs, sender.getIdentifier());
+ auto args = std::make_shared>(allArgs, sender.getIdentifier());
WebRtcPluginManager::Instance().negotiateSdp(session, type, *args, [invoker, offer, headerOut, location](const WebRtcInterface &exchanger) mutable {
auto &handler = const_cast(exchanger);
try {
@@ -2220,6 +2117,103 @@ void installWebApi() {
obj->safeShutdown(SockException(Err_shutdown, "deleted by http api"));
invoker(200, headerOut, "");
});
+
+ // 获取WebRTCProxyPlayer 连接信息
+ api_regist("/index/api/getWebrtcProxyPlayerInfo", [](API_ARGS_MAP_ASYNC) {
+ CHECK_SECRET();
+ CHECK_ARGS("key");
+
+ auto player_proxy = s_player_proxy.find(allArgs["key"]);
+ if (!player_proxy) {
+ throw ApiRetException("Stream proxy not found", API::NotFound);
+ }
+
+ auto media_player = player_proxy->getDelegate();
+ if (!media_player) {
+ throw ApiRetException("Media player not found", API::OtherFailed);
+ }
+
+ auto webrtc_player_imp = std::dynamic_pointer_cast(media_player);
+ if (!webrtc_player_imp) {
+ throw ApiRetException("Stream proxy is not WebRTC type", API::OtherFailed);
+ }
+
+ auto webrtc_transport = webrtc_player_imp->getWebRtcTransport();
+ if (!webrtc_transport) {
+ throw ApiRetException("WebRTC transport not available", API::OtherFailed);
+ }
+
+ std::string stream_key = allArgs["key"];
+ webrtc_transport->getTransportInfo([val, headerOut, invoker, stream_key](Json::Value transport_info) mutable {
+ transport_info["stream_key"] = stream_key;
+
+ if (transport_info.isMember("error")) {
+ Json::Value error_val;
+ error_val["code"] = API::OtherFailed;
+ error_val["msg"] = transport_info["error"].asString();
+ invoker(200, headerOut, error_val.toStyledString());
+ return;
+ }
+
+ // 成功返回结果
+ Json::Value success_val;
+ success_val["code"] = API::Success;
+ success_val["msg"] = "success";
+ success_val["data"] = transport_info;
+ invoker(200, headerOut, success_val.toStyledString());
+ });
+ });
+
+ api_regist("/index/api/addWebrtcRoomKeeper",[](API_ARGS_MAP_ASYNC){
+ CHECK_SECRET();
+ CHECK_ARGS("server_host", "server_port", "room_id", "ssl");
+ //server_host: 信令服务器host
+ //server_post: 信令服务器host
+ //room_id: 注册的id,信令服务器会对该id进行唯一性检查
+ addWebrtcRoomKeeper(allArgs["server_host"], allArgs["server_port"], allArgs["room_id"], allArgs["ssl"],
+ [val, headerOut, invoker](const SockException &ex, const string &key) mutable {
+ if (ex) {
+ val["code"] = API::OtherFailed;
+ val["msg"] = ex.what();
+ } else {
+ val["msg"] = "success";
+ val["data"]["room_key"] = key;
+ }
+ invoker(200, headerOut, val.toStyledString());
+ });
+ });
+
+ api_regist("/index/api/delWebrtcRoomKeeper",[](API_ARGS_MAP_ASYNC){
+ CHECK_SECRET();
+ CHECK_ARGS("room_key");
+
+ delWebrtcRoomKeeper(allArgs["room_key"],
+ [val, headerOut, invoker](const SockException &ex) mutable {
+ if (ex) {
+ val["code"] = API::OtherFailed;
+ val["msg"] = ex.what();
+ }
+ invoker(200, headerOut, val.toStyledString());
+ });
+ });
+
+ api_regist("/index/api/listWebrtcRoomKeepers", [](API_ARGS_MAP) {
+ CHECK_SECRET();
+ listWebrtcRoomKeepers([&val](const std::string& key, const WebRtcSignalingPeer::Ptr& p) {
+ Json::Value item = ToJson(p);
+ item["room_key"] = key;
+ val["data"].append(item);
+ });
+ });
+
+ api_regist("/index/api/listWebrtcRooms", [](API_ARGS_MAP) {
+ CHECK_SECRET();
+ listWebrtcRooms([&val](const std::string& key, const WebRtcSignalingSession::Ptr& p) {
+ Json::Value item = ToJson(p);
+ item["room_id"] = key;
+ val["data"].append(item);
+ });
+ });
#endif
#if defined(ENABLE_VERSION)
diff --git a/server/WebApi.h b/server/WebApi.h
index 9830dfd4..19da8b99 100755
--- a/server/WebApi.h
+++ b/server/WebApi.h
@@ -1,228 +1,364 @@
-/*
- * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
- *
- * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
- *
- * Use of this source code is governed by MIT-like license that can be found in the
- * LICENSE file in the root of the source tree. All contributing project authors
- * may be found in the AUTHORS file in the root of the source tree.
- */
-
-#ifndef ZLMEDIAKIT_WEBAPI_H
-#define ZLMEDIAKIT_WEBAPI_H
-
-#include
-#include
-#include "json/json.h"
-#include "Common/Parser.h"
-#include "Network/Socket.h"
-#include "Http/HttpSession.h"
-#include "Common/MultiMediaSourceMuxer.h"
-
-// 配置文件路径 [AUTO-TRANSLATED:8a373c2f]
-// Configuration file path
-extern std::string g_ini_file;
-
-namespace mediakit {
-// //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981]
-// //////////RTSP server configuration///////////
-namespace Rtsp {
-extern const std::string kPort;
-} //namespace Rtsp
-
-// //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f]
-// //////////RTMP server configuration///////////
-namespace Rtmp {
-extern const std::string kPort;
-} //namespace RTMP
-} // namespace mediakit
-
-namespace API {
-typedef enum {
- NotFound = -500,//未找到
- Exception = -400,//代码抛异常
- InvalidArgs = -300,//参数不合法
- SqlFailed = -200,//sql执行失败
- AuthFailed = -100,//鉴权失败
- OtherFailed = -1,//业务代码执行失败,
- Success = 0//执行成功
-} ApiErr;
-
-extern const std::string kSecret;
-}//namespace API
-
-class ApiRetException: public std::runtime_error {
-public:
- ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){
- _code = code;
- }
- int code(){ return _code; }
-private:
- int _code;
-};
-
-class AuthException : public ApiRetException {
-public:
- AuthException(const char *str):ApiRetException(str,API::AuthFailed){}
-};
-
-class InvalidArgsException: public ApiRetException {
-public:
- InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){}
-};
-
-class SuccessException: public ApiRetException {
-public:
- SuccessException():ApiRetException("success",API::Success){}
-};
-
-using ApiArgsType = std::map;
-
-template
-std::string getValue(Args &args, const Key &key) {
- auto it = args.find(key);
- if (it == args.end()) {
- return "";
- }
- return it->second;
-}
-
-template
-std::string getValue(Json::Value &args, const Key &key) {
- auto value = args.find(key);
- if (value == nullptr) {
- return "";
- }
- return value->asString();
-}
-
-template
-std::string getValue(std::string &args, const Key &key) {
- return "";
-}
-
-template
-std::string getValue(const mediakit::Parser &parser, const Key &key) {
- auto ret = getValue(parser.getUrlArgs(), key);
- if (!ret.empty()) {
- return ret;
- }
- return getValue(parser.getHeader(), key);
-}
-
-template
-std::string getValue(const mediakit::Parser &parser, Args &args, const Key &key) {
- auto ret = getValue(args, key);
- if (!ret.empty()) {
- return ret;
- }
- return getValue(parser, key);
-}
-
-template
-class HttpAllArgs {
- mediakit::Parser* _parser = nullptr;
- Args* _args = nullptr;
-public:
- const mediakit::Parser& parser;
- Args& args;
-
- HttpAllArgs(const mediakit::Parser &p, Args &a): parser(p), args(a) {}
-
- HttpAllArgs(const HttpAllArgs &that): _parser(new mediakit::Parser(that.parser)),
- _args(new Args(that.args)),
- parser(*_parser), args(*_args) {}
- ~HttpAllArgs() {
- if (_parser) {
- delete _parser;
- }
- if (_args) {
- delete _args;
- }
- }
-
- template
- toolkit::variant operator[](const Key &key) const {
- return (toolkit::variant)getValue(parser, args, key);
- }
-};
-
-using ArgsMap = HttpAllArgs;
-using ArgsJson = HttpAllArgs;
-using ArgsString = HttpAllArgs;
-
-#define API_ARGS_MAP toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsMap &allArgs, Json::Value &val
-#define API_ARGS_MAP_ASYNC API_ARGS_MAP, const mediakit::HttpSession::HttpResponseInvoker &invoker
-#define API_ARGS_JSON toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsJson &allArgs, Json::Value &val
-#define API_ARGS_JSON_ASYNC API_ARGS_JSON, const mediakit::HttpSession::HttpResponseInvoker &invoker
-#define API_ARGS_STRING toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsString &allArgs, Json::Value &val
-#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker
-#define API_ARGS_VALUE sender, headerOut, allArgs, val
-
-// 注册http请求参数是map类型的http api [AUTO-TRANSLATED:8a273897]
-// Register http request parameters as map type http api
-void api_regist(const std::string &api_path, const std::function &func);
-// 注册http请求参数是map类型,但是可以异步回复的的http api [AUTO-TRANSLATED:9da5d5f5]
-// Register http request parameters as map type, but can be replied asynchronously http api
-void api_regist(const std::string &api_path, const std::function &func);
-
-// 注册http请求参数是Json::Value类型的http api(可以支持多级嵌套的json参数对象) [AUTO-TRANSLATED:c4794456]
-// Register http request parameters as Json::Value type http api (can support multi-level nested json parameter objects)
-void api_regist(const std::string &api_path, const std::function &func);
-// 注册http请求参数是Json::Value类型,但是可以异步回复的的http api [AUTO-TRANSLATED:742e57fd]
-// Register http request parameters as Json::Value type, but can be replied asynchronously http api
-void api_regist(const std::string &api_path, const std::function &func);
-
-// 注册http请求参数是http原始请求信息的http api [AUTO-TRANSLATED:72d3fe93]
-// Register http request parameters as http original request information http api
-void api_regist(const std::string &api_path, const std::function &func);
-// 注册http请求参数是http原始请求信息的异步回复的http api [AUTO-TRANSLATED:49feefa8]
-// Register http request parameters as http original request information asynchronous reply http api
-void api_regist(const std::string &api_path, const std::function &func);
-
-template
-bool checkArgs(Args &args, const Key &key) {
- return !args[key].empty();
-}
-
-template
-bool checkArgs(Args &args, const Key &key, const KeyTypes &...keys) {
- return checkArgs(args, key) && checkArgs(args, keys...);
-}
-
-// 检查http url中或body中或http header参数是否为空的宏 [AUTO-TRANSLATED:9de001a4]
-// Check whether the http url, body or http header parameters are empty
-#define CHECK_ARGS(...) \
- if(!checkArgs(allArgs,##__VA_ARGS__)){ \
- throw InvalidArgsException("Required parameter missed: " #__VA_ARGS__); \
- }
-
-// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 [AUTO-TRANSLATED:7546956c]
-// Check whether the http parameters contain the secret key, the ip of 127.0.0.1 does not check the key
-// 同时检测是否在ip白名单内 [AUTO-TRANSLATED:d12f963d]
-// Check whether it is in the ip whitelist at the same time
-#define CHECK_SECRET() \
- do { \
- auto ip = sender.get_peer_ip(); \
- if (!HttpFileManager::isIPAllowed(ip)) { \
- throw AuthException("Your ip is not allowed to access the service."); \
- } \
- CHECK_ARGS("secret"); \
- if (api_secret != allArgs["secret"]) { \
- throw AuthException("Incorrect secret"); \
- } \
- } while(false);
-
-void installWebApi();
-void unInstallWebApi();
-
-#if defined(ENABLE_RTPPROXY)
-uint16_t openRtpServer(uint16_t local_port, const mediakit::MediaTuple &tuple, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex=false);
-#endif
-
-Json::Value makeMediaSourceJson(mediakit::MediaSource &media);
-void getStatisticJson(const std::function &cb);
-void addStreamProxy(const mediakit::MediaTuple &tuple, const std::string &url, int retry_count,
- const mediakit::ProtocolOption &option, int rtp_type, float timeout_sec, const toolkit::mINI &args,
- const std::function &cb);
-#endif //ZLMEDIAKIT_WEBAPI_H
+/*
+ * Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
+ *
+ * This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
+ *
+ * Use of this source code is governed by MIT-like license that can be found in the
+ * LICENSE file in the root of the source tree. All contributing project authors
+ * may be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef ZLMEDIAKIT_WEBAPI_H
+#define ZLMEDIAKIT_WEBAPI_H
+
+#include
+#include
+#include "json/json.h"
+#include "Common/Parser.h"
+#include "Network/Socket.h"
+#include "Http/HttpSession.h"
+#include "Common/MultiMediaSourceMuxer.h"
+
+#if defined(ENABLE_WEBRTC)
+#include "webrtc/WebRtcTransport.h"
+#endif
+
+// 配置文件路径 [AUTO-TRANSLATED:8a373c2f]
+// Configuration file path
+extern std::string g_ini_file;
+
+namespace mediakit {
+// //////////RTSP服务器配置/////////// [AUTO-TRANSLATED:950e1981]
+// //////////RTSP server configuration///////////
+namespace Rtsp {
+extern const std::string kPort;
+} //namespace Rtsp
+
+// //////////RTMP服务器配置/////////// [AUTO-TRANSLATED:8de6f41f]
+// //////////RTMP server configuration///////////
+namespace Rtmp {
+extern const std::string kPort;
+} //namespace RTMP
+} // namespace mediakit
+
+namespace API {
+typedef enum {
+ NotFound = -500,//未找到
+ Exception = -400,//代码抛异常
+ InvalidArgs = -300,//参数不合法
+ SqlFailed = -200,//sql执行失败
+ AuthFailed = -100,//鉴权失败
+ OtherFailed = -1,//业务代码执行失败,
+ Success = 0//执行成功
+} ApiErr;
+
+extern const std::string kSecret;
+}//namespace API
+
+class ApiRetException: public std::runtime_error {
+public:
+ ApiRetException(const char *str = "success" ,int code = API::Success):runtime_error(str){
+ _code = code;
+ }
+ int code(){ return _code; }
+private:
+ int _code;
+};
+
+class AuthException : public ApiRetException {
+public:
+ AuthException(const char *str):ApiRetException(str,API::AuthFailed){}
+};
+
+class InvalidArgsException: public ApiRetException {
+public:
+ InvalidArgsException(const char *str):ApiRetException(str,API::InvalidArgs){}
+};
+
+class SuccessException: public ApiRetException {
+public:
+ SuccessException():ApiRetException("success",API::Success){}
+};
+
+using ApiArgsType = std::map;
+
+template
+std::string getValue(Args &args, const Key &key) {
+ auto it = args.find(key);
+ if (it == args.end()) {
+ return "";
+ }
+ return it->second;
+}
+
+template
+std::string getValue(Json::Value &args, const Key &key) {
+ auto value = args.find(key);
+ if (value == nullptr) {
+ return "";
+ }
+ return value->asString();
+}
+
+template
+std::string getValue(std::string &args, const Key &key) {
+ return "";
+}
+
+template
+std::string getValue(const mediakit::Parser &parser, const Key &key) {
+ auto ret = getValue(parser.getUrlArgs(), key);
+ if (!ret.empty()) {
+ return ret;
+ }
+ return getValue(parser.getHeader(), key);
+}
+
+template
+std::string getValue(mediakit::Parser &parser, const Key &key) {
+ return getValue((const mediakit::Parser &) parser, key);
+}
+
+template
+std::string getValue(const mediakit::Parser &parser, Args &args, const Key &key) {
+ auto ret = getValue(args, key);
+ if (!ret.empty()) {
+ return ret;
+ }
+ return getValue(parser, key);
+}
+
+template
+class HttpAllArgs {
+ mediakit::Parser* _parser = nullptr;
+ Args* _args = nullptr;
+public:
+ const mediakit::Parser& parser;
+ Args& args;
+
+ HttpAllArgs(const mediakit::Parser &p, Args &a): parser(p), args(a) {}
+
+ HttpAllArgs(const HttpAllArgs &that): _parser(new mediakit::Parser(that.parser)),
+ _args(new Args(that.args)),
+ parser(*_parser), args(*_args) {}
+ ~HttpAllArgs() {
+ if (_parser) {
+ delete _parser;
+ }
+ if (_args) {
+ delete _args;
+ }
+ }
+
+ template
+ toolkit::variant operator[](const Key &key) const {
+ return (toolkit::variant)getValue(parser, args, key);
+ }
+
+ const Args& getArgs() const {
+ return args;
+ }
+
+ const mediakit::Parser &getParser() const {
+ return parser;
+ }
+};
+
+using ArgsMap = HttpAllArgs;
+using ArgsJson = HttpAllArgs;
+using ArgsString = HttpAllArgs;
+
+#define API_ARGS_MAP toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsMap &allArgs, Json::Value &val
+#define API_ARGS_MAP_ASYNC API_ARGS_MAP, const mediakit::HttpSession::HttpResponseInvoker &invoker
+#define API_ARGS_JSON toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsJson &allArgs, Json::Value &val
+#define API_ARGS_JSON_ASYNC API_ARGS_JSON, const mediakit::HttpSession::HttpResponseInvoker &invoker
+#define API_ARGS_STRING toolkit::SockInfo &sender, mediakit::HttpSession::KeyValue &headerOut, const ArgsString &allArgs, Json::Value &val
+#define API_ARGS_STRING_ASYNC API_ARGS_STRING, const mediakit::HttpSession::HttpResponseInvoker &invoker
+#define API_ARGS_VALUE sender, headerOut, allArgs, val
+
+// 注册http请求参数是map类型的http api [AUTO-TRANSLATED:8a273897]
+// Register http request parameters as map type http api
+void api_regist(const std::string &api_path, const std::function &func);
+// 注册http请求参数是map类型,但是可以异步回复的的http api [AUTO-TRANSLATED:9da5d5f5]
+// Register http request parameters as map type, but can be replied asynchronously http api
+void api_regist(const std::string &api_path, const std::function &func);
+
+// 注册http请求参数是Json::Value类型的http api(可以支持多级嵌套的json参数对象) [AUTO-TRANSLATED:c4794456]
+// Register http request parameters as Json::Value type http api (can support multi-level nested json parameter objects)
+void api_regist(const std::string &api_path, const std::function &func);
+// 注册http请求参数是Json::Value类型,但是可以异步回复的的http api [AUTO-TRANSLATED:742e57fd]
+// Register http request parameters as Json::Value type, but can be replied asynchronously http api
+void api_regist(const std::string &api_path, const std::function &func);
+
+// 注册http请求参数是http原始请求信息的http api [AUTO-TRANSLATED:72d3fe93]
+// Register http request parameters as http original request information http api
+void api_regist(const std::string &api_path, const std::function &func);
+// 注册http请求参数是http原始请求信息的异步回复的http api [AUTO-TRANSLATED:49feefa8]
+// Register http request parameters as http original request information asynchronous reply http api
+void api_regist(const std::string &api_path, const std::function &func);
+
+template
+bool checkArgs(Args &args, const Key &key) {
+ return !args[key].empty();
+}
+
+template
+bool checkArgs(Args &args, const Key &key, const KeyTypes &...keys) {
+ return checkArgs(args, key) && checkArgs(args, keys...);
+}
+
+// 检查http url中或body中或http header参数是否为空的宏 [AUTO-TRANSLATED:9de001a4]
+// Check whether the http url, body or http header parameters are empty
+#define CHECK_ARGS(...) \
+ if(!checkArgs(allArgs,##__VA_ARGS__)){ \
+ throw InvalidArgsException("Required parameter missed: " #__VA_ARGS__); \
+ }
+
+// 检查http参数中是否附带secret密钥的宏,127.0.0.1的ip不检查密钥 [AUTO-TRANSLATED:7546956c]
+// Check whether the http parameters contain the secret key, the ip of 127.0.0.1 does not check the key
+// 同时检测是否在ip白名单内 [AUTO-TRANSLATED:d12f963d]
+// Check whether it is in the ip whitelist at the same time
+#define CHECK_SECRET() \
+ do { \
+ auto ip = sender.get_peer_ip(); \
+ if (!HttpFileManager::isIPAllowed(ip)) { \
+ throw AuthException("Your ip is not allowed to access the service."); \
+ } \
+ CHECK_ARGS("secret"); \
+ if (api_secret != allArgs["secret"]) { \
+ throw AuthException("Incorrect secret"); \
+ } \
+ } while(false);
+
+void installWebApi();
+void unInstallWebApi();
+
+#if defined(ENABLE_RTPPROXY)
+uint16_t openRtpServer(uint16_t local_port, const mediakit::MediaTuple &tuple, int tcp_mode, const std::string &local_ip, bool re_use_port, uint32_t ssrc, int only_track, bool multiplex=false);
+#endif
+
+Json::Value makeMediaSourceJson(mediakit::MediaSource &media);
+void getStatisticJson(const std::function &cb);
+void addStreamProxy(const mediakit::MediaTuple &tuple, const std::string &url, int retry_count,
+ const mediakit::ProtocolOption &option, int rtp_type, float timeout_sec, const toolkit::mINI &args,
+ const std::function &cb);
+
+template
+class ServiceController {
+public:
+ using Pointer = std::shared_ptr;
+ std::unordered_map _map;
+ mutable std::recursive_mutex _mtx;
+
+ void clear() {
+ decltype(_map) copy;
+ {
+ std::lock_guard lck(_mtx);
+ copy.swap(_map);
+ }
+ }
+
+ size_t erase(const std::string &key) {
+ Pointer erase_ptr;
+ {
+ std::lock_guard lck(_mtx);
+ auto itr = _map.find(key);
+ if (itr != _map.end()) {
+ erase_ptr = std::move(itr->second);
+ _map.erase(itr);
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ size_t size() {
+ std::lock_guard lck(_mtx);
+ return _map.size();
+ }
+
+ Pointer find(const std::string &key) const {
+ std::lock_guard lck(_mtx);
+ auto it = _map.find(key);
+ if (it == _map.end()) {
+ return nullptr;
+ }
+ return it->second;
+ }
+
+ void for_each(const std::function& cb) {
+ std::lock_guard lck(_mtx);
+ auto it = _map.begin();
+ while (it != _map.end()) {
+ cb(it->first, it->second);
+ it++;
+ }
+ }
+
+ template
+ Pointer make(const std::string &key, _Args&& ...__args) {
+ // assert(!find(key));
+
+ auto server = std::make_shared(std::forward<_Args>(__args)...);
+ std::lock_guard lck(_mtx);
+ auto it = _map.emplace(key, server);
+ assert(it.second);
+ return server;
+ }
+
+ template
+ Pointer makeWithAction(const std::string &key, std::function action, _Args&& ...__args) {
+ // assert(!find(key));
+
+ auto server = std::make_shared(std::forward<_Args>(__args)...);
+ action(server);
+ std::lock_guard lck(_mtx);
+ auto it = _map.emplace(key, server);
+ assert(it.second);
+ return server;
+ }
+
+ template
+ Pointer emplace(const std::string &key, _Args&& ...__args) {
+ // assert(!find(key));
+
+ auto server = std::static_pointer_cast(std::forward<_Args>(__args)...);
+ std::lock_guard lck(_mtx);
+ auto it = _map.emplace(key, server);
+ assert(it.second);
+ return server;
+ }
+};
+
+#if defined(ENABLE_WEBRTC)
+template
+class WebRtcArgsImp : public mediakit::WebRtcArgs {
+public:
+ WebRtcArgsImp(const HttpAllArgs &args, std::string session_id)
+ : _args(args)
+ , _session_id(std::move(session_id)) {}
+ ~WebRtcArgsImp() override = default;
+
+ toolkit::variant operator[](const std::string &key) const override {
+ if (key == "url") {
+ return getUrl();
+ }
+ return _args[key];
+ }
+
+private:
+ std::string getUrl() const {
+ auto &allArgs = _args;
+ CHECK_ARGS("app", "stream");
+
+ return StrPrinter << RTC_SCHEMA << "://" << (_args["Host"].empty() ? DEFAULT_VHOST : _args["Host"].data()) << "/" << _args["app"] << "/"
+ << _args["stream"] << "?" << _args.getParser().params() + "&session=" + _session_id;
+ }
+
+private:
+ HttpAllArgs _args;
+ std::string _session_id;
+};
+#endif
+
+#endif //ZLMEDIAKIT_WEBAPI_H
diff --git a/server/main.cpp b/server/main.cpp
index 014517da..3591dcff 100644
--- a/server/main.cpp
+++ b/server/main.cpp
@@ -30,6 +30,8 @@
#if defined(ENABLE_WEBRTC)
#include "../webrtc/WebRtcTransport.h"
#include "../webrtc/WebRtcSession.h"
+#include "../webrtc/WebRtcSignalingSession.h"
+#include "../webrtc/IceSession.hpp"
#endif
#if defined(ENABLE_SRT)
@@ -368,8 +370,17 @@ int start_main(int argc,char *argv[]) {
}
return Socket::createSocket(new_poller, false);
});
+
+ auto signaleSrv = std::make_shared();
+ auto signalsSrv = std::make_shared();
+ auto iceTcpSrv = std::make_shared();
+ auto iceSrv = std::make_shared();
uint16_t rtcPort = mINI::Instance()[Rtc::kPort];
uint16_t rtcTcpPort = mINI::Instance()[Rtc::kTcpPort];
+ uint16_t signalingPort = mINI::Instance()[Rtc::kSignalingPort];
+ uint16_t signalSslPort = mINI::Instance()[Rtc::kSignalingSslPort];
+ uint16_t icePort = mINI::Instance()[Rtc::kIcePort];
+ uint16_t iceTcpPort = mINI::Instance()[Rtc::kIceTcpPort];
#endif//defined(ENABLE_WEBRTC)
@@ -435,6 +446,12 @@ int start_main(int argc,char *argv[]) {
if (rtcTcpPort) { rtcSrv_tcp->start(rtcTcpPort, listen_ip);}
+ //webrtc 信令服务器
+ if (signalingPort) { signaleSrv->start(signalingPort);}
+ if (signalSslPort) { signalsSrv->start(signalSslPort);}
+ //STUN/TURN服务
+ if (icePort) { iceSrv->start(icePort);}
+ if (iceTcpPort) { iceTcpSrv->start(iceTcpPort);}
#endif//defined(ENABLE_WEBRTC)
#if defined(ENABLE_SRT)
diff --git a/src/Common/macros.cpp b/src/Common/macros.cpp
index e9574dcc..51a5eda2 100644
--- a/src/Common/macros.cpp
+++ b/src/Common/macros.cpp
@@ -30,7 +30,7 @@ namespace mediakit {
* [AUTO-TRANSLATED:f214f734]
*/
#if !defined(ENABLE_VERSION)
-const char kServerName[] = "ZLMediaKit-8.0(build in " __DATE__ " " __TIME__ ")";
+const char kServerName[] = "ZLMediaKit-9.0(build in " __DATE__ " " __TIME__ ")";
#else
const char kServerName[] = "ZLMediaKit(git hash:" COMMIT_HASH "/" COMMIT_TIME ",branch:" BRANCH_NAME ",build time:" BUILD_TIME ")";
#endif
diff --git a/src/Common/macros.h b/src/Common/macros.h
index e9f87826..4bca5b7e 100644
--- a/src/Common/macros.h
+++ b/src/Common/macros.h
@@ -62,6 +62,7 @@
}
#endif // CLEAR_ARR
+#define RTC_SCHEMA "rtc"
#define RTSP_SCHEMA "rtsp"
#define RTMP_SCHEMA "rtmp"
#define TS_SCHEMA "ts"
diff --git a/src/Http/HttpBody.cpp b/src/Http/HttpBody.cpp
index 9c062508..f7b0d3d0 100644
--- a/src/Http/HttpBody.cpp
+++ b/src/Http/HttpBody.cpp
@@ -163,7 +163,7 @@ static std::shared_ptr getSharedMmap(const string &file_path, int64_t &fil
if (addr_ == nullptr) {
mmap_close(hfile, hmapping, addr_);
- WarnL << "MapViewOfFile() " << file_path << " failed:";
+ WarnL << "MapViewOfFile() " << file_path << " failed:";
return nullptr;
}
diff --git a/src/Player/MediaPlayer.h b/src/Player/MediaPlayer.h
index ca187019..2d43b6ed 100644
--- a/src/Player/MediaPlayer.h
+++ b/src/Player/MediaPlayer.h
@@ -26,6 +26,7 @@ public:
void play(const std::string &url) override;
toolkit::EventPoller::Ptr getPoller();
void setOnCreateSocket(toolkit::Socket::onCreateSocket cb);
+ const PlayerBase::Ptr& getDelegate() const { return _delegate; }
private:
toolkit::EventPoller::Ptr _poller;
diff --git a/src/Player/PlayerBase.cpp b/src/Player/PlayerBase.cpp
index 57ac236a..949c555c 100644
--- a/src/Player/PlayerBase.cpp
+++ b/src/Player/PlayerBase.cpp
@@ -18,7 +18,9 @@
#ifdef ENABLE_SRT
#include "Srt/SrtPlayerImp.h"
#endif // ENABLE_SRT
-
+#ifdef ENABLE_WEBRTC
+#include "../webrtc/WebRtcProxyPlayerImp.h"
+#endif // ENABLE_WEBRTC
using namespace std;
using namespace toolkit;
@@ -84,6 +86,11 @@ PlayerBase::Ptr PlayerBase::createPlayer(const EventPoller::Ptr &in_poller, cons
return PlayerBase::Ptr(new SrtPlayerImp(poller), release_func);
}
#endif//ENABLE_SRT
+#ifdef ENABLE_WEBRTC
+ if ((strcasecmp("webrtc", prefix.data()) == 0 || strcasecmp("webrtcs", prefix.data()) == 0)) {
+ return PlayerBase::Ptr(new WebRtcProxyPlayerImp(poller), release_func);
+ }
+#endif//ENABLE_WEBRTC
throw std::invalid_argument("not supported play schema:" + url_in);
}
diff --git a/src/Player/PlayerProxy.cpp b/src/Player/PlayerProxy.cpp
index a7cea3e7..58e4850c 100644
--- a/src/Player/PlayerProxy.cpp
+++ b/src/Player/PlayerProxy.cpp
@@ -286,6 +286,10 @@ float PlayerProxy::getLossRate(MediaSource &sender, TrackType type) {
return getPacketLossRate(type);
}
+toolkit::EventPoller::Ptr PlayerProxy::getOwnerPoller(MediaSource &sender) {
+ return getPoller();
+}
+
TranslationInfo PlayerProxy::getTranslationInfo() {
return _transtalion_info;
}
diff --git a/src/Player/PlayerProxy.h b/src/Player/PlayerProxy.h
index 304d22a6..cc67ad94 100644
--- a/src/Player/PlayerProxy.h
+++ b/src/Player/PlayerProxy.h
@@ -151,6 +151,7 @@ private:
std::string getOriginUrl(MediaSource &sender) const override;
std::shared_ptr getOriginSock(MediaSource &sender) const override;
float getLossRate(MediaSource &sender, TrackType type) override;
+ toolkit::EventPoller::Ptr getOwnerPoller(MediaSource &sender) override;
void rePlay(const std::string &strUrl, int iFailedCnt);
void onPlaySuccess();
diff --git a/src/Pusher/PusherBase.cpp b/src/Pusher/PusherBase.cpp
index 47e6b8d1..055f5629 100644
--- a/src/Pusher/PusherBase.cpp
+++ b/src/Pusher/PusherBase.cpp
@@ -15,6 +15,9 @@
#ifdef ENABLE_SRT
#include "Srt/SrtPusher.h"
#endif // ENABLE_SRT
+#ifdef ENABLE_WEBRTC
+#include "../webrtc/WebRtcProxyPusher.h"
+#endif // ENABLE_WEBRTC
using namespace toolkit;
@@ -23,7 +26,8 @@ namespace mediakit {
static bool checkMediaSourceAndUrlMatch(const MediaSource::Ptr &src, const std::string &url) {
std::string prefix = findSubString(url.data(), NULL, "://");
- if (strcasecmp("rtsps", prefix.data()) == 0 || strcasecmp("rtsp", prefix.data()) == 0) {
+ if (strcasecmp("rtsps", prefix.data()) == 0 || strcasecmp("rtsp", prefix.data()) == 0 ||
+ strcasecmp("webrtcs", prefix.data()) == 0 || strcasecmp("webrtc", prefix.data()) == 0 ) {
auto rtsp_src = std::dynamic_pointer_cast(src);
if (!rtsp_src) {
return false;
@@ -91,6 +95,11 @@ PusherBase::Ptr PusherBase::createPusher(const EventPoller::Ptr &in_poller,
}
#endif//ENABLE_SRT
+#ifdef ENABLE_WEBRTC
+ if ((strcasecmp("webrtc", prefix.data()) == 0 || strcasecmp("webrtcs", prefix.data()) == 0)) {
+ return PusherBase::Ptr(new WebRtcProxyPusherImp(poller, std::dynamic_pointer_cast(src)), release_func);
+ }
+#endif//ENABLE_WEBRTC
throw std::invalid_argument("not supported push schema:" + url);
}
diff --git a/src/Pusher/PusherBase.h b/src/Pusher/PusherBase.h
index b6d5b1f1..269a5f38 100644
--- a/src/Pusher/PusherBase.h
+++ b/src/Pusher/PusherBase.h
@@ -69,7 +69,7 @@ public:
virtual size_t getSendSpeed() { return 0; }
virtual size_t getSendTotalBytes() { return 0; }
-
+
protected:
virtual void onShutdown(const toolkit::SockException &ex) = 0;
virtual void onPublishResult(const toolkit::SockException &ex) = 0;
@@ -139,11 +139,11 @@ public:
size_t getSendSpeed() override {
return _delegate ? _delegate->getSendSpeed() : Parent::getSendSpeed();
}
-
+
size_t getSendTotalBytes() override {
return _delegate ? _delegate->getSendTotalBytes() : Parent::getSendTotalBytes();
}
-
+
protected:
void onShutdown(const toolkit::SockException &ex) override {
if (_on_shutdown) {
diff --git a/src/Record/HlsMaker.cpp b/src/Record/HlsMaker.cpp
index 9905b4be..c5df3a9e 100644
--- a/src/Record/HlsMaker.cpp
+++ b/src/Record/HlsMaker.cpp
@@ -17,7 +17,7 @@ using namespace std;
namespace mediakit {
HlsMaker::HlsMaker(bool is_fmp4, float seg_duration, uint32_t seg_number, bool seg_keep) {
- _is_fmp4 = is_fmp4;
+ _is_fmp4 = is_fmp4;
// 最小允许设置为0,0个切片代表点播 [AUTO-TRANSLATED:19235e8e]
// Minimum allowed setting is 0, 0 slices represent on-demand
_seg_number = seg_number;
diff --git a/src/Record/Recorder.h b/src/Record/Recorder.h
index e0fde02a..e2107daf 100644
--- a/src/Record/Recorder.h
+++ b/src/Record/Recorder.h
@@ -27,6 +27,11 @@ struct MediaTuple {
std::string shortUrl() const {
return vhost + '/' + app + '/' + stream;
}
+
+ MediaTuple() = default;
+ MediaTuple(std::string vhost, std::string app, std::string stream, std::string params = "")
+ : vhost(std::move(vhost)), app(std::move(app)), stream(std::move(stream)), params(std::move(params)) {
+ }
};
class RecordInfo: public MediaTuple {
diff --git a/src/Rtmp/RtmpPusher.h b/src/Rtmp/RtmpPusher.h
index d634bcd8..878fa8f4 100644
--- a/src/Rtmp/RtmpPusher.h
+++ b/src/Rtmp/RtmpPusher.h
@@ -29,7 +29,7 @@ public:
size_t getSendSpeed() override;
size_t getSendTotalBytes() override;
-
+
protected:
//for Tcpclient override
void onRecv(const toolkit::Buffer::Ptr &buf) override;
diff --git a/src/Srt/SrtCaller.cpp b/src/Srt/SrtCaller.cpp
index 64936052..9fbed896 100644
--- a/src/Srt/SrtCaller.cpp
+++ b/src/Srt/SrtCaller.cpp
@@ -1012,9 +1012,9 @@ float SrtCaller::getTimeOutSec() {
GET_CONFIG(uint32_t, timeout, SRT::kTimeOutSec);
if (timeout <= 0) {
WarnL << "config srt " << kTimeOutSec << " not vaild";
- return 5 * 1000;
+ return 5.0f;
}
- return (float)timeout * (float)1000;
+ return (float)timeout;
};
std::string SrtCaller::generateStreamId() {
diff --git a/srt/Crypto.cpp b/srt/Crypto.cpp
index 40c29f8f..798809a4 100644
--- a/srt/Crypto.cpp
+++ b/srt/Crypto.cpp
@@ -49,11 +49,11 @@ inline const EVP_CIPHER* aes_key_len_mapping_ctr_cipher(int key_len) {
static bool aes_wrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len) {
#if defined(ENABLE_OPENSSL)
- EVP_CIPHER_CTX* ctx = NULL;
+ EVP_CIPHER_CTX* ctx = NULL;
*outLen = 0;
- do {
+ do {
if (!(ctx = EVP_CIPHER_CTX_new())) {
WarnL << "EVP_CIPHER_CTX_new fail";
break;
@@ -62,29 +62,29 @@ static bool aes_wrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, u
if (1 != EVP_EncryptInit_ex(ctx, aes_key_len_mapping_wrap_cipher(key_len), NULL, key, NULL)) {
WarnL << "EVP_EncryptInit_ex fail";
- break;
- }
+ break;
+ }
- int len1 = 0;
- if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
+ int len1 = 0;
+ if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
WarnL << "EVP_EncryptUpdate fail";
- break;
- }
+ break;
+ }
- int len2 = 0;
- if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
+ int len2 = 0;
+ if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
WarnL << "EVP_EncryptFinal_ex fail";
- break;
- }
+ break;
+ }
- *outLen = len1 + len2;
- } while (0);
+ *outLen = len1 + len2;
+ } while (0);
- if (ctx != NULL) {
- EVP_CIPHER_CTX_free(ctx);
- }
+ if (ctx != NULL) {
+ EVP_CIPHER_CTX_free(ctx);
+ }
- return *outLen != 0;
+ return *outLen != 0;
#else
return false;
#endif
@@ -103,11 +103,11 @@ static bool aes_wrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, u
static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len) {
#if defined(ENABLE_OPENSSL)
- EVP_CIPHER_CTX* ctx = NULL;
+ EVP_CIPHER_CTX* ctx = NULL;
*outLen = 0;
- do {
+ do {
if (!(ctx = EVP_CIPHER_CTX_new())) {
WarnL << "EVP_CIPHER_CTX_new fail";
@@ -117,8 +117,8 @@ static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen,
if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_wrap_cipher(key_len), NULL, key, NULL)) {
WarnL << "EVP_DecryptInit_ex fail";
- break;
- }
+ break;
+ }
//设置pkcs7padding
if (1 != EVP_CIPHER_CTX_set_padding(ctx, 1)) {
@@ -126,26 +126,26 @@ static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen,
break;
}
- int len1 = 0;
- if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
+ int len1 = 0;
+ if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
WarnL << "EVP_DecryptUpdate fail";
- break;
- }
+ break;
+ }
- int len2 = 0;
- if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
+ int len2 = 0;
+ if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
WarnL << "EVP_DecryptFinal_ex fail";
- break;
- }
+ break;
+ }
- *outLen = len1 + len2;
- } while (0);
+ *outLen = len1 + len2;
+ } while (0);
- if (ctx != NULL) {
- EVP_CIPHER_CTX_free(ctx);
- }
+ if (ctx != NULL) {
+ EVP_CIPHER_CTX_free(ctx);
+ }
- return *outLen != 0;
+ return *outLen != 0;
#else
return false;
@@ -166,11 +166,11 @@ static bool aes_unwrap(const uint8_t* in, int in_len, uint8_t* out, int* outLen,
static bool aes_ctr_encrypt(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len, uint8_t* iv) {
#if defined(ENABLE_OPENSSL)
- EVP_CIPHER_CTX* ctx = NULL;
+ EVP_CIPHER_CTX* ctx = NULL;
*outLen = 0;
- do {
+ do {
if (!(ctx = EVP_CIPHER_CTX_new())) {
WarnL << "EVP_CIPHER_CTX_new fail";
break;
@@ -178,29 +178,29 @@ static bool aes_ctr_encrypt(const uint8_t* in, int in_len, uint8_t* out, int* ou
if (1 != EVP_EncryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) {
WarnL << "EVP_EncryptInit_ex fail";
- break;
- }
+ break;
+ }
- int len1 = 0;
- if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
+ int len1 = 0;
+ if (1 != EVP_EncryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
WarnL << "EVP_EncryptUpdate fail";
- break;
- }
+ break;
+ }
- int len2 = 0;
- if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
+ int len2 = 0;
+ if (1 != EVP_EncryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
WarnL << "EVP_EncryptFinal_ex fail";
- break;
- }
+ break;
+ }
- *outLen = len1 + len2;
- } while (0);
+ *outLen = len1 + len2;
+ } while (0);
- if (ctx != NULL) {
- EVP_CIPHER_CTX_free(ctx);
- }
+ if (ctx != NULL) {
+ EVP_CIPHER_CTX_free(ctx);
+ }
- return *outLen != 0;
+ return *outLen != 0;
#else
return false;
#endif
@@ -221,42 +221,42 @@ static bool aes_ctr_encrypt(const uint8_t* in, int in_len, uint8_t* out, int* ou
static bool aes_ctr_decrypt(const uint8_t* in, int in_len, uint8_t* out, int* outLen, uint8_t* key, int key_len, uint8_t* iv) {
#if defined(ENABLE_OPENSSL)
- EVP_CIPHER_CTX* ctx = NULL;
+ EVP_CIPHER_CTX* ctx = NULL;
*outLen = 0;
- do {
+ do {
if (!(ctx = EVP_CIPHER_CTX_new())) {
WarnL << "EVP_CIPHER_CTX_new fail";
break;
}
- if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) {
+ if (1 != EVP_DecryptInit_ex(ctx, aes_key_len_mapping_ctr_cipher(key_len), NULL, key, iv)) {
WarnL << "EVP_DecryptInit_ex fail";
- break;
- }
+ break;
+ }
- int len1 = 0;
- if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
+ int len1 = 0;
+ if (1 != EVP_DecryptUpdate(ctx, (uint8_t*)out, &len1, (uint8_t*)in, in_len)) {
WarnL << "EVP_DecryptUpdate fail";
- break;
- }
+ break;
+ }
- int len2 = 0;
- if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
+ int len2 = 0;
+ if (1 != EVP_DecryptFinal_ex(ctx, (uint8_t*)out + len1, &len2)) {
WarnL << "EVP_DecryptFinal_ex fail";
- break;
- }
+ break;
+ }
- *outLen = len1 + len2;
- } while (0);
+ *outLen = len1 + len2;
+ } while (0);
- if (ctx != NULL) {
- EVP_CIPHER_CTX_free(ctx);
- }
+ if (ctx != NULL) {
+ EVP_CIPHER_CTX_free(ctx);
+ }
- return *outLen != 0;
+ return *outLen != 0;
#else
return false;
diff --git a/srt/Packet.cpp b/srt/Packet.cpp
index 1692d8fa..f3f55ca0 100644
--- a/srt/Packet.cpp
+++ b/srt/Packet.cpp
@@ -353,7 +353,7 @@ bool HandshakePacket::loadExtMessage(uint8_t *buf, size_t len) {
case HSExt::SRT_CMD_SID: ext = std::make_shared(); break;
case HSExt::SRT_CMD_KMREQ:
case HSExt::SRT_CMD_KMRSP:
- ext = std::make_shared(); break;
+ ext = std::make_shared(); break;
default: WarnL << "not support ext " << type; break;
}
if (ext) {
diff --git a/srt/SrtSession.hpp b/srt/SrtSession.hpp
index 4064534f..33579585 100644
--- a/srt/SrtSession.hpp
+++ b/srt/SrtSession.hpp
@@ -1,29 +1,29 @@
-#ifndef ZLMEDIAKIT_SRT_SESSION_H
-#define ZLMEDIAKIT_SRT_SESSION_H
-
-#include "Network/Session.h"
-#include "SrtTransport.hpp"
-
-namespace SRT {
-
-using namespace toolkit;
-
-class SrtSession : public Session {
-public:
- SrtSession(const Socket::Ptr &sock);
-
- void onRecv(const Buffer::Ptr &) override;
- void onError(const SockException &err) override;
- void onManager() override;
- void attachServer(const toolkit::Server &server) override;
- static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer);
-
-private:
- bool _find_transport = true;
- Ticker _ticker;
- struct sockaddr_storage _peer_addr;
- SrtTransport::Ptr _transport;
-};
-
-} // namespace SRT
-#endif // ZLMEDIAKIT_SRT_SESSION_H
\ No newline at end of file
+#ifndef ZLMEDIAKIT_SRT_SESSION_H
+#define ZLMEDIAKIT_SRT_SESSION_H
+
+#include "Network/Session.h"
+#include "SrtTransport.hpp"
+
+namespace SRT {
+
+using namespace toolkit;
+
+class SrtSession : public Session {
+public:
+ SrtSession(const Socket::Ptr &sock);
+
+ void onRecv(const Buffer::Ptr &) override;
+ void onError(const SockException &err) override;
+ void onManager() override;
+ void attachServer(const toolkit::Server &server) override;
+ static EventPoller::Ptr queryPoller(const Buffer::Ptr &buffer);
+
+private:
+ bool _find_transport = true;
+ Ticker _ticker;
+ struct sockaddr_storage _peer_addr;
+ SrtTransport::Ptr _transport;
+};
+
+} // namespace SRT
+#endif // ZLMEDIAKIT_SRT_SESSION_H
diff --git a/srt/SrtTransport.cpp b/srt/SrtTransport.cpp
index a968dbcd..d81d17b7 100644
--- a/srt/SrtTransport.cpp
+++ b/srt/SrtTransport.cpp
@@ -400,7 +400,7 @@ void SrtTransport::sendMsgDropReq(uint32_t first, uint32_t last) {
}
void SrtTransport::tryAnnounceKeyMaterial() {
- //TraceL;
+ //TraceL;
if (!_crypto) {
return;
diff --git a/srt/SrtTransport.hpp b/srt/SrtTransport.hpp
index 36edf093..c9511633 100644
--- a/srt/SrtTransport.hpp
+++ b/srt/SrtTransport.hpp
@@ -169,8 +169,8 @@ private:
// for encryption
Crypto::Ptr _crypto;
- Timer::Ptr _announce_timer;
- KeyMaterialPacket::Ptr _announce_req;
+ Timer::Ptr _announce_timer;
+ KeyMaterialPacket::Ptr _announce_req;
};
class SrtTransportManager {
diff --git a/webrtc/DtlsTransport.cpp b/webrtc/DtlsTransport.cpp
index b55906be..2480ce65 100644
--- a/webrtc/DtlsTransport.cpp
+++ b/webrtc/DtlsTransport.cpp
@@ -1,1442 +1,1445 @@
-/**
-ISC License
-
-Copyright © 2015, Iñaki Baz Castillo
-
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define MS_CLASS "RTC::DtlsTransport"
-// #define MS_LOG_DEV_LEVEL 3
-
-#include "DtlsTransport.hpp"
-#include "logger.h"
-#include
-#include
-#include
-#include
-#include
-#include // std::sprintf(), std::fopen()
-#include // std::memcpy(), std::strcmp()
-#include "Util/util.h"
-#include "Util/SSLBox.h"
-#include "Util/SSLUtil.h"
-
-using namespace std;
-
-#define LOG_OPENSSL_ERROR(desc) \
- do \
- { \
- if (ERR_peek_error() == 0) \
- MS_ERROR("OpenSSL error [desc:'%s']", desc); \
- else \
- { \
- int64_t err; \
- while ((err = ERR_get_error()) != 0) \
- { \
- MS_ERROR("OpenSSL error [desc:'%s', error:'%s']", desc, ERR_error_string(err, nullptr)); \
- } \
- ERR_clear_error(); \
- } \
- } while (false)
-
-/* Static methods for OpenSSL callbacks. */
-
-inline static int onSslCertificateVerify(int /*preverifyOk*/, X509_STORE_CTX* /*ctx*/)
-{
- MS_TRACE();
-
- // Always valid since DTLS certificates are self-signed.
- return 1;
-}
-
-inline static unsigned int onSslDtlsTimer(SSL* /*ssl*/, unsigned int timerUs)
-{
- if (timerUs == 0)
- return 100000;
- else if (timerUs >= 4000000)
- return 4000000;
- else
- return 2 * timerUs;
-}
-
-namespace RTC
-{
- /* Static. */
-
- // clang-format off
- static constexpr int DtlsMtu{ 1350 };
- // AES-HMAC: http://tools.ietf.org/html/rfc3711
- static constexpr size_t SrtpMasterKeyLength{ 16 };
- static constexpr size_t SrtpMasterSaltLength{ 14 };
- static constexpr size_t SrtpMasterLength{ SrtpMasterKeyLength + SrtpMasterSaltLength };
- // AES-GCM: http://tools.ietf.org/html/rfc7714
- static constexpr size_t SrtpAesGcm256MasterKeyLength{ 32 };
- static constexpr size_t SrtpAesGcm256MasterSaltLength{ 12 };
- static constexpr size_t SrtpAesGcm256MasterLength{ SrtpAesGcm256MasterKeyLength + SrtpAesGcm256MasterSaltLength };
- static constexpr size_t SrtpAesGcm128MasterKeyLength{ 16 };
- static constexpr size_t SrtpAesGcm128MasterSaltLength{ 12 };
- static constexpr size_t SrtpAesGcm128MasterLength{ SrtpAesGcm128MasterKeyLength + SrtpAesGcm128MasterSaltLength };
- // clang-format on
-
- /* Class variables. */
- // clang-format off
- std::map DtlsTransport::string2FingerprintAlgorithm =
- {
- { "sha-1", DtlsTransport::FingerprintAlgorithm::SHA1 },
- { "sha-224", DtlsTransport::FingerprintAlgorithm::SHA224 },
- { "sha-256", DtlsTransport::FingerprintAlgorithm::SHA256 },
- { "sha-384", DtlsTransport::FingerprintAlgorithm::SHA384 },
- { "sha-512", DtlsTransport::FingerprintAlgorithm::SHA512 }
- };
- std::map DtlsTransport::fingerprintAlgorithm2String =
- {
- { DtlsTransport::FingerprintAlgorithm::SHA1, "sha-1" },
- { DtlsTransport::FingerprintAlgorithm::SHA224, "sha-224" },
- { DtlsTransport::FingerprintAlgorithm::SHA256, "sha-256" },
- { DtlsTransport::FingerprintAlgorithm::SHA384, "sha-384" },
- { DtlsTransport::FingerprintAlgorithm::SHA512, "sha-512" }
- };
- std::map DtlsTransport::string2Role =
- {
- { "auto", DtlsTransport::Role::AUTO },
- { "client", DtlsTransport::Role::CLIENT },
- { "server", DtlsTransport::Role::SERVER }
- };
- std::vector DtlsTransport::srtpCryptoSuites =
- {
- { RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM, "SRTP_AEAD_AES_256_GCM" },
- { RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM, "SRTP_AEAD_AES_128_GCM" },
- { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80, "SRTP_AES128_CM_SHA1_80" },
- { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32, "SRTP_AES128_CM_SHA1_32" }
- };
- // clang-format on
-
- INSTANCE_IMP(DtlsTransport::DtlsEnvironment);
-
- /* Class methods. */
-
- DtlsTransport::DtlsEnvironment::DtlsEnvironment()
- {
- MS_TRACE();
-
- // Generate a X509 certificate and private key (unless PEM files are provided).
- auto ssl = toolkit::SSL_Initor::Instance().getSSLCtx("", true);
- if (!ssl || !ReadCertificateAndPrivateKeyFromContext(ssl.get())) {
- GenerateCertificateAndPrivateKey();
- }
-
- // Create a global SSL_CTX.
- CreateSslCtx();
-
- // Generate certificate fingerprints.
- GenerateFingerprints();
- }
-
- DtlsTransport::DtlsEnvironment::~DtlsEnvironment()
- {
- MS_TRACE();
-
- if (privateKey)
- EVP_PKEY_free(privateKey);
- if (certificate)
- X509_free(certificate);
- if (sslCtx)
- SSL_CTX_free(sslCtx);
- }
-
- void DtlsTransport::DtlsEnvironment::GenerateCertificateAndPrivateKey()
- {
- MS_TRACE();
-
- int ret{ 0 };
- EC_KEY* ecKey{ nullptr };
- X509_NAME* certName{ nullptr };
- std::string subject =
- std::string("mediasoup") + to_string(rand() % 999999 + 100000);
-
- // Create key with curve.
- ecKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
-
- if (!ecKey)
- {
- LOG_OPENSSL_ERROR("EC_KEY_new_by_curve_name() failed");
-
- goto error;
- }
-
- EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE);
-
- // NOTE: This can take some time.
- ret = EC_KEY_generate_key(ecKey);
-
- if (ret == 0)
- {
- LOG_OPENSSL_ERROR("EC_KEY_generate_key() failed");
-
- goto error;
- }
-
- // Create a private key object.
- privateKey = EVP_PKEY_new();
-
- if (!privateKey)
- {
- LOG_OPENSSL_ERROR("EVP_PKEY_new() failed");
-
- goto error;
- }
-
- // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
- ret = EVP_PKEY_assign_EC_KEY(privateKey, ecKey);
-
- if (ret == 0)
- {
- LOG_OPENSSL_ERROR("EVP_PKEY_assign_EC_KEY() failed");
-
- goto error;
- }
-
- // The EC key now belongs to the private key, so don't clean it up separately.
- ecKey = nullptr;
-
- // Create the X509 certificate.
- certificate = X509_new();
-
- if (!certificate)
- {
- LOG_OPENSSL_ERROR("X509_new() failed");
-
- goto error;
- }
-
- // Set version 3 (note that 0 means version 1).
- X509_set_version(certificate, 2);
-
- // Set serial number (avoid default 0).
- ASN1_INTEGER_set(
- X509_get_serialNumber(certificate),
- static_cast(rand() % 999999 + 100000));
-
- // Set valid period.
- X509_gmtime_adj(X509_get_notBefore(certificate), -315360000); // -10 years.
- X509_gmtime_adj(X509_get_notAfter(certificate), 315360000); // 10 years.
-
- // Set the public key for the certificate using the key.
- ret = X509_set_pubkey(certificate, privateKey);
-
- if (ret == 0)
- {
- LOG_OPENSSL_ERROR("X509_set_pubkey() failed");
-
- goto error;
- }
-
- // Set certificate fields.
- certName = X509_get_subject_name(certificate);
-
- if (!certName)
- {
- LOG_OPENSSL_ERROR("X509_get_subject_name() failed");
-
- goto error;
- }
-
- X509_NAME_add_entry_by_txt(
- certName, "O", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0);
- X509_NAME_add_entry_by_txt(
- certName, "CN", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0);
-
- // It is self-signed so set the issuer name to be the same as the subject.
- ret = X509_set_issuer_name(certificate, certName);
-
- if (ret == 0)
- {
- LOG_OPENSSL_ERROR("X509_set_issuer_name() failed");
-
- goto error;
- }
-
- // Sign the certificate with its own private key.
- ret = X509_sign(certificate, privateKey, EVP_sha1());
-
- if (ret == 0)
- {
- LOG_OPENSSL_ERROR("X509_sign() failed");
-
- goto error;
- }
-
- return;
-
- error:
-
- if (ecKey)
- EC_KEY_free(ecKey);
-
- if (privateKey)
- EVP_PKEY_free(privateKey); // NOTE: This also frees the EC key.
-
- if (certificate)
- X509_free(certificate);
-
- MS_THROW_ERROR("DTLS certificate and private key generation failed");
- }
-
- bool DtlsTransport::DtlsEnvironment::ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx)
- {
- MS_TRACE();
- certificate = SSL_CTX_get0_certificate(ctx);
- if (!certificate) {
- return false;
- }
- X509_up_ref(certificate);
-
- privateKey = SSL_CTX_get0_privatekey(ctx);
- if (!privateKey) {
- return false;
- }
- EVP_PKEY_up_ref(privateKey);
- InfoL << "Load webrtc dtls certificate: " << toolkit::SSLUtil::getServerName(certificate);
- return true;
- }
-
- void DtlsTransport::DtlsEnvironment::CreateSslCtx()
- {
- MS_TRACE();
-
- std::string dtlsSrtpCryptoSuites;
- int ret;
-
- /* Set the global DTLS context. */
-
- // Both DTLS 1.0 and 1.2 (requires OpenSSL >= 1.1.0).
- sslCtx = SSL_CTX_new(DTLS_method());
-
- if (!sslCtx)
- {
- LOG_OPENSSL_ERROR("SSL_CTX_new() failed");
-
- goto error;
- }
-
- ret = SSL_CTX_use_certificate(sslCtx, certificate);
-
- if (ret == 0)
- {
- LOG_OPENSSL_ERROR("SSL_CTX_use_certificate() failed");
-
- goto error;
- }
-
- ret = SSL_CTX_use_PrivateKey(sslCtx, privateKey);
-
- if (ret == 0)
- {
- LOG_OPENSSL_ERROR("SSL_CTX_use_PrivateKey() failed");
-
- goto error;
- }
-
- ret = SSL_CTX_check_private_key(sslCtx);
-
- if (ret == 0)
- {
- LOG_OPENSSL_ERROR("SSL_CTX_check_private_key() failed");
-
- goto error;
- }
-
- // Set options.
- SSL_CTX_set_options(
- sslCtx,
- SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET | SSL_OP_SINGLE_ECDH_USE |
- SSL_OP_NO_QUERY_MTU);
-
- // Don't use sessions cache.
- SSL_CTX_set_session_cache_mode(sslCtx, SSL_SESS_CACHE_OFF);
-
- // Read always as much into the buffer as possible.
- // NOTE: This is the default for DTLS, but a bug in non latest OpenSSL
- // versions makes this call required.
- SSL_CTX_set_read_ahead(sslCtx, 1);
-
- SSL_CTX_set_verify_depth(sslCtx, 4);
-
- // Require certificate from peer.
- SSL_CTX_set_verify(
- sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, onSslCertificateVerify);
-
- // Set SSL info callback.
- SSL_CTX_set_info_callback(sslCtx, [](const SSL* ssl, int where, int ret){
- static_cast(SSL_get_ex_data(ssl, 0))->OnSslInfo(where, ret);
- });
- // Set ciphers.
- ret = SSL_CTX_set_cipher_list(
- sslCtx, "DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK:!RC4");
-
- if (ret == 0)
- {
- LOG_OPENSSL_ERROR("SSL_CTX_set_cipher_list() failed");
-
- goto error;
- }
-
- // Enable ECDH ciphers.
- // DOC: http://en.wikibooks.org/wiki/OpenSSL/Diffie-Hellman_parameters
- // NOTE: https://code.google.com/p/chromium/issues/detail?id=406458
- // NOTE: https://bugs.ruby-lang.org/issues/12324
-
- // For OpenSSL >= 1.0.2.
- SSL_CTX_set_ecdh_auto(sslCtx, 1);
-
- // Set the "use_srtp" DTLS extension.
- for (auto it = DtlsTransport::srtpCryptoSuites.begin();
- it != DtlsTransport::srtpCryptoSuites.end();
- ++it)
- {
- if (it != DtlsTransport::srtpCryptoSuites.begin())
- dtlsSrtpCryptoSuites += ":";
-
- SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(*it);
- dtlsSrtpCryptoSuites += cryptoSuiteEntry->name;
- }
-
- MS_DEBUG_2TAGS(dtls, srtp, "setting SRTP cryptoSuites for DTLS: %s", dtlsSrtpCryptoSuites.c_str());
-
- // NOTE: This function returns 0 on success.
- ret = SSL_CTX_set_tlsext_use_srtp(sslCtx, dtlsSrtpCryptoSuites.c_str());
-
- if (ret != 0)
- {
- MS_ERROR(
- "SSL_CTX_set_tlsext_use_srtp() failed when entering '%s'", dtlsSrtpCryptoSuites.c_str());
- LOG_OPENSSL_ERROR("SSL_CTX_set_tlsext_use_srtp() failed");
-
- goto error;
- }
-
- return;
-
- error:
-
- if (sslCtx)
- {
- SSL_CTX_free(sslCtx);
- sslCtx = nullptr;
- }
-
- MS_THROW_ERROR("SSL context creation failed");
- }
-
- void DtlsTransport::DtlsEnvironment::GenerateFingerprints()
- {
- MS_TRACE();
-
- for (auto& kv : DtlsTransport::string2FingerprintAlgorithm)
- {
- const std::string& algorithmString = kv.first;
- FingerprintAlgorithm algorithm = kv.second;
- uint8_t binaryFingerprint[EVP_MAX_MD_SIZE];
- unsigned int size{ 0 };
- char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];
- const EVP_MD* hashFunction;
- int ret;
-
- switch (algorithm)
- {
- case FingerprintAlgorithm::SHA1:
- hashFunction = EVP_sha1();
- break;
-
- case FingerprintAlgorithm::SHA224:
- hashFunction = EVP_sha224();
- break;
-
- case FingerprintAlgorithm::SHA256:
- hashFunction = EVP_sha256();
- break;
-
- case FingerprintAlgorithm::SHA384:
- hashFunction = EVP_sha384();
- break;
-
- case FingerprintAlgorithm::SHA512:
- hashFunction = EVP_sha512();
- break;
-
- default:
- MS_THROW_ERROR("unknown algorithm");
- }
-
- ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size);
-
- if (ret == 0)
- {
- MS_ERROR("X509_digest() failed");
- MS_THROW_ERROR("Fingerprints generation failed");
- }
-
- // Convert to hexadecimal format in uppercase with colons.
- for (unsigned int i{ 0 }; i < size; ++i)
- {
- std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]);
- }
- hexFingerprint[(size * 3) - 1] = '\0';
-
- MS_DEBUG_TAG(dtls, "%-7s fingerprint: %s", algorithmString.c_str(), hexFingerprint);
-
- // Store it in the vector.
- DtlsTransport::Fingerprint fingerprint;
-
- fingerprint.algorithm = DtlsTransport::GetFingerprintAlgorithm(algorithmString);
- fingerprint.value = hexFingerprint;
-
- localFingerprints.push_back(fingerprint);
- }
- }
-
- /* Instance methods. */
-
- DtlsTransport::DtlsTransport(EventPoller::Ptr poller,Listener* listener) : poller(std::move(poller)), listener(listener)
- {
- MS_TRACE();
- env = DtlsEnvironment::Instance().shared_from_this();
-
- /* Set SSL. */
-
- this->ssl = SSL_new(env->sslCtx);
-
- if (!this->ssl)
- {
- LOG_OPENSSL_ERROR("SSL_new() failed");
-
- goto error;
- }
-
- // Set this as custom data.
- SSL_set_ex_data(this->ssl, 0, static_cast(this));
-
- this->sslBioFromNetwork = BIO_new(BIO_s_mem());
-
- if (!this->sslBioFromNetwork)
- {
- LOG_OPENSSL_ERROR("BIO_new() failed");
-
- SSL_free(this->ssl);
-
- goto error;
- }
-
- this->sslBioToNetwork = BIO_new(BIO_s_mem());
-
- if (!this->sslBioToNetwork)
- {
- LOG_OPENSSL_ERROR("BIO_new() failed");
-
- BIO_free(this->sslBioFromNetwork);
- SSL_free(this->ssl);
-
- goto error;
- }
-
- SSL_set_bio(this->ssl, this->sslBioFromNetwork, this->sslBioToNetwork);
-
- // Set the MTU so that we don't send packets that are too large with no fragmentation.
- SSL_set_mtu(this->ssl, DtlsMtu);
- DTLS_set_link_mtu(this->ssl, DtlsMtu);
-
- // Set callback handler for setting DTLS timer interval.
- DTLS_set_timer_cb(this->ssl, onSslDtlsTimer);
-
- return;
-
- error:
-
- // NOTE: At this point SSL_set_bio() was not called so we must free BIOs as
- // well.
- if (this->sslBioFromNetwork)
- BIO_free(this->sslBioFromNetwork);
-
- if (this->sslBioToNetwork)
- BIO_free(this->sslBioToNetwork);
-
- if (this->ssl)
- SSL_free(this->ssl);
-
- // NOTE: If this is not catched by the caller the program will abort, but
- // this should never happen.
- MS_THROW_ERROR("DtlsTransport instance creation failed");
- }
-
- DtlsTransport::~DtlsTransport()
- {
- MS_TRACE();
-
- if (IsRunning())
- {
- // Send close alert to the peer.
- SSL_shutdown(this->ssl);
- SendPendingOutgoingDtlsData();
- }
-
- if (this->ssl)
- {
- SSL_free(this->ssl);
-
- this->ssl = nullptr;
- this->sslBioFromNetwork = nullptr;
- this->sslBioToNetwork = nullptr;
- }
-
- // Close the DTLS timer.
- this->timer = nullptr;
- }
-
- void DtlsTransport::Dump() const
- {
- MS_TRACE();
-
- std::string state{ "new" };
- std::string role{ "none " };
-
- switch (this->state)
- {
- case DtlsState::CONNECTING:
- state = "connecting";
- break;
- case DtlsState::CONNECTED:
- state = "connected";
- break;
- case DtlsState::FAILED:
- state = "failed";
- break;
- case DtlsState::CLOSED:
- state = "closed";
- break;
- default:;
- }
-
- switch (this->localRole)
- {
- case Role::AUTO:
- role = "auto";
- break;
- case Role::SERVER:
- role = "server";
- break;
- case Role::CLIENT:
- role = "client";
- break;
- default:;
- }
-
- MS_DUMP("");
- MS_DUMP(" state : %s", state.c_str());
- MS_DUMP(" role : %s", role.c_str());
- MS_DUMP(" handshake done: : %s", this->handshakeDone ? "yes" : "no");
- MS_DUMP("");
- }
-
- void DtlsTransport::Run(Role localRole)
- {
- MS_TRACE();
-
- MS_ASSERT(
- localRole == Role::CLIENT || localRole == Role::SERVER,
- "local DTLS role must be 'client' or 'server'");
-
- Role previousLocalRole = this->localRole;
-
- if (localRole == previousLocalRole)
- {
- MS_ERROR("same local DTLS role provided, doing nothing");
-
- return;
- }
-
- // If the previous local DTLS role was 'client' or 'server' do reset.
- if (previousLocalRole == Role::CLIENT || previousLocalRole == Role::SERVER)
- {
- MS_DEBUG_TAG(dtls, "resetting DTLS due to local role change");
-
- Reset();
- }
-
- // Update local role.
- this->localRole = localRole;
-
- // Set state and notify the listener.
- this->state = DtlsState::CONNECTING;
- this->listener->OnDtlsTransportConnecting(this);
-
- switch (this->localRole)
- {
- case Role::CLIENT:
- {
- MS_DEBUG_TAG(dtls, "running [role:client]");
-
- SSL_set_connect_state(this->ssl);
- SSL_do_handshake(this->ssl);
- SendPendingOutgoingDtlsData();
- SetTimeout();
-
- break;
- }
-
- case Role::SERVER:
- {
- MS_DEBUG_TAG(dtls, "running [role:server]");
-
- SSL_set_accept_state(this->ssl);
- SSL_do_handshake(this->ssl);
-
- break;
- }
-
- default:
- {
- MS_ABORT("invalid local DTLS role");
- }
- }
- }
-
- bool DtlsTransport::SetRemoteFingerprint(Fingerprint fingerprint)
- {
- MS_TRACE();
-
- MS_ASSERT(
- fingerprint.algorithm != FingerprintAlgorithm::NONE, "no fingerprint algorithm provided");
-
- this->remoteFingerprint = fingerprint;
-
- // The remote fingerpring may have been set after DTLS handshake was done,
- // so we may need to process it now.
- if (this->handshakeDone && this->state != DtlsState::CONNECTED)
- {
- MS_DEBUG_TAG(dtls, "handshake already done, processing it right now");
-
- return ProcessHandshake();
- }
-
- return true;
- }
-
- void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len)
- {
- MS_TRACE();
-
- int written;
- int read;
-
- if (!IsRunning())
- {
- MS_WARN_TAG(nullptr,"cannot process data while not running");
- return;
- }
-
- // Write the received DTLS data into the sslBioFromNetwork.
- written =
- BIO_write(this->sslBioFromNetwork, static_cast(data), static_cast(len));
-
- if (written != static_cast(len))
- {
- MS_WARN_TAG(
- dtls,
- "OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)",
- static_cast(written),
- len);
- }
-
- // Must call SSL_read() to process received DTLS data.
- read = SSL_read(this->ssl, static_cast(DtlsTransport::sslReadBuffer), SslReadBufferSize);
-
- // Send data if it's ready.
- SendPendingOutgoingDtlsData();
-
- // Check SSL status and return if it is bad/closed.
- if (!CheckStatus(read))
- return;
-
- // Set/update the DTLS timeout.
- if (!SetTimeout())
- return;
-
- // Application data received. Notify to the listener.
- if (read > 0)
- {
- // It is allowed to receive DTLS data even before validating remote fingerprint.
- if (!this->handshakeDone)
- {
- MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done");
-
- return;
- }
-
- // Notify the listener.
- this->listener->OnDtlsTransportApplicationDataReceived(
- this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast(read));
- }
- }
-
- void DtlsTransport::SendApplicationData(const uint8_t* data, size_t len)
- {
- MS_TRACE();
-
- // We cannot send data to the peer if its remote fingerprint is not validated.
- if (this->state != DtlsState::CONNECTED)
- {
- MS_WARN_TAG(dtls, "cannot send application data while DTLS is not fully connected");
-
- return;
- }
-
- if (len == 0)
- {
- MS_WARN_TAG(dtls, "ignoring 0 length data");
-
- return;
- }
-
- int written;
-
- written = SSL_write(this->ssl, static_cast(data), static_cast(len));
-
- if (written < 0)
- {
- LOG_OPENSSL_ERROR("SSL_write() failed");
-
- if (!CheckStatus(written))
- return;
- }
- else if (written != static_cast(len))
- {
- MS_WARN_TAG(
- dtls, "OpenSSL SSL_write() wrote less (%d bytes) than given data (%zu bytes)", written, len);
- }
-
- // Send data.
- SendPendingOutgoingDtlsData();
- }
-
- void DtlsTransport::Reset()
- {
- MS_TRACE();
-
- int ret;
-
- if (!IsRunning())
- return;
-
- MS_WARN_TAG(dtls, "resetting DTLS transport");
-
- // Stop the DTLS timer.
- this->timer = nullptr;
-
- // We need to reset the SSL instance so we need to "shutdown" it, but we
- // don't want to send a Close Alert to the peer, so just don't call
- // SendPendingOutgoingDTLSData().
- SSL_shutdown(this->ssl);
-
- this->localRole = Role::NONE;
- this->state = DtlsState::NEW;
- this->handshakeDone = false;
- this->handshakeDoneNow = false;
-
- // Reset SSL status.
- // NOTE: For this to properly work, SSL_shutdown() must be called before.
- // NOTE: This may fail if not enough DTLS handshake data has been received,
- // but we don't care so just clear the error queue.
- ret = SSL_clear(this->ssl);
-
- if (ret == 0)
- ERR_clear_error();
- }
-
- inline bool DtlsTransport::CheckStatus(int returnCode)
- {
- MS_TRACE();
-
- int err;
- bool wasHandshakeDone = this->handshakeDone;
-
- err = SSL_get_error(this->ssl, returnCode);
-
- switch (err)
- {
- case SSL_ERROR_NONE:
- break;
-
- case SSL_ERROR_SSL:
- LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SSL");
- break;
-
- case SSL_ERROR_WANT_READ:
- break;
-
- case SSL_ERROR_WANT_WRITE:
- MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_WRITE");
- break;
-
- case SSL_ERROR_WANT_X509_LOOKUP:
- MS_DEBUG_TAG(dtls, "SSL status: SSL_ERROR_WANT_X509_LOOKUP");
- break;
-
- case SSL_ERROR_SYSCALL:
- LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SYSCALL");
- break;
-
- case SSL_ERROR_ZERO_RETURN:
- break;
-
- case SSL_ERROR_WANT_CONNECT:
- MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_CONNECT");
- break;
-
- case SSL_ERROR_WANT_ACCEPT:
- MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_ACCEPT");
- break;
-
- default:
- MS_WARN_TAG(dtls, "SSL status: unknown error");
- }
-
- // Check if the handshake (or re-handshake) has been done right now.
- if (this->handshakeDoneNow)
- {
- this->handshakeDoneNow = false;
- this->handshakeDone = true;
-
- // Stop the timer.
- this->timer = nullptr;
-
- // Process the handshake just once (ignore if DTLS renegotiation).
- if (!wasHandshakeDone && this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE)
- return ProcessHandshake();
-
- return true;
- }
- // Check if the peer sent close alert or a fatal error happened.
- else if (((SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN) != 0) || err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL)
- {
- if (this->state == DtlsState::CONNECTED)
- {
- MS_DEBUG_TAG(dtls, "disconnected");
-
- Reset();
-
- // Set state and notify the listener.
- this->state = DtlsState::CLOSED;
- this->listener->OnDtlsTransportClosed(this);
- }
- else
- {
- MS_WARN_TAG(dtls, "connection failed");
-
- Reset();
-
- // Set state and notify the listener.
- this->state = DtlsState::FAILED;
- this->listener->OnDtlsTransportFailed(this);
- }
-
- return false;
- }
- else
- {
- return true;
- }
- }
-
- inline void DtlsTransport::SendPendingOutgoingDtlsData()
- {
- MS_TRACE();
-
- if (BIO_eof(this->sslBioToNetwork))
- return;
-
- int64_t read;
- char* data{ nullptr };
-
- read = BIO_get_mem_data(this->sslBioToNetwork, &data); // NOLINT
-
- if (read <= 0)
- return;
-
- MS_DEBUG_DEV("%" PRIu64 " bytes of DTLS data ready to sent to the peer", read);
-
- // Notify the listener.
- this->listener->OnDtlsTransportSendData(
- this, reinterpret_cast(data), static_cast(read));
-
- // Clear the BIO buffer.
- // NOTE: the (void) avoids the -Wunused-value warning.
- (void)BIO_reset(this->sslBioToNetwork);
- }
-
- inline bool DtlsTransport::SetTimeout()
- {
- MS_TRACE();
-
- MS_ASSERT(
- this->state == DtlsState::CONNECTING || this->state == DtlsState::CONNECTED,
- "invalid DTLS state");
-
- int64_t ret;
- struct timeval dtlsTimeout{ 0, 0 };
- uint64_t timeoutMs;
-
- // NOTE: If ret == 0 then ignore the value in dtlsTimeout.
- // NOTE: No DTLSv_1_2_get_timeout() or DTLS_get_timeout() in OpenSSL 1.1.0-dev.
- ret = DTLSv1_get_timeout(this->ssl, static_cast(&dtlsTimeout)); // NOLINT
-
- if (ret == 0)
- return true;
-
- timeoutMs = (dtlsTimeout.tv_sec * static_cast(1000)) + (dtlsTimeout.tv_usec / 1000);
-
- if (timeoutMs == 0)
- {
- return true;
- }
- else if (timeoutMs < 30000)
- {
- MS_DEBUG_DEV("DTLS timer set in %" PRIu64 "ms", timeoutMs);
-
- weak_ptr weak_self = shared_from_this();
- this->timer = std::make_shared(timeoutMs / 1000.0f, [weak_self](){
- auto strong_self = weak_self.lock();
- if(strong_self){
- strong_self->OnTimer();
- }
- return true;
- }, this->poller);
-
- return true;
- }
- // NOTE: Don't start the timer again if the timeout is greater than 30 seconds.
- else
- {
- MS_WARN_TAG(dtls, "DTLS timeout too high (%" PRIu64 "ms), resetting DLTS", timeoutMs);
-
- Reset();
-
- // Set state and notify the listener.
- this->state = DtlsState::FAILED;
- this->listener->OnDtlsTransportFailed(this);
-
- return false;
- }
- }
-
- inline bool DtlsTransport::ProcessHandshake()
- {
- MS_TRACE();
-
- MS_ASSERT(this->handshakeDone, "handshake not done yet");
- MS_ASSERT(
- this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set");
-
- // Validate the remote fingerprint.
- if (!CheckRemoteFingerprint())
- {
- Reset();
-
- // Set state and notify the listener.
- this->state = DtlsState::FAILED;
- this->listener->OnDtlsTransportFailed(this);
-
- return false;
- }
-
- // Get the negotiated SRTP crypto suite.
- RTC::SrtpSession::CryptoSuite srtpCryptoSuite = GetNegotiatedSrtpCryptoSuite();
-
- if (srtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE)
- {
- // Extract the SRTP keys (will notify the listener with them).
- ExtractSrtpKeys(srtpCryptoSuite);
-
- return true;
- }
-
- // NOTE: We assume that "use_srtp" DTLS extension is required even if
- // there is no audio/video.
- MS_WARN_2TAGS(dtls, srtp, "SRTP crypto suite not negotiated");
-
- Reset();
-
- // Set state and notify the listener.
- this->state = DtlsState::FAILED;
- this->listener->OnDtlsTransportFailed(this);
-
- return false;
- }
-
- inline bool DtlsTransport::CheckRemoteFingerprint()
- {
- MS_TRACE();
-
- MS_ASSERT(
- this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set");
-
- X509* certificate;
- uint8_t binaryFingerprint[EVP_MAX_MD_SIZE];
- unsigned int size{ 0 };
- char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];
- const EVP_MD* hashFunction;
- int ret;
-
- certificate = SSL_get_peer_certificate(this->ssl);
-
- if (!certificate)
- {
- MS_WARN_TAG(dtls, "no certificate was provided by the peer");
-
- return false;
- }
-
- switch (this->remoteFingerprint.algorithm)
- {
- case FingerprintAlgorithm::SHA1:
- hashFunction = EVP_sha1();
- break;
-
- case FingerprintAlgorithm::SHA224:
- hashFunction = EVP_sha224();
- break;
-
- case FingerprintAlgorithm::SHA256:
- hashFunction = EVP_sha256();
- break;
-
- case FingerprintAlgorithm::SHA384:
- hashFunction = EVP_sha384();
- break;
-
- case FingerprintAlgorithm::SHA512:
- hashFunction = EVP_sha512();
- break;
-
- default:
- MS_ABORT("unknown algorithm");
- }
-
- // Compare the remote fingerprint with the value given via signaling.
- ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size);
-
- if (ret == 0)
- {
- MS_ERROR("X509_digest() failed");
-
- X509_free(certificate);
-
- return false;
- }
-
- // Convert to hexadecimal format in uppercase with colons.
- for (unsigned int i{ 0 }; i < size; ++i)
- {
- std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]);
- }
- hexFingerprint[(size * 3) - 1] = '\0';
-
- if (this->remoteFingerprint.value != hexFingerprint)
- {
- MS_WARN_TAG(
- dtls,
- "fingerprint in the remote certificate (%s) does not match the announced one (%s)",
- hexFingerprint,
- this->remoteFingerprint.value.c_str());
- X509_free(certificate);
- return false;
- }
-
- MS_DEBUG_TAG(dtls, "valid remote fingerprint");
-
- // Get the remote certificate in PEM format.
-
- BIO* bio = BIO_new(BIO_s_mem());
-
- // Ensure the underlying BUF_MEM structure is also freed.
- // NOTE: Avoid stupid "warning: value computed is not used [-Wunused-value]" since
- // BIO_set_close() always returns 1.
- (void)BIO_set_close(bio, BIO_CLOSE);
-
- ret = PEM_write_bio_X509(bio, certificate);
-
- if (ret != 1)
- {
- LOG_OPENSSL_ERROR("PEM_write_bio_X509() failed");
-
- X509_free(certificate);
- BIO_free(bio);
-
- return false;
- }
-
- BUF_MEM* mem;
-
- BIO_get_mem_ptr(bio, &mem); // NOLINT[cppcoreguidelines-pro-type-cstyle-cast]
-
- if (!mem || !mem->data || mem->length == 0u)
- {
- LOG_OPENSSL_ERROR("BIO_get_mem_ptr() failed");
-
- X509_free(certificate);
- BIO_free(bio);
-
- return false;
- }
-
- this->remoteCert = std::string(mem->data, mem->length);
-
- X509_free(certificate);
- BIO_free(bio);
-
- return true;
- }
-
- inline void DtlsTransport::ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite)
- {
- MS_TRACE();
-
- size_t srtpKeyLength{ 0 };
- size_t srtpSaltLength{ 0 };
- size_t srtpMasterLength{ 0 };
-
- switch (srtpCryptoSuite)
- {
- case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80:
- case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32:
- {
- srtpKeyLength = SrtpMasterKeyLength;
- srtpSaltLength = SrtpMasterSaltLength;
- srtpMasterLength = SrtpMasterLength;
-
- break;
- }
-
- case RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM:
- {
- srtpKeyLength = SrtpAesGcm256MasterKeyLength;
- srtpSaltLength = SrtpAesGcm256MasterSaltLength;
- srtpMasterLength = SrtpAesGcm256MasterLength;
-
- break;
- }
-
- case RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM:
- {
- srtpKeyLength = SrtpAesGcm128MasterKeyLength;
- srtpSaltLength = SrtpAesGcm128MasterSaltLength;
- srtpMasterLength = SrtpAesGcm128MasterLength;
-
- break;
- }
-
- default:
- {
- MS_ABORT("unknown SRTP crypto suite");
- }
- }
-
- auto* srtpMaterial = new uint8_t[srtpMasterLength * 2];
- uint8_t* srtpLocalKey{ nullptr };
- uint8_t* srtpLocalSalt{ nullptr };
- uint8_t* srtpRemoteKey{ nullptr };
- uint8_t* srtpRemoteSalt{ nullptr };
- auto* srtpLocalMasterKey = new uint8_t[srtpMasterLength];
- auto* srtpRemoteMasterKey = new uint8_t[srtpMasterLength];
- int ret;
-
- ret = SSL_export_keying_material(
- this->ssl, srtpMaterial, srtpMasterLength * 2, "EXTRACTOR-dtls_srtp", 19, nullptr, 0, 0);
-
- MS_ASSERT(ret != 0, "SSL_export_keying_material() failed");
-
- switch (this->localRole)
- {
- case Role::SERVER:
- {
- srtpRemoteKey = srtpMaterial;
- srtpLocalKey = srtpRemoteKey + srtpKeyLength;
- srtpRemoteSalt = srtpLocalKey + srtpKeyLength;
- srtpLocalSalt = srtpRemoteSalt + srtpSaltLength;
-
- break;
- }
-
- case Role::CLIENT:
- {
- srtpLocalKey = srtpMaterial;
- srtpRemoteKey = srtpLocalKey + srtpKeyLength;
- srtpLocalSalt = srtpRemoteKey + srtpKeyLength;
- srtpRemoteSalt = srtpLocalSalt + srtpSaltLength;
-
- break;
- }
-
- default:
- {
- MS_ABORT("no DTLS role set");
- }
- }
-
- // Create the SRTP local master key.
- std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength);
- std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength);
- // Create the SRTP remote master key.
- std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength);
- std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength);
-
- // Set state and notify the listener.
- this->state = DtlsState::CONNECTED;
- this->listener->OnDtlsTransportConnected(
- this,
- srtpCryptoSuite,
- srtpLocalMasterKey,
- srtpMasterLength,
- srtpRemoteMasterKey,
- srtpMasterLength,
- this->remoteCert);
-
- delete[] srtpMaterial;
- delete[] srtpLocalMasterKey;
- delete[] srtpRemoteMasterKey;
- }
-
- inline RTC::SrtpSession::CryptoSuite DtlsTransport::GetNegotiatedSrtpCryptoSuite()
- {
- MS_TRACE();
-
- RTC::SrtpSession::CryptoSuite negotiatedSrtpCryptoSuite = RTC::SrtpSession::CryptoSuite::NONE;
-
- // Ensure that the SRTP crypto suite has been negotiated.
- // NOTE: This is a OpenSSL type.
- SRTP_PROTECTION_PROFILE* sslSrtpCryptoSuite = SSL_get_selected_srtp_profile(this->ssl);
-
- if (!sslSrtpCryptoSuite)
- return negotiatedSrtpCryptoSuite;
-
- // Get the negotiated SRTP crypto suite.
- for (auto& srtpCryptoSuite : DtlsTransport::srtpCryptoSuites)
- {
- SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(srtpCryptoSuite);
-
- if (std::strcmp(sslSrtpCryptoSuite->name, cryptoSuiteEntry->name) == 0)
- {
- MS_DEBUG_2TAGS(dtls, srtp, "chosen SRTP crypto suite: %s", cryptoSuiteEntry->name);
-
- negotiatedSrtpCryptoSuite = cryptoSuiteEntry->cryptoSuite;
- }
- }
-
- MS_ASSERT(
- negotiatedSrtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE,
- "chosen SRTP crypto suite is not an available one");
-
- return negotiatedSrtpCryptoSuite;
- }
-
- inline void DtlsTransport::OnSslInfo(int where, int ret)
- {
- MS_TRACE();
-
- int w = where & -SSL_ST_MASK;
- const char* role;
-
- if ((w & SSL_ST_CONNECT) != 0)
- role = "client";
- else if ((w & SSL_ST_ACCEPT) != 0)
- role = "server";
- else
- role = "undefined";
-
- if ((where & SSL_CB_LOOP) != 0)
- {
- MS_DEBUG_TAG(dtls, "[role:%s, action:'%s']", role, SSL_state_string_long(this->ssl));
- }
- else if ((where & SSL_CB_ALERT) != 0)
- {
- const char* alertType;
-
- switch (*SSL_alert_type_string(ret))
- {
- case 'W':
- alertType = "warning";
- break;
-
- case 'F':
- alertType = "fatal";
- break;
-
- default:
- alertType = "undefined";
- }
-
- if ((where & SSL_CB_READ) != 0)
- {
- MS_WARN_TAG(dtls, "received DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
- }
- else if ((where & SSL_CB_WRITE) != 0)
- {
- MS_DEBUG_TAG(dtls, "sending DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
- }
- else
- {
- MS_DEBUG_TAG(dtls, "DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
- }
- }
- else if ((where & SSL_CB_EXIT) != 0)
- {
- if (ret == 0)
- MS_DEBUG_TAG(dtls, "[role:%s, failed:'%s']", role, SSL_state_string_long(this->ssl));
- else if (ret < 0)
- MS_DEBUG_TAG(dtls, "role: %s, waiting:'%s']", role, SSL_state_string_long(this->ssl));
- }
- else if ((where & SSL_CB_HANDSHAKE_START) != 0)
- {
- MS_DEBUG_TAG(dtls, "DTLS handshake start");
- }
- else if ((where & SSL_CB_HANDSHAKE_DONE) != 0)
- {
- MS_DEBUG_TAG(dtls, "DTLS handshake done");
-
- this->handshakeDoneNow = true;
- }
-
- // NOTE: checking SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN here upon
- // receipt of a close alert does not work (the flag is set after this callback).
- }
-
- inline void DtlsTransport::OnTimer()
- {
- MS_TRACE();
-
- // Workaround for https://github.com/openssl/openssl/issues/7998.
- if (this->handshakeDone)
- {
- // MS_DEBUG_DEV("handshake is done so return");
- return;
- }
-
- DTLSv1_handle_timeout(this->ssl);
-
- // If required, send DTLS data.
- SendPendingOutgoingDtlsData();
-
- // Set the DTLS timer again.
- SetTimeout();
- }
-} // namespace RTC
+/**
+ISC License
+
+Copyright © 2015, Iñaki Baz Castillo
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define MS_CLASS "RTC::DtlsTransport"
+// #define MS_LOG_DEV_LEVEL 3
+
+#include "DtlsTransport.hpp"
+#include "logger.h"
+#include
+#include
+#include
+#include
+#include
+#include // std::sprintf(), std::fopen()
+#include // std::memcpy(), std::strcmp()
+#include "Util/util.h"
+#include "Util/SSLBox.h"
+#include "Util/SSLUtil.h"
+
+using namespace std;
+using namespace toolkit;
+
+#define LOG_OPENSSL_ERROR(desc) \
+ do \
+ { \
+ if (ERR_peek_error() == 0) \
+ MS_ERROR("OpenSSL error [desc:'%s']", desc); \
+ else \
+ { \
+ int64_t err; \
+ while ((err = ERR_get_error()) != 0) \
+ { \
+ MS_ERROR("OpenSSL error [desc:'%s', error:'%s']", desc, ERR_error_string(err, nullptr)); \
+ } \
+ ERR_clear_error(); \
+ } \
+ } while (false)
+
+/* Static methods for OpenSSL callbacks. */
+
+inline static int onSslCertificateVerify(int /*preverifyOk*/, X509_STORE_CTX* /*ctx*/)
+{
+ MS_TRACE();
+
+ // Always valid since DTLS certificates are self-signed.
+ return 1;
+}
+
+inline static unsigned int onSslDtlsTimer(SSL* /*ssl*/, unsigned int timerUs)
+{
+ if (timerUs == 0)
+ return 100000;
+ else if (timerUs >= 4000000)
+ return 4000000;
+ else
+ return 2 * timerUs;
+}
+
+namespace RTC
+{
+ /* Static. */
+
+ // clang-format off
+ static constexpr int DtlsMtu{ 1350 };
+ // AES-HMAC: http://tools.ietf.org/html/rfc3711
+ static constexpr size_t SrtpMasterKeyLength{ 16 };
+ static constexpr size_t SrtpMasterSaltLength{ 14 };
+ static constexpr size_t SrtpMasterLength{ SrtpMasterKeyLength + SrtpMasterSaltLength };
+ // AES-GCM: http://tools.ietf.org/html/rfc7714
+ static constexpr size_t SrtpAesGcm256MasterKeyLength{ 32 };
+ static constexpr size_t SrtpAesGcm256MasterSaltLength{ 12 };
+ static constexpr size_t SrtpAesGcm256MasterLength{ SrtpAesGcm256MasterKeyLength + SrtpAesGcm256MasterSaltLength };
+ static constexpr size_t SrtpAesGcm128MasterKeyLength{ 16 };
+ static constexpr size_t SrtpAesGcm128MasterSaltLength{ 12 };
+ static constexpr size_t SrtpAesGcm128MasterLength{ SrtpAesGcm128MasterKeyLength + SrtpAesGcm128MasterSaltLength };
+ // clang-format on
+
+ /* Class variables. */
+ // clang-format off
+ std::map DtlsTransport::string2FingerprintAlgorithm =
+ {
+ { "sha-1", DtlsTransport::FingerprintAlgorithm::SHA1 },
+ { "sha-224", DtlsTransport::FingerprintAlgorithm::SHA224 },
+ { "sha-256", DtlsTransport::FingerprintAlgorithm::SHA256 },
+ { "sha-384", DtlsTransport::FingerprintAlgorithm::SHA384 },
+ { "sha-512", DtlsTransport::FingerprintAlgorithm::SHA512 }
+ };
+ std::map DtlsTransport::fingerprintAlgorithm2String =
+ {
+ { DtlsTransport::FingerprintAlgorithm::SHA1, "sha-1" },
+ { DtlsTransport::FingerprintAlgorithm::SHA224, "sha-224" },
+ { DtlsTransport::FingerprintAlgorithm::SHA256, "sha-256" },
+ { DtlsTransport::FingerprintAlgorithm::SHA384, "sha-384" },
+ { DtlsTransport::FingerprintAlgorithm::SHA512, "sha-512" }
+ };
+ std::map DtlsTransport::string2Role =
+ {
+ { "auto", DtlsTransport::Role::AUTO },
+ { "client", DtlsTransport::Role::CLIENT },
+ { "server", DtlsTransport::Role::SERVER }
+ };
+ std::vector DtlsTransport::srtpCryptoSuites =
+ {
+ { RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM, "SRTP_AEAD_AES_256_GCM" },
+ { RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM, "SRTP_AEAD_AES_128_GCM" },
+ { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80, "SRTP_AES128_CM_SHA1_80" },
+ { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32, "SRTP_AES128_CM_SHA1_32" }
+ };
+ // clang-format on
+
+ INSTANCE_IMP(DtlsTransport::DtlsEnvironment);
+
+ /* Class methods. */
+
+ DtlsTransport::DtlsEnvironment::DtlsEnvironment()
+ {
+ MS_TRACE();
+
+ // Generate a X509 certificate and private key (unless PEM files are provided).
+ auto ssl = toolkit::SSL_Initor::Instance().getSSLCtx("", true);
+ if (!ssl || !ReadCertificateAndPrivateKeyFromContext(ssl.get())) {
+ GenerateCertificateAndPrivateKey();
+ }
+
+ // Create a global SSL_CTX.
+ CreateSslCtx();
+
+ // Generate certificate fingerprints.
+ GenerateFingerprints();
+ }
+
+ DtlsTransport::DtlsEnvironment::~DtlsEnvironment()
+ {
+ MS_TRACE();
+
+ if (privateKey)
+ EVP_PKEY_free(privateKey);
+ if (certificate)
+ X509_free(certificate);
+ if (sslCtx)
+ SSL_CTX_free(sslCtx);
+ }
+
+ void DtlsTransport::DtlsEnvironment::GenerateCertificateAndPrivateKey()
+ {
+ MS_TRACE();
+
+ int ret{ 0 };
+ EC_KEY* ecKey{ nullptr };
+ X509_NAME* certName{ nullptr };
+ std::string subject =
+ std::string("mediasoup") + to_string(rand() % 999999 + 100000);
+
+ // Create key with curve.
+ ecKey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+
+ if (!ecKey)
+ {
+ LOG_OPENSSL_ERROR("EC_KEY_new_by_curve_name() failed");
+
+ goto error;
+ }
+
+ EC_KEY_set_asn1_flag(ecKey, OPENSSL_EC_NAMED_CURVE);
+
+ // NOTE: This can take some time.
+ ret = EC_KEY_generate_key(ecKey);
+
+ if (ret == 0)
+ {
+ LOG_OPENSSL_ERROR("EC_KEY_generate_key() failed");
+
+ goto error;
+ }
+
+ // Create a private key object.
+ privateKey = EVP_PKEY_new();
+
+ if (!privateKey)
+ {
+ LOG_OPENSSL_ERROR("EVP_PKEY_new() failed");
+
+ goto error;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
+ ret = EVP_PKEY_assign_EC_KEY(privateKey, ecKey);
+
+ if (ret == 0)
+ {
+ LOG_OPENSSL_ERROR("EVP_PKEY_assign_EC_KEY() failed");
+
+ goto error;
+ }
+
+ // The EC key now belongs to the private key, so don't clean it up separately.
+ ecKey = nullptr;
+
+ // Create the X509 certificate.
+ certificate = X509_new();
+
+ if (!certificate)
+ {
+ LOG_OPENSSL_ERROR("X509_new() failed");
+
+ goto error;
+ }
+
+ // Set version 3 (note that 0 means version 1).
+ X509_set_version(certificate, 2);
+
+ // Set serial number (avoid default 0).
+ ASN1_INTEGER_set(
+ X509_get_serialNumber(certificate),
+ static_cast(rand() % 999999 + 100000));
+
+ // Set valid period.
+ X509_gmtime_adj(X509_get_notBefore(certificate), -315360000); // -10 years.
+ X509_gmtime_adj(X509_get_notAfter(certificate), 315360000); // 10 years.
+
+ // Set the public key for the certificate using the key.
+ ret = X509_set_pubkey(certificate, privateKey);
+
+ if (ret == 0)
+ {
+ LOG_OPENSSL_ERROR("X509_set_pubkey() failed");
+
+ goto error;
+ }
+
+ // Set certificate fields.
+ certName = X509_get_subject_name(certificate);
+
+ if (!certName)
+ {
+ LOG_OPENSSL_ERROR("X509_get_subject_name() failed");
+
+ goto error;
+ }
+
+ X509_NAME_add_entry_by_txt(
+ certName, "O", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0);
+ X509_NAME_add_entry_by_txt(
+ certName, "CN", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0);
+
+ // It is self-signed so set the issuer name to be the same as the subject.
+ ret = X509_set_issuer_name(certificate, certName);
+
+ if (ret == 0)
+ {
+ LOG_OPENSSL_ERROR("X509_set_issuer_name() failed");
+
+ goto error;
+ }
+
+ // Sign the certificate with its own private key.
+ ret = X509_sign(certificate, privateKey, EVP_sha1());
+
+ if (ret == 0)
+ {
+ LOG_OPENSSL_ERROR("X509_sign() failed");
+
+ goto error;
+ }
+
+ return;
+
+ error:
+
+ if (ecKey)
+ EC_KEY_free(ecKey);
+
+ if (privateKey)
+ EVP_PKEY_free(privateKey); // NOTE: This also frees the EC key.
+
+ if (certificate)
+ X509_free(certificate);
+
+ MS_THROW_ERROR("DTLS certificate and private key generation failed");
+ }
+
+ bool DtlsTransport::DtlsEnvironment::ReadCertificateAndPrivateKeyFromContext(SSL_CTX *ctx)
+ {
+ MS_TRACE();
+ certificate = SSL_CTX_get0_certificate(ctx);
+ if (!certificate) {
+ return false;
+ }
+ X509_up_ref(certificate);
+
+ privateKey = SSL_CTX_get0_privatekey(ctx);
+ if (!privateKey) {
+ return false;
+ }
+ EVP_PKEY_up_ref(privateKey);
+ InfoL << "Load webrtc dtls certificate: " << toolkit::SSLUtil::getServerName(certificate);
+ return true;
+ }
+
+ void DtlsTransport::DtlsEnvironment::CreateSslCtx()
+ {
+ MS_TRACE();
+
+ std::string dtlsSrtpCryptoSuites;
+ int ret;
+
+ /* Set the global DTLS context. */
+
+ // Both DTLS 1.0 and 1.2 (requires OpenSSL >= 1.1.0).
+ sslCtx = SSL_CTX_new(DTLS_method());
+
+ if (!sslCtx)
+ {
+ LOG_OPENSSL_ERROR("SSL_CTX_new() failed");
+
+ goto error;
+ }
+
+ ret = SSL_CTX_use_certificate(sslCtx, certificate);
+
+ if (ret == 0)
+ {
+ LOG_OPENSSL_ERROR("SSL_CTX_use_certificate() failed");
+
+ goto error;
+ }
+
+ ret = SSL_CTX_use_PrivateKey(sslCtx, privateKey);
+
+ if (ret == 0)
+ {
+ LOG_OPENSSL_ERROR("SSL_CTX_use_PrivateKey() failed");
+
+ goto error;
+ }
+
+ ret = SSL_CTX_check_private_key(sslCtx);
+
+ if (ret == 0)
+ {
+ LOG_OPENSSL_ERROR("SSL_CTX_check_private_key() failed");
+
+ goto error;
+ }
+
+ // Set options.
+ SSL_CTX_set_options(
+ sslCtx,
+ SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET | SSL_OP_SINGLE_ECDH_USE |
+ SSL_OP_NO_QUERY_MTU);
+
+ // Don't use sessions cache.
+ SSL_CTX_set_session_cache_mode(sslCtx, SSL_SESS_CACHE_OFF);
+
+ // Read always as much into the buffer as possible.
+ // NOTE: This is the default for DTLS, but a bug in non latest OpenSSL
+ // versions makes this call required.
+ SSL_CTX_set_read_ahead(sslCtx, 1);
+
+ SSL_CTX_set_verify_depth(sslCtx, 4);
+
+ // Require certificate from peer.
+ SSL_CTX_set_verify(
+ sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, onSslCertificateVerify);
+
+ // Set SSL info callback.
+ SSL_CTX_set_info_callback(sslCtx, [](const SSL* ssl, int where, int ret){
+ static_cast(SSL_get_ex_data(ssl, 0))->OnSslInfo(where, ret);
+ });
+ // Set ciphers.
+ ret = SSL_CTX_set_cipher_list(
+ sslCtx, "DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK:!RC4");
+
+ if (ret == 0)
+ {
+ LOG_OPENSSL_ERROR("SSL_CTX_set_cipher_list() failed");
+
+ goto error;
+ }
+
+ // Enable ECDH ciphers.
+ // DOC: http://en.wikibooks.org/wiki/OpenSSL/Diffie-Hellman_parameters
+ // NOTE: https://code.google.com/p/chromium/issues/detail?id=406458
+ // NOTE: https://bugs.ruby-lang.org/issues/12324
+
+ // For OpenSSL >= 1.0.2.
+ SSL_CTX_set_ecdh_auto(sslCtx, 1);
+
+ // Set the "use_srtp" DTLS extension.
+ for (auto it = DtlsTransport::srtpCryptoSuites.begin();
+ it != DtlsTransport::srtpCryptoSuites.end();
+ ++it)
+ {
+ if (it != DtlsTransport::srtpCryptoSuites.begin())
+ dtlsSrtpCryptoSuites += ":";
+
+ SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(*it);
+ dtlsSrtpCryptoSuites += cryptoSuiteEntry->name;
+ }
+
+ MS_DEBUG_2TAGS(dtls, srtp, "setting SRTP cryptoSuites for DTLS: %s", dtlsSrtpCryptoSuites.c_str());
+
+ // NOTE: This function returns 0 on success.
+ ret = SSL_CTX_set_tlsext_use_srtp(sslCtx, dtlsSrtpCryptoSuites.c_str());
+
+ if (ret != 0)
+ {
+ MS_ERROR(
+ "SSL_CTX_set_tlsext_use_srtp() failed when entering '%s'", dtlsSrtpCryptoSuites.c_str());
+ LOG_OPENSSL_ERROR("SSL_CTX_set_tlsext_use_srtp() failed");
+
+ goto error;
+ }
+
+ return;
+
+ error:
+
+ if (sslCtx)
+ {
+ SSL_CTX_free(sslCtx);
+ sslCtx = nullptr;
+ }
+
+ MS_THROW_ERROR("SSL context creation failed");
+ }
+
+ void DtlsTransport::DtlsEnvironment::GenerateFingerprints()
+ {
+ MS_TRACE();
+
+ for (auto& kv : DtlsTransport::string2FingerprintAlgorithm)
+ {
+ const std::string& algorithmString = kv.first;
+ FingerprintAlgorithm algorithm = kv.second;
+ uint8_t binaryFingerprint[EVP_MAX_MD_SIZE];
+ unsigned int size{ 0 };
+ char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];
+ const EVP_MD* hashFunction;
+ int ret;
+
+ switch (algorithm)
+ {
+ case FingerprintAlgorithm::SHA1:
+ hashFunction = EVP_sha1();
+ break;
+
+ case FingerprintAlgorithm::SHA224:
+ hashFunction = EVP_sha224();
+ break;
+
+ case FingerprintAlgorithm::SHA256:
+ hashFunction = EVP_sha256();
+ break;
+
+ case FingerprintAlgorithm::SHA384:
+ hashFunction = EVP_sha384();
+ break;
+
+ case FingerprintAlgorithm::SHA512:
+ hashFunction = EVP_sha512();
+ break;
+
+ default:
+ MS_THROW_ERROR("unknown algorithm");
+ }
+
+ ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size);
+
+ if (ret == 0)
+ {
+ MS_ERROR("X509_digest() failed");
+ MS_THROW_ERROR("Fingerprints generation failed");
+ }
+
+ // Convert to hexadecimal format in uppercase with colons.
+ for (unsigned int i{ 0 }; i < size; ++i)
+ {
+ std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]);
+ }
+ hexFingerprint[(size * 3) - 1] = '\0';
+
+ MS_DEBUG_TAG(dtls, "%-7s fingerprint: %s", algorithmString.c_str(), hexFingerprint);
+
+ // Store it in the vector.
+ DtlsTransport::Fingerprint fingerprint;
+
+ fingerprint.algorithm = DtlsTransport::GetFingerprintAlgorithm(algorithmString);
+ fingerprint.value = hexFingerprint;
+
+ localFingerprints.push_back(fingerprint);
+ }
+ }
+
+ /* Instance methods. */
+
+ DtlsTransport::DtlsTransport(EventPoller::Ptr poller,Listener* listener) : poller(std::move(poller)), listener(listener)
+ {
+ MS_TRACE();
+ env = DtlsEnvironment::Instance().shared_from_this();
+
+ /* Set SSL. */
+
+ this->ssl = SSL_new(env->sslCtx);
+
+ if (!this->ssl)
+ {
+ LOG_OPENSSL_ERROR("SSL_new() failed");
+
+ goto error;
+ }
+
+ // Set this as custom data.
+ SSL_set_ex_data(this->ssl, 0, static_cast(this));
+
+ this->sslBioFromNetwork = BIO_new(BIO_s_mem());
+
+ if (!this->sslBioFromNetwork)
+ {
+ LOG_OPENSSL_ERROR("BIO_new() failed");
+
+ SSL_free(this->ssl);
+
+ goto error;
+ }
+
+ this->sslBioToNetwork = BIO_new(BIO_s_mem());
+
+ if (!this->sslBioToNetwork)
+ {
+ LOG_OPENSSL_ERROR("BIO_new() failed");
+
+ BIO_free(this->sslBioFromNetwork);
+ SSL_free(this->ssl);
+
+ goto error;
+ }
+
+ SSL_set_bio(this->ssl, this->sslBioFromNetwork, this->sslBioToNetwork);
+
+ // Set the MTU so that we don't send packets that are too large with no fragmentation.
+ SSL_set_mtu(this->ssl, DtlsMtu);
+ DTLS_set_link_mtu(this->ssl, DtlsMtu);
+
+ // Set callback handler for setting DTLS timer interval.
+ DTLS_set_timer_cb(this->ssl, onSslDtlsTimer);
+
+ return;
+
+ error:
+
+ // NOTE: At this point SSL_set_bio() was not called so we must free BIOs as
+ // well.
+ if (this->sslBioFromNetwork)
+ BIO_free(this->sslBioFromNetwork);
+
+ if (this->sslBioToNetwork)
+ BIO_free(this->sslBioToNetwork);
+
+ if (this->ssl)
+ SSL_free(this->ssl);
+
+ // NOTE: If this is not catched by the caller the program will abort, but
+ // this should never happen.
+ MS_THROW_ERROR("DtlsTransport instance creation failed");
+ }
+
+ DtlsTransport::~DtlsTransport()
+ {
+ MS_TRACE();
+
+ if (IsRunning())
+ {
+ // Send close alert to the peer.
+ SSL_shutdown(this->ssl);
+ SendPendingOutgoingDtlsData();
+ }
+
+ if (this->ssl)
+ {
+ SSL_free(this->ssl);
+
+ this->ssl = nullptr;
+ this->sslBioFromNetwork = nullptr;
+ this->sslBioToNetwork = nullptr;
+ }
+
+ // Close the DTLS timer.
+ this->timer = nullptr;
+ }
+
+ void DtlsTransport::Dump() const
+ {
+ MS_TRACE();
+
+ std::string state{ "new" };
+ std::string role{ "none " };
+
+ switch (this->state)
+ {
+ case DtlsState::CONNECTING:
+ state = "connecting";
+ break;
+ case DtlsState::CONNECTED:
+ state = "connected";
+ break;
+ case DtlsState::FAILED:
+ state = "failed";
+ break;
+ case DtlsState::CLOSED:
+ state = "closed";
+ break;
+ default:;
+ }
+
+ switch (this->localRole)
+ {
+ case Role::AUTO:
+ role = "auto";
+ break;
+ case Role::SERVER:
+ role = "server";
+ break;
+ case Role::CLIENT:
+ role = "client";
+ break;
+ default:;
+ }
+
+ MS_DUMP("");
+ MS_DUMP(" state : %s", state.c_str());
+ MS_DUMP(" role : %s", role.c_str());
+ MS_DUMP(" handshake done: : %s", this->handshakeDone ? "yes" : "no");
+ MS_DUMP("");
+ }
+
+ void DtlsTransport::Run(Role localRole)
+ {
+ DebugL << ((localRole == RTC::DtlsTransport::Role::SERVER)? "Server" : "Client");
+
+ MS_TRACE();
+
+ MS_ASSERT(
+ localRole == Role::CLIENT || localRole == Role::SERVER,
+ "local DTLS role must be 'client' or 'server'");
+
+ Role previousLocalRole = this->localRole;
+
+ if (localRole == previousLocalRole)
+ {
+ MS_ERROR("same local DTLS role provided, doing nothing");
+
+ return;
+ }
+
+ // If the previous local DTLS role was 'client' or 'server' do reset.
+ if (previousLocalRole == Role::CLIENT || previousLocalRole == Role::SERVER)
+ {
+ MS_DEBUG_TAG(dtls, "resetting DTLS due to local role change");
+
+ Reset();
+ }
+
+ // Update local role.
+ this->localRole = localRole;
+
+ // Set state and notify the listener.
+ this->state = DtlsState::CONNECTING;
+ this->listener->OnDtlsTransportConnecting(this);
+
+ switch (this->localRole)
+ {
+ case Role::CLIENT:
+ {
+ MS_DEBUG_TAG(dtls, "running [role:client]");
+
+ SSL_set_connect_state(this->ssl);
+ SSL_do_handshake(this->ssl);
+ SendPendingOutgoingDtlsData();
+ SetTimeout();
+
+ break;
+ }
+
+ case Role::SERVER:
+ {
+ MS_DEBUG_TAG(dtls, "running [role:server]");
+
+ SSL_set_accept_state(this->ssl);
+ SSL_do_handshake(this->ssl);
+
+ break;
+ }
+
+ default:
+ {
+ MS_ABORT("invalid local DTLS role");
+ }
+ }
+ }
+
+ bool DtlsTransport::SetRemoteFingerprint(Fingerprint fingerprint)
+ {
+ MS_TRACE();
+
+ MS_ASSERT(
+ fingerprint.algorithm != FingerprintAlgorithm::NONE, "no fingerprint algorithm provided");
+
+ this->remoteFingerprint = fingerprint;
+
+ // The remote fingerpring may have been set after DTLS handshake was done,
+ // so we may need to process it now.
+ if (this->handshakeDone && this->state != DtlsState::CONNECTED)
+ {
+ MS_DEBUG_TAG(dtls, "handshake already done, processing it right now");
+
+ return ProcessHandshake();
+ }
+
+ return true;
+ }
+
+ void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len)
+ {
+ MS_TRACE();
+
+ int written;
+ int read;
+
+ if (!IsRunning())
+ {
+ MS_WARN_TAG(nullptr,"cannot process data while not running");
+ return;
+ }
+
+ // Write the received DTLS data into the sslBioFromNetwork.
+ written =
+ BIO_write(this->sslBioFromNetwork, static_cast(data), static_cast(len));
+
+ if (written != static_cast(len))
+ {
+ MS_WARN_TAG(
+ dtls,
+ "OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)",
+ static_cast(written),
+ len);
+ }
+
+ // Must call SSL_read() to process received DTLS data.
+ read = SSL_read(this->ssl, static_cast(DtlsTransport::sslReadBuffer), SslReadBufferSize);
+
+ // Send data if it's ready.
+ SendPendingOutgoingDtlsData();
+
+ // Check SSL status and return if it is bad/closed.
+ if (!CheckStatus(read))
+ return;
+
+ // Set/update the DTLS timeout.
+ if (!SetTimeout())
+ return;
+
+ // Application data received. Notify to the listener.
+ if (read > 0)
+ {
+ // It is allowed to receive DTLS data even before validating remote fingerprint.
+ if (!this->handshakeDone)
+ {
+ MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done");
+
+ return;
+ }
+
+ // Notify the listener.
+ this->listener->OnDtlsTransportApplicationDataReceived(
+ this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast(read));
+ }
+ }
+
+ void DtlsTransport::SendApplicationData(const uint8_t* data, size_t len)
+ {
+ MS_TRACE();
+
+ // We cannot send data to the peer if its remote fingerprint is not validated.
+ if (this->state != DtlsState::CONNECTED)
+ {
+ MS_WARN_TAG(dtls, "cannot send application data while DTLS is not fully connected");
+
+ return;
+ }
+
+ if (len == 0)
+ {
+ MS_WARN_TAG(dtls, "ignoring 0 length data");
+
+ return;
+ }
+
+ int written;
+
+ written = SSL_write(this->ssl, static_cast(data), static_cast(len));
+
+ if (written < 0)
+ {
+ LOG_OPENSSL_ERROR("SSL_write() failed");
+
+ if (!CheckStatus(written))
+ return;
+ }
+ else if (written != static_cast(len))
+ {
+ MS_WARN_TAG(
+ dtls, "OpenSSL SSL_write() wrote less (%d bytes) than given data (%zu bytes)", written, len);
+ }
+
+ // Send data.
+ SendPendingOutgoingDtlsData();
+ }
+
+ void DtlsTransport::Reset()
+ {
+ MS_TRACE();
+
+ int ret;
+
+ if (!IsRunning())
+ return;
+
+ MS_WARN_TAG(dtls, "resetting DTLS transport");
+
+ // Stop the DTLS timer.
+ this->timer = nullptr;
+
+ // We need to reset the SSL instance so we need to "shutdown" it, but we
+ // don't want to send a Close Alert to the peer, so just don't call
+ // SendPendingOutgoingDTLSData().
+ SSL_shutdown(this->ssl);
+
+ this->localRole = Role::NONE;
+ this->state = DtlsState::NEW;
+ this->handshakeDone = false;
+ this->handshakeDoneNow = false;
+
+ // Reset SSL status.
+ // NOTE: For this to properly work, SSL_shutdown() must be called before.
+ // NOTE: This may fail if not enough DTLS handshake data has been received,
+ // but we don't care so just clear the error queue.
+ ret = SSL_clear(this->ssl);
+
+ if (ret == 0)
+ ERR_clear_error();
+ }
+
+ inline bool DtlsTransport::CheckStatus(int returnCode)
+ {
+ MS_TRACE();
+
+ int err;
+ bool wasHandshakeDone = this->handshakeDone;
+
+ err = SSL_get_error(this->ssl, returnCode);
+
+ switch (err)
+ {
+ case SSL_ERROR_NONE:
+ break;
+
+ case SSL_ERROR_SSL:
+ LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SSL");
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_WRITE");
+ break;
+
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ MS_DEBUG_TAG(dtls, "SSL status: SSL_ERROR_WANT_X509_LOOKUP");
+ break;
+
+ case SSL_ERROR_SYSCALL:
+ LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SYSCALL");
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ break;
+
+ case SSL_ERROR_WANT_CONNECT:
+ MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_CONNECT");
+ break;
+
+ case SSL_ERROR_WANT_ACCEPT:
+ MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_ACCEPT");
+ break;
+
+ default:
+ MS_WARN_TAG(dtls, "SSL status: unknown error");
+ }
+
+ // Check if the handshake (or re-handshake) has been done right now.
+ if (this->handshakeDoneNow)
+ {
+ this->handshakeDoneNow = false;
+ this->handshakeDone = true;
+
+ // Stop the timer.
+ this->timer = nullptr;
+
+ // Process the handshake just once (ignore if DTLS renegotiation).
+ if (!wasHandshakeDone && this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE)
+ return ProcessHandshake();
+
+ return true;
+ }
+ // Check if the peer sent close alert or a fatal error happened.
+ else if (((SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN) != 0) || err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL)
+ {
+ if (this->state == DtlsState::CONNECTED)
+ {
+ MS_DEBUG_TAG(dtls, "disconnected");
+
+ Reset();
+
+ // Set state and notify the listener.
+ this->state = DtlsState::CLOSED;
+ this->listener->OnDtlsTransportClosed(this);
+ }
+ else
+ {
+ MS_WARN_TAG(dtls, "connection failed");
+
+ Reset();
+
+ // Set state and notify the listener.
+ this->state = DtlsState::FAILED;
+ this->listener->OnDtlsTransportFailed(this);
+ }
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ inline void DtlsTransport::SendPendingOutgoingDtlsData()
+ {
+ MS_TRACE();
+
+ if (BIO_eof(this->sslBioToNetwork))
+ return;
+
+ int64_t read;
+ char* data{ nullptr };
+
+ read = BIO_get_mem_data(this->sslBioToNetwork, &data); // NOLINT
+
+ if (read <= 0)
+ return;
+
+ MS_DEBUG_DEV("%" PRIu64 " bytes of DTLS data ready to sent to the peer", read);
+
+ // Notify the listener.
+ this->listener->OnDtlsTransportSendData(
+ this, reinterpret_cast(data), static_cast(read));
+
+ // Clear the BIO buffer.
+ // NOTE: the (void) avoids the -Wunused-value warning.
+ (void)BIO_reset(this->sslBioToNetwork);
+ }
+
+ inline bool DtlsTransport::SetTimeout()
+ {
+ MS_TRACE();
+
+ MS_ASSERT(
+ this->state == DtlsState::CONNECTING || this->state == DtlsState::CONNECTED,
+ "invalid DTLS state");
+
+ int64_t ret;
+ struct timeval dtlsTimeout{ 0, 0 };
+ uint64_t timeoutMs;
+
+ // NOTE: If ret == 0 then ignore the value in dtlsTimeout.
+ // NOTE: No DTLSv_1_2_get_timeout() or DTLS_get_timeout() in OpenSSL 1.1.0-dev.
+ ret = DTLSv1_get_timeout(this->ssl, static_cast(&dtlsTimeout)); // NOLINT
+
+ if (ret == 0)
+ return true;
+
+ timeoutMs = (dtlsTimeout.tv_sec * static_cast(1000)) + (dtlsTimeout.tv_usec / 1000);
+
+ if (timeoutMs == 0)
+ {
+ return true;
+ }
+ else if (timeoutMs < 30000)
+ {
+ MS_DEBUG_DEV("DTLS timer set in %" PRIu64 "ms", timeoutMs);
+
+ weak_ptr weak_self = shared_from_this();
+ this->timer = std::make_shared(timeoutMs / 1000.0f, [weak_self](){
+ auto strong_self = weak_self.lock();
+ if(strong_self){
+ strong_self->OnTimer();
+ }
+ return true;
+ }, this->poller);
+
+ return true;
+ }
+ // NOTE: Don't start the timer again if the timeout is greater than 30 seconds.
+ else
+ {
+ MS_WARN_TAG(dtls, "DTLS timeout too high (%" PRIu64 "ms), resetting DLTS", timeoutMs);
+
+ Reset();
+
+ // Set state and notify the listener.
+ this->state = DtlsState::FAILED;
+ this->listener->OnDtlsTransportFailed(this);
+
+ return false;
+ }
+ }
+
+ inline bool DtlsTransport::ProcessHandshake()
+ {
+ MS_TRACE();
+
+ MS_ASSERT(this->handshakeDone, "handshake not done yet");
+ MS_ASSERT(
+ this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set");
+
+ // Validate the remote fingerprint.
+ if (!CheckRemoteFingerprint())
+ {
+ Reset();
+
+ // Set state and notify the listener.
+ this->state = DtlsState::FAILED;
+ this->listener->OnDtlsTransportFailed(this);
+
+ return false;
+ }
+
+ // Get the negotiated SRTP crypto suite.
+ RTC::SrtpSession::CryptoSuite srtpCryptoSuite = GetNegotiatedSrtpCryptoSuite();
+
+ if (srtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE)
+ {
+ // Extract the SRTP keys (will notify the listener with them).
+ ExtractSrtpKeys(srtpCryptoSuite);
+
+ return true;
+ }
+
+ // NOTE: We assume that "use_srtp" DTLS extension is required even if
+ // there is no audio/video.
+ MS_WARN_2TAGS(dtls, srtp, "SRTP crypto suite not negotiated");
+
+ Reset();
+
+ // Set state and notify the listener.
+ this->state = DtlsState::FAILED;
+ this->listener->OnDtlsTransportFailed(this);
+
+ return false;
+ }
+
+ inline bool DtlsTransport::CheckRemoteFingerprint()
+ {
+ MS_TRACE();
+
+ MS_ASSERT(
+ this->remoteFingerprint.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set");
+
+ X509* certificate;
+ uint8_t binaryFingerprint[EVP_MAX_MD_SIZE];
+ unsigned int size{ 0 };
+ char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];
+ const EVP_MD* hashFunction;
+ int ret;
+
+ certificate = SSL_get_peer_certificate(this->ssl);
+
+ if (!certificate)
+ {
+ MS_WARN_TAG(dtls, "no certificate was provided by the peer");
+
+ return false;
+ }
+
+ switch (this->remoteFingerprint.algorithm)
+ {
+ case FingerprintAlgorithm::SHA1:
+ hashFunction = EVP_sha1();
+ break;
+
+ case FingerprintAlgorithm::SHA224:
+ hashFunction = EVP_sha224();
+ break;
+
+ case FingerprintAlgorithm::SHA256:
+ hashFunction = EVP_sha256();
+ break;
+
+ case FingerprintAlgorithm::SHA384:
+ hashFunction = EVP_sha384();
+ break;
+
+ case FingerprintAlgorithm::SHA512:
+ hashFunction = EVP_sha512();
+ break;
+
+ default:
+ MS_ABORT("unknown algorithm");
+ }
+
+ // Compare the remote fingerprint with the value given via signaling.
+ ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size);
+
+ if (ret == 0)
+ {
+ MS_ERROR("X509_digest() failed");
+
+ X509_free(certificate);
+
+ return false;
+ }
+
+ // Convert to hexadecimal format in uppercase with colons.
+ for (unsigned int i{ 0 }; i < size; ++i)
+ {
+ std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]);
+ }
+ hexFingerprint[(size * 3) - 1] = '\0';
+
+ if (this->remoteFingerprint.value != hexFingerprint)
+ {
+ MS_WARN_TAG(
+ dtls,
+ "fingerprint in the remote certificate (%s) does not match the announced one (%s)",
+ hexFingerprint,
+ this->remoteFingerprint.value.c_str());
+ X509_free(certificate);
+ return false;
+ }
+
+ MS_DEBUG_TAG(dtls, "valid remote fingerprint");
+
+ // Get the remote certificate in PEM format.
+
+ BIO* bio = BIO_new(BIO_s_mem());
+
+ // Ensure the underlying BUF_MEM structure is also freed.
+ // NOTE: Avoid stupid "warning: value computed is not used [-Wunused-value]" since
+ // BIO_set_close() always returns 1.
+ (void)BIO_set_close(bio, BIO_CLOSE);
+
+ ret = PEM_write_bio_X509(bio, certificate);
+
+ if (ret != 1)
+ {
+ LOG_OPENSSL_ERROR("PEM_write_bio_X509() failed");
+
+ X509_free(certificate);
+ BIO_free(bio);
+
+ return false;
+ }
+
+ BUF_MEM* mem;
+
+ BIO_get_mem_ptr(bio, &mem); // NOLINT[cppcoreguidelines-pro-type-cstyle-cast]
+
+ if (!mem || !mem->data || mem->length == 0u)
+ {
+ LOG_OPENSSL_ERROR("BIO_get_mem_ptr() failed");
+
+ X509_free(certificate);
+ BIO_free(bio);
+
+ return false;
+ }
+
+ this->remoteCert = std::string(mem->data, mem->length);
+
+ X509_free(certificate);
+ BIO_free(bio);
+
+ return true;
+ }
+
+ inline void DtlsTransport::ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite)
+ {
+ MS_TRACE();
+
+ size_t srtpKeyLength{ 0 };
+ size_t srtpSaltLength{ 0 };
+ size_t srtpMasterLength{ 0 };
+
+ switch (srtpCryptoSuite)
+ {
+ case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80:
+ case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32:
+ {
+ srtpKeyLength = SrtpMasterKeyLength;
+ srtpSaltLength = SrtpMasterSaltLength;
+ srtpMasterLength = SrtpMasterLength;
+
+ break;
+ }
+
+ case RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM:
+ {
+ srtpKeyLength = SrtpAesGcm256MasterKeyLength;
+ srtpSaltLength = SrtpAesGcm256MasterSaltLength;
+ srtpMasterLength = SrtpAesGcm256MasterLength;
+
+ break;
+ }
+
+ case RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM:
+ {
+ srtpKeyLength = SrtpAesGcm128MasterKeyLength;
+ srtpSaltLength = SrtpAesGcm128MasterSaltLength;
+ srtpMasterLength = SrtpAesGcm128MasterLength;
+
+ break;
+ }
+
+ default:
+ {
+ MS_ABORT("unknown SRTP crypto suite");
+ }
+ }
+
+ auto* srtpMaterial = new uint8_t[srtpMasterLength * 2];
+ uint8_t* srtpLocalKey{ nullptr };
+ uint8_t* srtpLocalSalt{ nullptr };
+ uint8_t* srtpRemoteKey{ nullptr };
+ uint8_t* srtpRemoteSalt{ nullptr };
+ auto* srtpLocalMasterKey = new uint8_t[srtpMasterLength];
+ auto* srtpRemoteMasterKey = new uint8_t[srtpMasterLength];
+ int ret;
+
+ ret = SSL_export_keying_material(
+ this->ssl, srtpMaterial, srtpMasterLength * 2, "EXTRACTOR-dtls_srtp", 19, nullptr, 0, 0);
+
+ MS_ASSERT(ret != 0, "SSL_export_keying_material() failed");
+
+ switch (this->localRole)
+ {
+ case Role::SERVER:
+ {
+ srtpRemoteKey = srtpMaterial;
+ srtpLocalKey = srtpRemoteKey + srtpKeyLength;
+ srtpRemoteSalt = srtpLocalKey + srtpKeyLength;
+ srtpLocalSalt = srtpRemoteSalt + srtpSaltLength;
+
+ break;
+ }
+
+ case Role::CLIENT:
+ {
+ srtpLocalKey = srtpMaterial;
+ srtpRemoteKey = srtpLocalKey + srtpKeyLength;
+ srtpLocalSalt = srtpRemoteKey + srtpKeyLength;
+ srtpRemoteSalt = srtpLocalSalt + srtpSaltLength;
+
+ break;
+ }
+
+ default:
+ {
+ MS_ABORT("no DTLS role set");
+ }
+ }
+
+ // Create the SRTP local master key.
+ std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength);
+ std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength);
+ // Create the SRTP remote master key.
+ std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength);
+ std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength);
+
+ // Set state and notify the listener.
+ this->state = DtlsState::CONNECTED;
+ this->listener->OnDtlsTransportConnected(
+ this,
+ srtpCryptoSuite,
+ srtpLocalMasterKey,
+ srtpMasterLength,
+ srtpRemoteMasterKey,
+ srtpMasterLength,
+ this->remoteCert);
+
+ delete[] srtpMaterial;
+ delete[] srtpLocalMasterKey;
+ delete[] srtpRemoteMasterKey;
+ }
+
+ inline RTC::SrtpSession::CryptoSuite DtlsTransport::GetNegotiatedSrtpCryptoSuite()
+ {
+ MS_TRACE();
+
+ RTC::SrtpSession::CryptoSuite negotiatedSrtpCryptoSuite = RTC::SrtpSession::CryptoSuite::NONE;
+
+ // Ensure that the SRTP crypto suite has been negotiated.
+ // NOTE: This is a OpenSSL type.
+ SRTP_PROTECTION_PROFILE* sslSrtpCryptoSuite = SSL_get_selected_srtp_profile(this->ssl);
+
+ if (!sslSrtpCryptoSuite)
+ return negotiatedSrtpCryptoSuite;
+
+ // Get the negotiated SRTP crypto suite.
+ for (auto& srtpCryptoSuite : DtlsTransport::srtpCryptoSuites)
+ {
+ SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(srtpCryptoSuite);
+
+ if (std::strcmp(sslSrtpCryptoSuite->name, cryptoSuiteEntry->name) == 0)
+ {
+ MS_DEBUG_2TAGS(dtls, srtp, "chosen SRTP crypto suite: %s", cryptoSuiteEntry->name);
+
+ negotiatedSrtpCryptoSuite = cryptoSuiteEntry->cryptoSuite;
+ }
+ }
+
+ MS_ASSERT(
+ negotiatedSrtpCryptoSuite != RTC::SrtpSession::CryptoSuite::NONE,
+ "chosen SRTP crypto suite is not an available one");
+
+ return negotiatedSrtpCryptoSuite;
+ }
+
+ inline void DtlsTransport::OnSslInfo(int where, int ret)
+ {
+ MS_TRACE();
+
+ int w = where & -SSL_ST_MASK;
+ const char* role;
+
+ if ((w & SSL_ST_CONNECT) != 0)
+ role = "client";
+ else if ((w & SSL_ST_ACCEPT) != 0)
+ role = "server";
+ else
+ role = "undefined";
+
+ if ((where & SSL_CB_LOOP) != 0)
+ {
+ MS_DEBUG_TAG(dtls, "[role:%s, action:'%s']", role, SSL_state_string_long(this->ssl));
+ }
+ else if ((where & SSL_CB_ALERT) != 0)
+ {
+ const char* alertType;
+
+ switch (*SSL_alert_type_string(ret))
+ {
+ case 'W':
+ alertType = "warning";
+ break;
+
+ case 'F':
+ alertType = "fatal";
+ break;
+
+ default:
+ alertType = "undefined";
+ }
+
+ if ((where & SSL_CB_READ) != 0)
+ {
+ MS_WARN_TAG(dtls, "received DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
+ }
+ else if ((where & SSL_CB_WRITE) != 0)
+ {
+ MS_DEBUG_TAG(dtls, "sending DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
+ }
+ else
+ {
+ MS_DEBUG_TAG(dtls, "DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
+ }
+ }
+ else if ((where & SSL_CB_EXIT) != 0)
+ {
+ if (ret == 0)
+ MS_DEBUG_TAG(dtls, "[role:%s, failed:'%s']", role, SSL_state_string_long(this->ssl));
+ else if (ret < 0)
+ MS_DEBUG_TAG(dtls, "role: %s, waiting:'%s']", role, SSL_state_string_long(this->ssl));
+ }
+ else if ((where & SSL_CB_HANDSHAKE_START) != 0)
+ {
+ MS_DEBUG_TAG(dtls, "DTLS handshake start");
+ }
+ else if ((where & SSL_CB_HANDSHAKE_DONE) != 0)
+ {
+ MS_DEBUG_TAG(dtls, "DTLS handshake done");
+
+ this->handshakeDoneNow = true;
+ }
+
+ // NOTE: checking SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN here upon
+ // receipt of a close alert does not work (the flag is set after this callback).
+ }
+
+ inline void DtlsTransport::OnTimer()
+ {
+ MS_TRACE();
+
+ // Workaround for https://github.com/openssl/openssl/issues/7998.
+ if (this->handshakeDone)
+ {
+ // MS_DEBUG_DEV("handshake is done so return");
+ return;
+ }
+
+ DTLSv1_handle_timeout(this->ssl);
+
+ // If required, send DTLS data.
+ SendPendingOutgoingDtlsData();
+
+ // Set the DTLS timer again.
+ SetTimeout();
+ }
+} // namespace RTC
diff --git a/webrtc/DtlsTransport.hpp b/webrtc/DtlsTransport.hpp
index 53a1981d..48198358 100644
--- a/webrtc/DtlsTransport.hpp
+++ b/webrtc/DtlsTransport.hpp
@@ -1,254 +1,254 @@
-/**
-ISC License
-
-Copyright © 2015, Iñaki Baz Castillo
-
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MS_RTC_DTLS_TRANSPORT_HPP
-#define MS_RTC_DTLS_TRANSPORT_HPP
-
-#include "SrtpSession.hpp"
-#include
-#include
-#include
-#include