Skip to content
Snippets Groups Projects
Commit 612b55b1 authored by Adrien Béraud's avatar Adrien Béraud
Browse files

add initial project structure

Change-Id: I6a3fb080ff623b312e42d71754480a7ce00b81a0
parent 6f7f531a
Branches
Tags
No related merge requests found
Showing
with 9027 additions and 0 deletions
cmake_minimum_required(VERSION 3.20)
project(dhtnet)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package (PkgConfig REQUIRED)
find_package(msgpack REQUIRED QUIET CONFIG NAMES msgpack msgpack-cxx)
pkg_check_modules (opendht REQUIRED IMPORTED_TARGET opendht>=2.6.0)
pkg_check_modules (pjproject REQUIRED IMPORTED_TARGET libpjproject)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMSGPACK_NO_BOOST -DMSGPACK_DISABLE_LEGACY_NIL -DMSGPACK_DISABLE_LEGACY_CONVERT")
# Sources
list (APPEND dhtnet_SOURCES
src/connectionmanager.cpp
src/ice_transport.cpp
src/multiplexed_socket.cpp
src/peer_connection.cpp
src/string_utils.cpp
src/fileutils.cpp
src/security/tls_session.cpp
src/security/certstore.cpp
src/security/threadloop.cpp
)
list (APPEND dhtnet_HEADERS
include/connectionmanager.h
include/multiplexed_socket.h
)
add_library(dhtnet ${dhtnet_SOURCES})
target_link_libraries(dhtnet PUBLIC PkgConfig::opendht msgpack-cxx)
target_include_directories(dhtnet PUBLIC include)
target_compile_definitions(dhtnet PRIVATE
PJ_AUTOCONF=1
)
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
//#include "security_const.h"
//#include "noncopyable.h"
#include <opendht/crypto.h>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <future>
#include <mutex>
namespace crypto = ::dht::crypto;
namespace dht {
namespace log {
class Logger;
}
}
namespace jami {
using Logger = dht::log::Logger;
namespace tls {
enum class TrustStatus { UNTRUSTED = 0, TRUSTED };
TrustStatus trustStatusFromStr(const char* str);
const char* statusToStr(TrustStatus s);
/**
* Global certificate store.
* Stores system root CAs and any other encountred certificate
*/
class CertificateStore
{
public:
explicit CertificateStore(const std::string& accountId, std::shared_ptr<Logger> logger);
std::vector<std::string> getPinnedCertificates() const;
/**
* Return certificate (with full chain)
*/
std::shared_ptr<crypto::Certificate> getCertificate(const std::string& cert_id);
std::shared_ptr<crypto::Certificate> getCertificateLegacy(const std::string& dataDir, const std::string& cert_id);
std::shared_ptr<crypto::Certificate> findCertificateByName(
const std::string& name, crypto::NameType type = crypto::NameType::UNKNOWN) const;
std::shared_ptr<crypto::Certificate> findCertificateByUID(const std::string& uid) const;
std::shared_ptr<crypto::Certificate> findIssuer(
const std::shared_ptr<crypto::Certificate>& crt) const;
std::vector<std::string> pinCertificate(const std::vector<uint8_t>& crt,
bool local = true) noexcept;
std::vector<std::string> pinCertificate(crypto::Certificate&& crt, bool local = true);
std::vector<std::string> pinCertificate(const std::shared_ptr<crypto::Certificate>& crt,
bool local = true);
bool unpinCertificate(const std::string&);
void pinCertificatePath(const std::string& path,
std::function<void(const std::vector<std::string>&)> cb = {});
unsigned unpinCertificatePath(const std::string&);
bool setTrustedCertificate(const std::string& id, TrustStatus status);
std::vector<gnutls_x509_crt_t> getTrustedCertificates() const;
void pinRevocationList(const std::string& id,
const std::shared_ptr<dht::crypto::RevocationList>& crl);
void pinRevocationList(const std::string& id, dht::crypto::RevocationList&& crl)
{
pinRevocationList(id,
std::make_shared<dht::crypto::RevocationList>(
std::forward<dht::crypto::RevocationList>(crl)));
}
void pinOcspResponse(const dht::crypto::Certificate& cert);
void loadRevocations(crypto::Certificate& crt) const;
const std::shared_ptr<Logger>& logger() const {
return logger_;
}
private:
//NON_COPYABLE(CertificateStore);
unsigned loadLocalCertificates();
void pinRevocationList(const std::string& id, const dht::crypto::RevocationList& crl);
std::shared_ptr<Logger> logger_;
const std::string certPath_;
const std::string crlPath_;
const std::string ocspPath_;
mutable std::mutex lock_;
std::map<std::string, std::shared_ptr<crypto::Certificate>> certs_;
std::map<std::string, std::vector<std::weak_ptr<crypto::Certificate>>> paths_;
// globally trusted certificates (root CAs)
std::vector<std::shared_ptr<crypto::Certificate>> trustedCerts_;
};
/**
* Keeps track of the allowed and trust status of certificates
* Trusted is the status of top certificates we trust to build our
* certificate chain: root CAs and other configured CAs.
*
* Allowed is the status of certificates we accept for incoming
* connections.
*/
class TrustStore
{
public:
explicit TrustStore(CertificateStore& certStore)
: certStore_(certStore)
{}
enum class PermissionStatus { UNDEFINED = 0, ALLOWED, BANNED };
static PermissionStatus statusFromStr(const char* str);
static const char* statusToStr(PermissionStatus s);
bool addRevocationList(dht::crypto::RevocationList&& crl);
bool setCertificateStatus(const std::string& cert_id, const PermissionStatus status);
bool setCertificateStatus(const std::shared_ptr<crypto::Certificate>& cert,
PermissionStatus status,
bool local = true);
PermissionStatus getCertificateStatus(const std::string& cert_id) const;
std::vector<std::string> getCertificatesByStatus(PermissionStatus status) const;
/**
* Check that the certificate is allowed (valid and permited) for contact.
* Valid means the certificate chain matches with our CA list,
* has valid signatures, expiration dates etc.
* Permited means at least one of the certificate in the chain is
* ALLOWED (if allowPublic is false), and none is BANNED.
*
* @param crt the end certificate of the chain to check
* @param allowPublic if false, requires at least one ALLOWED certificate.
* (not required otherwise). In any case a BANNED
* certificate means permission refusal.
* @return true if the certificate is valid and permitted.
*/
bool isAllowed(const crypto::Certificate& crt, bool allowPublic = false);
private:
TrustStore(const TrustStore& o) = delete;
TrustStore& operator=(const TrustStore& o) = delete;
TrustStore(TrustStore&& o) = delete;
TrustStore& operator=(TrustStore&& o) = delete;
void updateKnownCerts();
bool setCertificateStatus(std::shared_ptr<crypto::Certificate> cert,
const std::string& cert_id,
const TrustStore::PermissionStatus status,
bool local);
void setStoreCertStatus(const crypto::Certificate& crt, bool status);
void rebuildTrust();
struct Status
{
bool allowed;
};
// unknown certificates with known status
mutable std::recursive_mutex mutex_;
std::map<std::string, Status> unknownCertStatus_;
std::map<std::string, std::pair<std::shared_ptr<crypto::Certificate>, Status>> certStatus_;
dht::crypto::TrustList allowed_;
CertificateStore& certStore_;
};
} // namespace tls
} // namespace jami
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "ice_options.h"
#include "multiplexed_socket.h"
#include <opendht/dhtrunner.h>
#include <opendht/infohash.h>
#include <opendht/value.h>
#include <opendht/default_types.h>
#include <opendht/sockaddr.h>
#include <opendht/logger.h>
#include <memory>
#include <vector>
#include <string>
namespace jami {
class ChannelSocket;
class ConnectionManager;
namespace upnp {
class Controller;
}
namespace tls {
class CertificateStore;
}
/**
* A PeerConnectionRequest is a request which ask for an initial connection
* It contains the ICE request an ID and if it's an answer
* Transmitted via the UDP DHT
*/
struct PeerConnectionRequest : public dht::EncryptedValue<PeerConnectionRequest>
{
static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA;
static constexpr const char* key_prefix = "peer:"; ///< base to compute the DHT listen key
dht::Value::Id id = dht::Value::INVALID_ID;
std::string ice_msg {};
bool isAnswer {false};
std::string connType {}; // Used for push notifications to know why we open a new connection
MSGPACK_DEFINE_MAP(id, ice_msg, isAnswer, connType)
};
/**
* Used to accept or not an incoming ICE connection (default accept)
*/
using onICERequestCallback = std::function<bool(const DeviceId&)>;
/**
* Used to accept or decline an incoming channel request
*/
using ChannelRequestCallback = std::function<bool(const std::shared_ptr<dht::crypto::Certificate>&,
const std::string& /* name */)>;
/**
* Used by connectDevice, when the socket is ready
*/
using ConnectCallback = std::function<void(const std::shared_ptr<ChannelSocket>&, const DeviceId&)>;
/**
* Used when an incoming connection is ready
*/
using ConnectionReadyCallback = std::function<
void(const DeviceId&, const std::string& /* channel_name */, std::shared_ptr<ChannelSocket>)>;
using iOSConnectedCallback
= std::function<bool(const std::string& /* connType */, dht::InfoHash /* peer_h */)>;
/**
* Manages connections to other devices
* @note the account MUST be valid if ConnectionManager lives
*/
class ConnectionManager
{
public:
class Config;
ConnectionManager(std::shared_ptr<Config> config_);
~ConnectionManager();
/**
* Open a new channel between the account's device and another device
* This method will send a message on the account's DHT, wait a reply
* and then, create a Tls socket with remote peer.
* @param deviceId Remote device
* @param name Name of the channel
* @param cb Callback called when socket is ready ready
* @param noNewSocket Do not negotiate a new socket if there is none
* @param forceNewSocket Negotiate a new socket even if there is one // todo group with previous
* (enum)
* @param connType Type of the connection
*/
void connectDevice(const DeviceId& deviceId,
const std::string& name,
ConnectCallback cb,
bool noNewSocket = false,
bool forceNewSocket = false,
const std::string& connType = "");
void connectDevice(const std::shared_ptr<dht::crypto::Certificate>& cert,
const std::string& name,
ConnectCallback cb,
bool noNewSocket = false,
bool forceNewSocket = false,
const std::string& connType = "");
/**
* Check if we are already connecting to a device with a specific name
* @param deviceId Remote device
* @param name Name of the channel
* @return if connecting
* @note isConnecting is not true just after connectDevice() as connectDevice is full async
*/
bool isConnecting(const DeviceId& deviceId, const std::string& name) const;
/**
* Close all connections with a current device
* @param peerUri Peer URI
*/
void closeConnectionsWith(const std::string& peerUri);
/**
* Method to call to listen to incoming requests
* @param deviceId Account's device
*/
void onDhtConnected(const dht::crypto::PublicKey& devicePk);
/**
* Add a callback to decline or accept incoming ICE connections
* @param cb Callback to trigger
*/
void onICERequest(onICERequestCallback&& cb);
/**
* Trigger cb on incoming peer channel
* @param cb Callback to trigger
* @note The callback is used to validate
* if the incoming request is accepted or not.
*/
void onChannelRequest(ChannelRequestCallback&& cb);
/**
* Trigger cb when connection with peer is ready
* @param cb Callback to trigger
*/
void onConnectionReady(ConnectionReadyCallback&& cb);
/**
* Trigger cb when connection with peer is ready for iOS devices
* @param cb Callback to trigger
*/
void oniOSConnected(iOSConnectedCallback&& cb);
/**
* @return the number of active sockets
*/
std::size_t activeSockets() const;
/**
* Log informations for all sockets
*/
void monitor() const;
/**
* Send beacon on peers supporting it
*/
void connectivityChanged();
/**
* Create and return ICE options.
*/
void getIceOptions(std::function<void(IceTransportOptions&&)> cb) noexcept;
IceTransportOptions getIceOptions() const noexcept;
/**
* Get the published IP address, fallbacks to NAT if family is unspecified
* Prefers the usage of IPv4 if possible.
*/
IpAddr getPublishedIpAddress(uint16_t family = PF_UNSPEC) const;
/**
* Set published IP address according to given family
*/
void setPublishedAddress(const IpAddr& ip_addr);
/**
* Store the local/public addresses used to register
*/
void storeActiveIpAddress(std::function<void()>&& cb = {});
std::shared_ptr<Config> getConfig();
private:
ConnectionManager() = delete;
class Impl;
std::shared_ptr<Impl> pimpl_;
};
struct ConnectionManager::Config
{
/**
* Determine if STUN public address resolution is required to register this account. In this
* case a STUN server hostname must be specified.
*/
bool stunEnabled {false};
/**
* The STUN server hostname (optional), used to provide the public IP address in case the
* softphone stay behind a NAT.
*/
std::string stunServer {};
/**
* Determine if TURN public address resolution is required to register this account. In this
* case a TURN server hostname must be specified.
*/
bool turnEnabled {false};
/**
* The TURN server hostname (optional), used to provide the public IP address in case the
* softphone stay behind a NAT.
*/
std::string turnServer;
std::string turnServerUserName;
std::string turnServerPwd;
std::string turnServerRealm;
mutable std::mutex cachedTurnMutex {};
dht::SockAddr cacheTurnV4 {};
dht::SockAddr cacheTurnV6 {};
std::string cachePath {};
std::shared_ptr<asio::io_context> ioContext;
std::shared_ptr<dht::DhtRunner> dht;
dht::crypto::Identity id;
tls::CertificateStore* certStore;
/**
* UPnP IGD controller and the mutex to access it
*/
bool upnpEnabled;
std::shared_ptr<jami::upnp::Controller> upnpCtrl;
std::shared_ptr<dht::log::Logger> logger;
/**
* returns whether or not UPnP is enabled and active
* ie: if it is able to make port mappings
*/
bool getUPnPActive() const;
};
} // namespace jami
\ No newline at end of file
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <gnutls/gnutls.h>
#include <vector>
#include <memory>
#include <cstdint>
#include <string>
namespace jami {
namespace tls {
class DhParams
{
public:
DhParams() = default;
DhParams(DhParams&&) = default;
DhParams(const DhParams& other) { *this = other; }
DhParams& operator=(DhParams&& other) = default;
DhParams& operator=(const DhParams& other);
/// \brief Construct by taking ownership of given gnutls DH params
///
/// User should not call gnutls_dh_params_deinit on given \a raw_params.
/// The object is stolen and its live is manager by our object.
explicit DhParams(gnutls_dh_params_t p)
: params_ {p, gnutls_dh_params_deinit}
{}
/** Deserialize DER or PEM encoded DH-params */
DhParams(const std::vector<uint8_t>& data);
gnutls_dh_params_t get() { return params_.get(); }
gnutls_dh_params_t get() const { return params_.get(); }
explicit inline operator bool() const { return bool(params_); }
/** Serialize data in PEM format */
std::vector<uint8_t> serialize() const;
static DhParams generate();
static DhParams loadDhParams(const std::string& path);
private:
std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)*>
params_ {nullptr, gnutls_dh_params_deinit};
};
} // namespace tls
} // namespace jami
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* Author: Rafaël Carré <rafael.carre@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <string>
#include <vector>
#include <chrono>
#include <mutex>
#include <cstdio>
#include <ios>
#ifndef _WIN32
#include <sys/stat.h> // mode_t
#define DIR_SEPARATOR_STR "/" // Directory separator string
#define DIR_SEPARATOR_CH '/' // Directory separator char
#define DIR_SEPARATOR_STR_ESC "\\/" // Escaped directory separator string
#else
#define mode_t unsigned
#define DIR_SEPARATOR_STR "\\" // Directory separator string
#define DIR_SEPARATOR_CH '\\' // Directory separator char
#define DIR_SEPARATOR_STR_ESC "//*" // Escaped directory separator string
#endif
namespace jami {
namespace fileutils {
/**
* Check directory existence and create it with given mode if it doesn't.
* @param path to check, relative or absolute
* @param dir last directory creation mode
* @param parents default mode for all created directories except the last
*/
bool check_dir(const char* path, mode_t dir = 0755, mode_t parents = 0755);
/*std::string expand_path(const std::string& path);*/
bool isDirectoryWritable(const std::string& directory);
bool recursive_mkdir(const std::string& path, mode_t mode = 0755);
bool isPathRelative(const std::string& path);
/**
* If path is contained in base, return the suffix, otherwise return the full path.
* @param base must not finish with DIR_SEPARATOR_STR, can be empty
* @param path the path
*/
//std::string getCleanPath(const std::string& base, const std::string& path);
/**
* If path is relative, it is appended to base.
*/
//std::string getFullPath(const std::string& base, const std::string& path);
bool isFile(const std::string& path, bool resolveSymlink = true);
bool isDirectory(const std::string& path);
bool isSymLink(const std::string& path);
bool hasHardLink(const std::string& path);
std::chrono::system_clock::time_point writeTime(const std::string& path);
/*void createFileLink(const std::string& src, const std::string& dest, bool hard = false);
std::string_view getFileExtension(std::string_view filename);*/
/**
* Read content of the directory.
* The result is a list of relative (to @param dir) paths of all entries
* in the directory, without "." and "..".
*/
std::vector<std::string> readDirectory(const std::string& dir);
/**
* Read the full content of a file at path.
* If path is relative, it is appended to default_dir.
*/
std::vector<uint8_t> loadFile(const std::string& path, const std::string& default_dir = {});
std::string loadTextFile(const std::string& path, const std::string& default_dir = {});
void saveFile(const std::string& path, const uint8_t* data, size_t data_size, mode_t mode = 0644);
inline void
saveFile(const std::string& path, const std::vector<uint8_t>& data, mode_t mode = 0644)
{
saveFile(path, data.data(), data.size(), mode);
}
/*std::vector<uint8_t> loadCacheFile(const std::string& path,
std::chrono::system_clock::duration maxAge);
std::string loadCacheTextFile(const std::string& path, std::chrono::system_clock::duration maxAge);
std::vector<uint8_t> readArchive(const std::string& path, const std::string& password = {});
void writeArchive(const std::string& data,
const std::string& path,
const std::string& password = {});*/
std::mutex& getFileLock(const std::string& path);
/**
* Remove a file with optional erasing of content.
* Return the same value as std::remove().
*/
//int remove(const std::string& path, bool erase = false);
/**
* Prune given directory's content and remove it, symlinks are not followed.
* Return 0 if succeed, -1 if directory is not removed (content can be removed partially).
*/
int removeAll(const std::string& path, bool erase = false);
/**
* Wrappers for fstream opening that will convert paths to wstring
* on windows
*/
void openStream(std::ifstream& file,
const std::string& path,
std::ios_base::openmode mode = std::ios_base::in);
void openStream(std::ofstream& file,
const std::string& path,
std::ios_base::openmode mode = std::ios_base::out);
std::ifstream ifstream(const std::string& path, std::ios_base::openmode mode = std::ios_base::in);
std::ofstream ofstream(const std::string& path, std::ios_base::openmode mode = std::ios_base::out);
int64_t size(const std::string& path);
std::string sha3File(const std::string& path);
std::string sha3sum(const std::vector<uint8_t>& buffer);
/**
* Windows compatibility wrapper for checking read-only attribute
*/
int accessFile(const std::string& file, int mode);
uint64_t lastWriteTime(const std::string& p);
} // namespace fileutils
} // namespace jami
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "ip_utils.h"
#include <functional>
#include <vector>
#include <chrono>
#include <system_error>
#include <cstdint>
#if defined(_MSC_VER)
#include <BaseTsd.h>
using ssize_t = SSIZE_T;
#endif
namespace jami {
template<typename T>
class GenericSocket
{
public:
using ValueType = T;
virtual ~GenericSocket() { shutdown(); }
using RecvCb = std::function<ssize_t(const ValueType* buf, std::size_t len)>;
/// Close established connection
/// \note Terminate outstanding blocking read operations with an empty error code, but a 0 read size.
virtual void shutdown() {}
/// Set Rx callback
/// \warning This method is here for backward compatibility
/// and because async IO are not implemented yet.
virtual void setOnRecv(RecvCb&& cb) = 0;
virtual bool isReliable() const = 0;
virtual bool isInitiator() const = 0;
/// Return maximum application payload size.
/// This value is negative if the session is not ready to give a valid answer.
/// The value is 0 if such information is irrelevant for the session.
/// If stricly positive, the user must use send() with an input buffer size below or equals
/// to this value if it want to be sure that the transport sent it in an atomic way.
/// Example: in case of non-reliable transport using packet oriented IO,
/// this value gives the maximal size used to send one packet.
virtual int maxPayload() const = 0;
/// Wait until data to read available, timeout or io error
/// \param ec error code set in case of error (if return value is < 0)
/// \return positive number if data ready for read, 0 in case of timeout or error.
/// \note error code is not set in case of timeout, but set only in case of io error
/// (i.e. socket deconnection).
/// \todo make a std::chrono version for the timeout
virtual int waitForData(std::chrono::milliseconds timeout, std::error_code& ec) const = 0;
/// Write a given amount of data.
/// \param buf data to write.
/// \param len number of bytes to write.
/// \param ec error code set in case of error.
/// \return number of bytes written, 0 is valid.
/// \warning error checking consists in checking if \a !ec is true, not if returned size is 0
/// as a write of 0 could be considered a valid operation.
virtual std::size_t write(const ValueType* buf, std::size_t len, std::error_code& ec) = 0;
/// Read a given amount of data.
/// \param buf data to read.
/// \param len number of bytes to read.
/// \param ec error code set in case of error.
/// \return number of bytes read, 0 is valid.
/// \warning error checking consists in checking if \a !ec is true, not if returned size is 0
/// as a read of 0 could be considered a valid operation (i.e. non-blocking IO).
virtual std::size_t read(ValueType* buf, std::size_t len, std::error_code& ec) = 0;
/// write() adaptor for STL containers
template<typename U>
std::size_t write(const U& obj, std::error_code& ec)
{
return write(obj.data(), obj.size() * sizeof(typename U::value_type), ec);
}
/// read() adaptor for STL containers
template<typename U>
std::size_t read(U& storage, std::error_code& ec)
{
auto res = read(storage.data(), storage.size() * sizeof(typename U::value_type), ec);
if (!ec)
storage.resize(res);
return res;
}
/// Return the local IP address if known.
/// \note The address is not valid (addr.isUnspecified() returns true) if it's not known
/// or not available.
virtual IpAddr localAddr() const { return {}; }
/// Return the remote IP address if known.
/// \note The address is not valid (addr.isUnspecified() returns true) if it's not known
/// or not available.
virtual IpAddr remoteAddr() const { return {}; }
protected:
GenericSocket() = default;
};
} // namespace jami
#pragma once
#include <functional>
#include <vector>
#include <string>
#include "ip_utils.h"
namespace jami {
class IceTransportFactory;
using IceTransportCompleteCb = std::function<void(bool)>;
struct StunServerInfo
{
inline StunServerInfo& setUri(const std::string& args) {
uri = args;
return *this;
}
std::string uri; // server URI, mandatory
};
struct TurnServerInfo
{
inline TurnServerInfo& setUri(const std::string& args) {
uri = args;
return *this;
}
inline TurnServerInfo& setUsername(const std::string& args) {
username = args;
return *this;
}
inline TurnServerInfo& setPassword(const std::string& args) {
password = args;
return *this;
}
inline TurnServerInfo& setRealm(const std::string& args) {
realm = args;
return *this;
}
std::string uri; // server URI, mandatory
std::string username; // credentials username (optional, empty if not used)
std::string password; // credentials password (optional, empty if not used)
std::string realm; // credentials realm (optional, empty if not used)
};
struct IceTransportOptions
{
IceTransportFactory* factory {nullptr};
bool master {true};
unsigned streamsCount {1};
unsigned compCountPerStream {1};
bool upnpEnable {false};
IceTransportCompleteCb onInitDone {};
IceTransportCompleteCb onNegoDone {};
std::vector<StunServerInfo> stunServers;
std::vector<TurnServerInfo> turnServers;
bool tcpEnable {false};
// Addresses used by the account owning the transport instance.
IpAddr accountLocalAddr {};
IpAddr accountPublicAddr {};
};
}
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#ifdef HAVE_CONFIG
#include <config.h>
#endif
#include <sstream> // include before pjlib.h to fix macros issues with pjlib.h
extern "C" {
#include <pjlib.h>
}
#include <ciso646> // fix windows compiler bug
#ifdef _WIN32
#ifdef RING_UWP
#define _WIN32_WINNT 0x0A00
#else
#define _WIN32_WINNT 0x0601
#endif
#include <ws2tcpip.h>
// define in mingw
#ifdef interface
#undef interface
#endif
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <unistd.h>
#endif
#include <string>
#include <vector>
/* An IPv4 equivalent to IN6_IS_ADDR_UNSPECIFIED */
#ifndef IN_IS_ADDR_UNSPECIFIED
#define IN_IS_ADDR_UNSPECIFIED(a) (((long int) (a)->s_addr) == 0x00000000)
#endif /* IN_IS_ADDR_UNSPECIFIED */
#define INVALID_SOCKET (-1)
namespace jami {
/**
* Binary representation of an IP address.
*/
class IpAddr
{
public:
IpAddr()
: IpAddr(AF_UNSPEC)
{}
IpAddr(const IpAddr&) = default;
IpAddr(IpAddr&&) = default;
IpAddr& operator=(const IpAddr&) = default;
IpAddr& operator=(IpAddr&&) = default;
explicit IpAddr(uint16_t family)
: addr()
{
addr.addr.sa_family = family;
}
IpAddr(const pj_sockaddr& ip)
: addr(ip)
{}
IpAddr(const pj_sockaddr& ip, socklen_t len)
: addr()
{
if (len > static_cast<socklen_t>(sizeof(addr)))
throw std::invalid_argument("IpAddr(): length overflows internal storage type");
memcpy(&addr, &ip, len);
}
IpAddr(const sockaddr& ip)
: addr()
{
memcpy(&addr, &ip, ip.sa_family == AF_INET6 ? sizeof addr.ipv6 : sizeof addr.ipv4);
}
IpAddr(const sockaddr_in& ip)
: addr()
{
static_assert(sizeof(ip) <= sizeof(addr), "sizeof(sockaddr_in) too large");
memcpy(&addr, &ip, sizeof(sockaddr_in));
}
IpAddr(const sockaddr_in6& ip)
: addr()
{
static_assert(sizeof(ip) <= sizeof(addr), "sizeof(sockaddr_in6) too large");
memcpy(&addr, &ip, sizeof(sockaddr_in6));
}
IpAddr(const sockaddr_storage& ip)
: IpAddr(*reinterpret_cast<const sockaddr*>(&ip))
{}
IpAddr(const in_addr& ip)
: addr()
{
static_assert(sizeof(ip) <= sizeof(addr), "sizeof(in_addr) too large");
addr.addr.sa_family = AF_INET;
memcpy(&addr.ipv4.sin_addr, &ip, sizeof(in_addr));
}
IpAddr(const in6_addr& ip)
: addr()
{
static_assert(sizeof(ip) <= sizeof(addr), "sizeof(in6_addr) too large");
addr.addr.sa_family = AF_INET6;
memcpy(&addr.ipv6.sin6_addr, &ip, sizeof(in6_addr));
}
IpAddr(std::string_view str, pj_uint16_t family = AF_UNSPEC)
: addr()
{
if (str.empty()) {
addr.addr.sa_family = AF_UNSPEC;
return;
}
const pj_str_t pjstring {(char*) str.data(), (pj_ssize_t) str.size()};
auto status = pj_sockaddr_parse(family, 0, &pjstring, &addr);
if (status != PJ_SUCCESS)
addr.addr.sa_family = AF_UNSPEC;
}
// Is defined
inline explicit operator bool() const { return isIpv4() or isIpv6(); }
inline explicit operator bool() { return isIpv4() or isIpv6(); }
inline operator pj_sockaddr&() { return addr; }
inline operator const pj_sockaddr&() const { return addr; }
inline operator pj_sockaddr_in&() { return addr.ipv4; }
inline operator const pj_sockaddr_in&() const
{
assert(addr.addr.sa_family != AF_INET6);
return addr.ipv4;
}
inline operator pj_sockaddr_in6&() { return addr.ipv6; }
inline operator const pj_sockaddr_in6&() const
{
assert(addr.addr.sa_family == AF_INET6);
return addr.ipv6;
}
inline operator const sockaddr&() const { return reinterpret_cast<const sockaddr&>(addr); }
inline operator const sockaddr*() const { return reinterpret_cast<const sockaddr*>(&addr); }
inline const pj_sockaddr* pjPtr() const { return &addr; }
inline pj_sockaddr* pjPtr() { return &addr; }
inline operator std::string() const { return toString(); }
std::string toString(bool include_port = false, bool force_ipv6_brackets = false) const
{
if (addr.addr.sa_family == AF_UNSPEC)
return {};
std::string str(PJ_INET6_ADDRSTRLEN, (char) 0);
if (include_port)
force_ipv6_brackets = true;
pj_sockaddr_print(&addr,
&(*str.begin()),
PJ_INET6_ADDRSTRLEN,
(include_port ? 1 : 0) | (force_ipv6_brackets ? 2 : 0));
str.resize(std::char_traits<char>::length(str.c_str()));
return str;
}
void setPort(uint16_t port) { pj_sockaddr_set_port(&addr, port); }
inline uint16_t getPort() const
{
if (not *this)
return 0;
return pj_sockaddr_get_port(&addr);
}
inline socklen_t getLength() const
{
if (not *this)
return 0;
return pj_sockaddr_get_len(&addr);
}
inline uint16_t getFamily() const { return addr.addr.sa_family; }
inline bool isIpv4() const { return addr.addr.sa_family == AF_INET; }
inline bool isIpv6() const { return addr.addr.sa_family == AF_INET6; }
/**
* Return true if address is a loopback IP address.
*/
bool isLoopback() const;
/**
* Return true if address is not a public IP address.
*/
bool isPrivate() const;
bool isUnspecified() const;
/**
* Return true if address is a valid IPv6.
*/
inline static bool isIpv6(std::string_view address) { return isValid(address, AF_INET6); }
/**
* Return true if address is a valid IP address of specified family (if provided) or of any kind
* (default). Does not resolve hostnames.
*/
static bool isValid(std::string_view address, pj_uint16_t family = pj_AF_UNSPEC());
private:
pj_sockaddr addr {};
};
// IpAddr helpers
inline bool
operator==(const IpAddr& lhs, const IpAddr& rhs)
{
return !pj_sockaddr_cmp(&lhs, &rhs);
}
inline bool
operator!=(const IpAddr& lhs, const IpAddr& rhs)
{
return !(lhs == rhs);
}
inline bool
operator<(const IpAddr& lhs, const IpAddr& rhs)
{
return pj_sockaddr_cmp(&lhs, &rhs) < 0;
}
inline bool
operator>(const IpAddr& lhs, const IpAddr& rhs)
{
return pj_sockaddr_cmp(&lhs, &rhs) > 0;
}
inline bool
operator<=(const IpAddr& lhs, const IpAddr& rhs)
{
return pj_sockaddr_cmp(&lhs, &rhs) <= 0;
}
inline bool
operator>=(const IpAddr& lhs, const IpAddr& rhs)
{
return pj_sockaddr_cmp(&lhs, &rhs) >= 0;
}
namespace ip_utils {
static const char* const DEFAULT_INTERFACE = "default";
static const unsigned int MAX_INTERFACE = 256;
static const unsigned int MIN_INTERFACE = 1;
enum class subnet_mask { prefix_8bit, prefix_16bit, prefix_24bit, prefix_32bit };
std::string getHostname();
int getHostName(char* out, size_t out_len);
std::string getGateway(char* localHost, ip_utils::subnet_mask prefix);
IpAddr getLocalGateway();
/**
* Return the generic "any host" IP address of the specified family.
* If family is unspecified, default to pj_AF_INET6() (IPv6).
*/
inline IpAddr
getAnyHostAddr(pj_uint16_t family)
{
return IpAddr(family);
}
/**
* Return the first host IP address of the specified family.
* If no address of the specified family is found, another family will
* be tried.
* Ex. : if family is pj_AF_INET6() (IPv6/default) and the system does not
* have an IPv6 address, an IPv4 address will be returned if available.
*
* If family is unspecified, default to pj_AF_INET6() if compiled
* with IPv6, or pj_AF_INET() otherwise.
*/
IpAddr getLocalAddr(pj_uint16_t family);
/**
* Get the IP address of the network interface interface with the specified
* address family, or of any address family if unspecified (default).
*/
IpAddr getInterfaceAddr(const std::string& interface, pj_uint16_t family);
/**
* List all the interfaces on the system and return
* a vector list containing their name (eth0, eth0:1 ...).
* @param void
* @return std::vector<std::string> A std::string vector
* of interface name available on all of the interfaces on
* the system.
*/
std::vector<std::string> getAllIpInterfaceByName();
/**
* List all the interfaces on the system and return
* a vector list containing their IP address.
* @param void
* @return std::vector<std::string> A std::string vector
* of IP address available on all of the interfaces on
* the system.
*/
std::vector<std::string> getAllIpInterface();
std::vector<IpAddr> getAddrList(std::string_view name, pj_uint16_t family = pj_AF_UNSPEC());
bool haveCommonAddr(const std::vector<IpAddr>& a, const std::vector<IpAddr>& b);
std::vector<IpAddr> getLocalNameservers();
} // namespace ip_utils
} // namespace jami
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "ip_utils.h"
#include "generic_io.h"
#include <opendht/default_types.h>
#include <condition_variable>
#include <cstdint>
namespace asio {
class io_context;
}
namespace dht {
namespace log {
class Logger;
}
}
namespace jami {
using Logger = dht::log::Logger;
class IceTransport;
class ChannelSocket;
class TlsSocketEndpoint;
using DeviceId = dht::PkId;
using OnConnectionRequestCb
= std::function<bool(const std::shared_ptr<dht::crypto::Certificate>& /* peer */,
const uint16_t& /* id */,
const std::string& /* name */)>;
using OnConnectionReadyCb
= std::function<void(const DeviceId& /* deviceId */, const std::shared_ptr<ChannelSocket>&)>;
using ChannelReadyCb = std::function<void(void)>;
using OnShutdownCb = std::function<void(void)>;
static constexpr auto SEND_BEACON_TIMEOUT = std::chrono::milliseconds(3000);
static constexpr uint16_t CONTROL_CHANNEL {0};
static constexpr uint16_t PROTOCOL_CHANNEL {0xffff};
enum class ChannelRequestState {
REQUEST,
ACCEPT,
DECLINE,
};
/**
* That msgpack structure is used to request a new channel (id, name)
* Transmitted over the TLS socket
*/
struct ChannelRequest
{
std::string name {};
uint16_t channel {0};
ChannelRequestState state {ChannelRequestState::REQUEST};
MSGPACK_DEFINE(name, channel, state)
};
/**
* A socket divided in channels over a TLS session
*/
class MultiplexedSocket : public std::enable_shared_from_this<MultiplexedSocket>
{
public:
MultiplexedSocket(std::shared_ptr<asio::io_context> ctx, const DeviceId& deviceId, std::unique_ptr<TlsSocketEndpoint> endpoint);
~MultiplexedSocket();
std::shared_ptr<ChannelSocket> addChannel(const std::string& name);
std::shared_ptr<MultiplexedSocket> shared()
{
return std::static_pointer_cast<MultiplexedSocket>(shared_from_this());
}
std::shared_ptr<MultiplexedSocket const> shared() const
{
return std::static_pointer_cast<MultiplexedSocket const>(shared_from_this());
}
std::weak_ptr<MultiplexedSocket> weak()
{
return std::static_pointer_cast<MultiplexedSocket>(shared_from_this());
}
std::weak_ptr<MultiplexedSocket const> weak() const
{
return std::static_pointer_cast<MultiplexedSocket const>(shared_from_this());
}
DeviceId deviceId() const;
bool isReliable() const;
bool isInitiator() const;
int maxPayload() const;
/**
* Will be triggered when a new channel is ready
*/
void setOnReady(OnConnectionReadyCb&& cb);
/**
* Will be triggered when the peer asks for a new channel
*/
void setOnRequest(OnConnectionRequestCb&& cb);
std::size_t write(const uint16_t& channel,
const uint8_t* buf,
std::size_t len,
std::error_code& ec);
/**
* This will close all channels and send a TLS EOF on the main socket.
*/
void shutdown();
/**
* This will wait that eventLoop is stopped and stop it if necessary
*/
void join();
/**
* Will trigger that callback when shutdown() is called
*/
void onShutdown(OnShutdownCb&& cb);
/**
* Get informations from socket (channels opened)
*/
void monitor() const;
const std::shared_ptr<Logger>& logger();
/**
* Send a beacon on the socket and close if no response come
* @param timeout
*/
void sendBeacon(const std::chrono::milliseconds& timeout = SEND_BEACON_TIMEOUT);
/**
* Get peer's certificate
*/
std::shared_ptr<dht::crypto::Certificate> peerCertificate() const;
IpAddr getLocalAddress() const;
IpAddr getRemoteAddress() const;
void eraseChannel(uint16_t channel);
#ifdef LIBJAMI_TESTABLE
/**
* Check if we can send beacon on the socket
*/
bool canSendBeacon() const;
/**
* Decide if yes or not we answer to beacon
* @param value New value
*/
void answerToBeacon(bool value);
/**
* Change version sent to the peer
*/
void setVersion(int version);
/**
* Set a callback to detect beacon messages
*/
void setOnBeaconCb(const std::function<void(bool)>& cb);
/**
* Set a callback to detect version messages
*/
void setOnVersionCb(const std::function<void(int)>& cb);
/**
* Send the version
*/
void sendVersion();
#endif
private:
class Impl;
std::unique_ptr<Impl> pimpl_;
};
class ChannelSocketInterface : public GenericSocket<uint8_t>
{
public:
using SocketType = GenericSocket<uint8_t>;
virtual DeviceId deviceId() const = 0;
virtual std::string name() const = 0;
virtual uint16_t channel() const = 0;
/**
* Triggered when a specific channel is ready
* Used by ConnectionManager::connectDevice()
*/
virtual void onReady(ChannelReadyCb&& cb) = 0;
/**
* Will trigger that callback when shutdown() is called
*/
virtual void onShutdown(OnShutdownCb&& cb) = 0;
virtual void onRecv(std::vector<uint8_t>&& pkt) = 0;
};
class ChannelSocketTest : public ChannelSocketInterface
{
public:
ChannelSocketTest(std::shared_ptr<asio::io_context> ctx, const DeviceId& deviceId, const std::string& name, const uint16_t& channel);
~ChannelSocketTest();
static void link(const std::shared_ptr<ChannelSocketTest>& socket1,
const std::shared_ptr<ChannelSocketTest>& socket2);
DeviceId deviceId() const override;
std::string name() const override;
uint16_t channel() const override;
bool isReliable() const override { return true; };
bool isInitiator() const override { return true; };
int maxPayload() const override { return 0; };
void shutdown() override;
std::size_t read(ValueType* buf, std::size_t len, std::error_code& ec) override;
std::size_t write(const ValueType* buf, std::size_t len, std::error_code& ec) override;
int waitForData(std::chrono::milliseconds timeout, std::error_code&) const override;
void setOnRecv(RecvCb&&) override;
void onRecv(std::vector<uint8_t>&& pkt) override;
/**
* Triggered when a specific channel is ready
* Used by ConnectionManager::connectDevice()
*/
void onReady(ChannelReadyCb&& cb) override;
/**
* Will trigger that callback when shutdown() is called
*/
void onShutdown(OnShutdownCb&& cb) override;
std::vector<uint8_t> rx_buf {};
mutable std::mutex mutex {};
mutable std::condition_variable cv {};
GenericSocket<uint8_t>::RecvCb cb {};
private:
const DeviceId pimpl_deviceId;
const std::string pimpl_name;
const uint16_t pimpl_channel;
asio::io_context& ioCtx_;
std::weak_ptr<ChannelSocketTest> remote;
OnShutdownCb shutdownCb_ {[&] {
}};
std::atomic_bool isShutdown_ {false};
};
/**
* Represents a channel of the multiplexed socket (channel, name)
*/
class ChannelSocket : public ChannelSocketInterface
{
public:
ChannelSocket(std::weak_ptr<MultiplexedSocket> endpoint,
const std::string& name,
const uint16_t& channel,
bool isInitiator = false,
std::function<void()> rmFromMxSockCb = {});
~ChannelSocket();
DeviceId deviceId() const override;
std::string name() const override;
uint16_t channel() const override;
bool isReliable() const override;
bool isInitiator() const override;
int maxPayload() const override;
/**
* Like shutdown, but don't send any packet on the socket.
* Used by Multiplexed Socket when the TLS endpoint is already shutting down
*/
void stop();
/**
* This will send an empty buffer as a packet (equivalent to EOF)
* Will trigger onShutdown's callback
*/
void shutdown() override;
void ready();
/**
* Triggered when a specific channel is ready
* Used by ConnectionManager::connectDevice()
*/
void onReady(ChannelReadyCb&& cb) override;
/**
* Will trigger that callback when shutdown() is called
*/
void onShutdown(OnShutdownCb&& cb) override;
std::size_t read(ValueType* buf, std::size_t len, std::error_code& ec) override;
/**
* @note len should be < UINT8_MAX, else you will get ec = EMSGSIZE
*/
std::size_t write(const ValueType* buf, std::size_t len, std::error_code& ec) override;
int waitForData(std::chrono::milliseconds timeout, std::error_code&) const override;
/**
* set a callback when receiving data
* @note: this callback should take a little time and not block
* but you can move it in a thread
*/
void setOnRecv(RecvCb&&) override;
void onRecv(std::vector<uint8_t>&& pkt) override;
/**
* Send a beacon on the socket and close if no response come
* @param timeout
*/
void sendBeacon(const std::chrono::milliseconds& timeout = SEND_BEACON_TIMEOUT);
/**
* Get peer's certificate
*/
std::shared_ptr<dht::crypto::Certificate> peerCertificate() const;
#ifdef LIBJAMI_TESTABLE
std::shared_ptr<MultiplexedSocket> underlyingSocket() const;
#endif
// Note: When a channel is accepted, it can receives data ASAP and when finished will be removed
// however, onAccept is it's own thread due to the callbacks. In this case, the channel must be
// deleted in the onAccept.
void answered();
bool isAnswered() const;
void removable();
bool isRemovable() const;
IpAddr getLocalAddress() const;
IpAddr getRemoteAddress() const;
private:
class Impl;
std::unique_ptr<Impl> pimpl_;
};
} // namespace jami
MSGPACK_ADD_ENUM(jami::ChannelRequestState);
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com>
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
#include <regex>
#include <iterator>
#include <charconv>
#ifdef _WIN32
#include <WTypes.h>
#endif
namespace jami {
constexpr static const char TRUE_STR[] = "true";
constexpr static const char FALSE_STR[] = "false";
constexpr static const char*
bool_to_str(bool b) noexcept
{
return b ? TRUE_STR : FALSE_STR;
}
std::string to_string(double value);
#ifdef _WIN32
std::wstring to_wstring(const std::string& str, int codePage = CP_UTF8);
std::string to_string(const std::wstring& wstr, int codePage = CP_UTF8);
#endif
std::string to_hex_string(uint64_t id);
uint64_t from_hex_string(const std::string& str);
template<typename T>
T
to_int(std::string_view str, T defaultValue)
{
T result;
auto [p, ec] = std::from_chars(str.data(), str.data()+str.size(), result);
if (ec == std::errc())
return result;
else
return defaultValue;
}
template<typename T>
T
to_int(std::string_view str)
{
T result;
auto [p, ec] = std::from_chars(str.data(), str.data()+str.size(), result);
if (ec == std::errc())
return result;
if (ec == std::errc::invalid_argument)
throw std::invalid_argument("Can't parse integer: invalid_argument");
else if (ec == std::errc::result_out_of_range)
throw std::out_of_range("Can't parse integer: out of range");
throw std::system_error(std::make_error_code(ec));
}
static inline int
stoi(const std::string& str)
{
return std::stoi(str);
}
static inline double
stod(const std::string& str)
{
return std::stod(str);
}
template<typename... Args>
std::string concat(Args &&... args){
static_assert((std::is_constructible_v<std::string_view, Args&&> && ...));
std::string s;
s.reserve((std::string_view{ args }.size() + ...));
(s.append(std::forward<Args>(args)), ...);
return s;
}
std::string_view trim(std::string_view s);
/**
* Split a string_view with an API similar to std::getline.
* @param str The input string stream to iterate on, trimed of line during iteration.
* @param line The output substring.
* @param delim The delimiter.
* @return True if line was set, false if the end of the input was reached.
*/
inline bool
getline_full(std::string_view& str, std::string_view& line, char delim = '\n')
{
if (str.empty())
return false;
auto pos = str.find(delim);
line = str.substr(0, pos);
str.remove_prefix(pos < str.size() ? pos + 1 : str.size());
return true;
}
/**
* Similar to @getline_full but skips empty results.
*/
inline bool
getline(std::string_view& str, std::string_view& line, char delim = '\n')
{
do {
if (!getline_full(str, line, delim))
return false;
} while (line.empty());
return true;
}
inline std::vector<std::string_view>
split_string(std::string_view str, char delim)
{
std::vector<std::string_view> output;
for (auto first = str.data(), second = str.data(), last = first + str.size();
second != last && first != last;
first = second + 1) {
second = std::find(first, last, delim);
if (first != second)
output.emplace_back(first, second - first);
}
return output;
}
inline std::vector<std::string_view>
split_string(std::string_view str, std::string_view delims = " ")
{
std::vector<std::string_view> output;
for (auto first = str.data(), second = str.data(), last = first + str.size();
second != last && first != last;
first = second + 1) {
second = std::find_first_of(first, last, std::cbegin(delims), std::cend(delims));
if (first != second)
output.emplace_back(first, second - first);
}
return output;
}
std::vector<unsigned> split_string_to_unsigned(std::string_view s, char sep);
void string_replace(std::string& str, const std::string& from, const std::string& to);
std::string_view string_remove_suffix(std::string_view str, char separator);
std::string string_join(const std::set<std::string>& set, std::string_view separator = "/");
std::set<std::string> string_split_set(std::string& str, std::string_view separator = "/");
} // namespace jami
/*
// Add string operators missing from standard
// see https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/1RcShRhrmRc
namespace std {
inline string
operator+(const string& s, const string_view& sv)
{
return jami::concat(s, sv);
}
inline string
operator+(const string_view& sv, const string& s)
{
return jami::concat(sv, s);
}
using svmatch = match_results<string_view::const_iterator>;
using svsub_match = sub_match<string_view::const_iterator>;
constexpr string_view svsub_match_view(const svsub_match& submatch) noexcept {
return string_view(&*submatch.first, submatch.second - submatch.first);
}
inline bool
regex_match(string_view sv,
svmatch& m,
const regex& e,
regex_constants::match_flag_type flags = regex_constants::match_default)
{
return regex_match(sv.begin(), sv.end(), m, e, flags);
}
inline bool
regex_match(string_view sv,
const regex& e,
regex_constants::match_flag_type flags = regex_constants::match_default)
{
return regex_match(sv.begin(), sv.end(), e, flags);
}
inline bool
regex_search(string_view sv,
svmatch& m,
const regex& e,
regex_constants::match_flag_type flags = regex_constants::match_default)
{
return regex_search(sv.begin(), sv.end(), m, e, flags);
}
} // namespace std
*/
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
* Author: Vsevolod Ivanov <vsevolod.ivanov@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
//#include "noncopyable.h"
#include "generic_io.h"
#include "certstore.h"
#include "diffie-hellman.h"
#include <gnutls/gnutls.h>
#include <asio/io_context.hpp>
#include <string>
#include <functional>
#include <memory>
#include <future>
#include <chrono>
#include <vector>
#include <array>
namespace dht {
namespace crypto {
struct Certificate;
struct PrivateKey;
} // namespace crypto
} // namespace dht
namespace jami {
namespace tls {
enum class TlsSessionState {
NONE,
SETUP,
COOKIE, // only used with non-initiator and non-reliable transport
HANDSHAKE,
MTU_DISCOVERY, // only used with non-reliable transport
ESTABLISHED,
SHUTDOWN
};
using clock = std::chrono::steady_clock;
using duration = clock::duration;
struct TlsParams
{
// User CA list for session credentials
std::string ca_list;
std::shared_ptr<dht::crypto::Certificate> peer_ca;
// User identity for credential
std::shared_ptr<dht::crypto::Certificate> cert;
std::shared_ptr<dht::crypto::PrivateKey> cert_key;
// Diffie-Hellman computed by gnutls_dh_params_init/gnutls_dh_params_generateX
std::shared_future<DhParams> dh_params;
tls::CertificateStore& certStore;
// handshake timeout
duration timeout;
// Callback for certificate checkings
std::function<int(unsigned status, const gnutls_datum_t* cert_list, unsigned cert_list_size)>
cert_check;
std::shared_ptr<asio::io_context> io_context;
std::shared_ptr<Logger> logger;
};
/// TlsSession
///
/// Manages a TLS/DTLS data transport overlayed on a given generic socket.
///
/// \note API is not thread-safe.
///
class TlsSession : public GenericSocket<uint8_t>
{
public:
using SocketType = GenericSocket<uint8_t>;
using OnStateChangeFunc = std::function<void(TlsSessionState)>;
using OnRxDataFunc = std::function<void(std::vector<uint8_t>&&)>;
using OnCertificatesUpdate
= std::function<void(const gnutls_datum_t*, const gnutls_datum_t*, unsigned int)>;
using VerifyCertificate = std::function<int(gnutls_session_t)>;
// ===> WARNINGS <===
// Following callbacks are called into the FSM thread context
// Do not call blocking routines inside them.
using TlsSessionCallbacks = struct
{
OnStateChangeFunc onStateChange;
OnRxDataFunc onRxData;
OnCertificatesUpdate onCertificatesUpdate;
VerifyCertificate verifyCertificate;
};
TlsSession(std::unique_ptr<SocketType>&& transport,
const TlsParams& params,
const TlsSessionCallbacks& cbs,
bool anonymous = true);
~TlsSession();
/// Request TLS thread to stop and quit.
/// \note IO operations return error after this call.
void shutdown() override;
void setOnRecv(RecvCb&& cb) override
{
(void) cb;
throw std::logic_error("TlsSession::setOnRecv not implemented");
}
/// Return true if the TLS session type is a server.
bool isInitiator() const override;
bool isReliable() const override;
int maxPayload() const override;
void waitForReady(const duration& timeout = {});
/// Synchronous writing.
/// Return a positive number for number of bytes write, or 0 and \a ec set in case of error.
std::size_t write(const ValueType* data, std::size_t size, std::error_code& ec) override;
/// Synchronous reading.
/// Return a positive number for number of bytes read, or 0 and \a ec set in case of error.
std::size_t read(ValueType* data, std::size_t size, std::error_code& ec) override;
int waitForData(std::chrono::milliseconds, std::error_code&) const override;
std::shared_ptr<dht::crypto::Certificate> peerCertificate() const;
const std::shared_ptr<dht::log::Logger>& logger() const;
private:
class TlsSessionImpl;
std::unique_ptr<TlsSessionImpl> pimpl_;
};
} // namespace tls
} // namespace jami
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "generic_io.h"
#include <memory>
#include <functional>
#if defined(_MSC_VER)
#include <BaseTsd.h>
using ssize_t = SSIZE_T;
#endif
namespace jami {
class IceTransport;
using IceRecvCb = std::function<ssize_t(unsigned char* buf, size_t len)>;
class IceSocket
{
private:
std::shared_ptr<IceTransport> ice_transport_ {};
int compId_ = -1;
public:
IceSocket(std::shared_ptr<IceTransport> iceTransport, int compId)
: ice_transport_(std::move(iceTransport))
, compId_(compId)
{}
void close();
ssize_t send(const unsigned char* buf, size_t len);
ssize_t waitForData(std::chrono::milliseconds timeout);
void setOnRecv(IceRecvCb cb);
uint16_t getTransportOverhead();
void setDefaultRemoteAddress(const IpAddr& addr);
int getCompId() const { return compId_; };
};
}; // namespace jami
This diff is collapsed.
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "ice_options.h"
#include "ice_socket.h"
#include "ip_utils.h"
#include <pjnath.h>
#include <pjlib.h>
#include <pjlib-util.h>
#include <functional>
#include <memory>
#include <msgpack.hpp>
#include <vector>
namespace dht {
namespace log {
class Logger;
}
}
namespace jami {
using Logger = dht::log::Logger;
namespace upnp {
class Controller;
}
class IceTransport;
using IceRecvCb = std::function<ssize_t(unsigned char* buf, size_t len)>;
using IceCandidate = pj_ice_sess_cand;
using onShutdownCb = std::function<void(void)>;
struct ICESDP
{
std::vector<IceCandidate> rem_candidates;
std::string rem_ufrag;
std::string rem_pwd;
};
struct SDP
{
std::string ufrag;
std::string pwd;
std::vector<std::string> candidates;
MSGPACK_DEFINE(ufrag, pwd, candidates)
};
class IceTransport
{
public:
using Attribute = struct
{
std::string ufrag;
std::string pwd;
};
/**
* Constructor
*/
IceTransport(std::string_view name);
~IceTransport();
const std::shared_ptr<Logger>& logger() const;
void initIceInstance(const IceTransportOptions& options);
/**
* Get current state
*/
bool isInitiator() const;
/**
* Start transport negotiation between local candidates and given remote
* to find the right candidate pair.
* This function doesn't block, the callback on_negodone_cb will be called
* with the negotiation result when operation is really done.
* Return false if negotiation cannot be started else true.
*/
bool startIce(const Attribute& rem_attrs, std::vector<IceCandidate>&& rem_candidates);
bool startIce(const SDP& sdp);
/**
* Cancel operations
*/
void cancelOperations();
/**
* Returns true if ICE transport has been initialized
* [mutex protected]
*/
bool isInitialized() const;
/**
* Returns true if ICE negotiation has been started
* [mutex protected]
*/
bool isStarted() const;
/**
* Returns true if ICE negotiation has completed with success
* [mutex protected]
*/
bool isRunning() const;
/**
* Returns true if ICE transport is in failure state
* [mutex protected]
*/
bool isFailed() const;
IpAddr getLocalAddress(unsigned comp_id) const;
IpAddr getRemoteAddress(unsigned comp_id) const;
IpAddr getDefaultLocalAddress() const { return getLocalAddress(1); }
/**
* Return ICE session attributes
*/
const Attribute getLocalAttributes() const;
/**
* Return ICE session attributes
*/
std::vector<std::string> getLocalCandidates(unsigned comp_id) const;
/**
* Return ICE session attributes
*/
std::vector<std::string> getLocalCandidates(unsigned streamIdx, unsigned compId) const;
bool parseIceAttributeLine(unsigned streamIdx,
const std::string& line,
IceCandidate& cand) const;
bool getCandidateFromSDP(const std::string& line, IceCandidate& cand) const;
// I/O methods
void setOnRecv(unsigned comp_id, IceRecvCb cb);
void setOnShutdown(onShutdownCb&& cb);
ssize_t recv(unsigned comp_id, unsigned char* buf, size_t len, std::error_code& ec);
ssize_t recvfrom(unsigned comp_id, char* buf, size_t len, std::error_code& ec);
ssize_t send(unsigned comp_id, const unsigned char* buf, size_t len);
bool waitForInitialization(std::chrono::milliseconds timeout);
int waitForNegotiation(std::chrono::milliseconds timeout);
ssize_t waitForData(unsigned comp_id, std::chrono::milliseconds timeout, std::error_code& ec);
unsigned getComponentCount() const;
// Set session state
bool setSlaveSession();
bool setInitiatorSession();
bool isTCPEnabled();
ICESDP parseIceCandidates(std::string_view sdp_msg);
void setDefaultRemoteAddress(unsigned comp_id, const IpAddr& addr);
std::string link() const;
private:
class Impl;
std::unique_ptr<Impl> pimpl_;
};
class IceTransportFactory
{
public:
IceTransportFactory();
~IceTransportFactory();
std::shared_ptr<IceTransport> createTransport(std::string_view name);
std::unique_ptr<IceTransport> createUTransport(std::string_view name);
/**
* PJSIP specifics
*/
pj_ice_strans_cfg getIceCfg() const { return ice_cfg_; }
pj_pool_factory* getPoolFactory() { return &cp_->factory; }
std::shared_ptr<pj_caching_pool> getPoolCaching() { return cp_; }
private:
std::shared_ptr<pj_caching_pool> cp_;
pj_ice_strans_cfg ice_cfg_;
};
}; // namespace jami
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*
* Copyright (C) 2004-2023 Savoir-faire Linux Inc.
*
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "ip_utils.h"
#include "certstore.h"
#include "opendht/crypto.h"
#include "ice_transport.h"
#include "tls_session.h"
#include <functional>
#include <future>
#include <limits>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace dht {
namespace crypto {
struct PrivateKey;
struct Certificate;
} // namespace crypto
} // namespace dht
namespace jami {
namespace tls {
class DhParams;
}
using OnStateChangeCb = std::function<bool(tls::TlsSessionState state)>;
using OnReadyCb = std::function<void(bool ok)>;
using onShutdownCb = std::function<void(void)>;
//==============================================================================
class IceSocketEndpoint : public GenericSocket<uint8_t>
{
public:
using SocketType = GenericSocket<uint8_t>;
explicit IceSocketEndpoint(std::shared_ptr<IceTransport> ice, bool isSender);
~IceSocketEndpoint();
void shutdown() override;
bool isReliable() const override { return ice_ ? ice_->isRunning() : false; }
bool isInitiator() const override { return ice_ ? ice_->isInitiator() : true; }
int maxPayload() const override
{
return 65536 /* The max for a RTP packet used to wrap data here */;
}
int waitForData(std::chrono::milliseconds timeout, std::error_code& ec) const override;
std::size_t read(ValueType* buf, std::size_t len, std::error_code& ec) override;
std::size_t write(const ValueType* buf, std::size_t len, std::error_code& ec) override;
std::shared_ptr<IceTransport> underlyingICE() const { return ice_; }
void setOnRecv(RecvCb&& cb) override
{
if (ice_)
ice_->setOnRecv(compId_, cb);
}
private:
std::shared_ptr<IceTransport> ice_ {nullptr};
std::atomic_bool iceStopped {false};
std::atomic_bool iceIsSender {false};
uint8_t compId_ {1};
};
//==============================================================================
/// Implement a TLS session IO over a system socket
class TlsSocketEndpoint : public GenericSocket<uint8_t>
{
public:
using SocketType = GenericSocket<uint8_t>;
using Identity = std::pair<std::shared_ptr<dht::crypto::PrivateKey>,
std::shared_ptr<dht::crypto::Certificate>>;
TlsSocketEndpoint(std::unique_ptr<IceSocketEndpoint>&& tr,
tls::CertificateStore& certStore,
const Identity& local_identity,
const std::shared_future<tls::DhParams>& dh_params,
const dht::crypto::Certificate& peer_cert);
TlsSocketEndpoint(std::unique_ptr<IceSocketEndpoint>&& tr,
tls::CertificateStore& certStore,
const Identity& local_identity,
const std::shared_future<tls::DhParams>& dh_params,
std::function<bool(const dht::crypto::Certificate&)>&& cert_check);
~TlsSocketEndpoint();
bool isReliable() const override { return true; }
bool isInitiator() const override;
int maxPayload() const override;
void shutdown() override;
std::size_t read(ValueType* buf, std::size_t len, std::error_code& ec) override;
std::size_t write(const ValueType* buf, std::size_t len, std::error_code& ec) override;
std::shared_ptr<dht::crypto::Certificate> peerCertificate() const;
void setOnRecv(RecvCb&&) override
{
throw std::logic_error("TlsSocketEndpoint::setOnRecv not implemented");
}
int waitForData(std::chrono::milliseconds timeout, std::error_code&) const override;
void waitForReady(const std::chrono::milliseconds& timeout = {});
void setOnStateChange(OnStateChangeCb&& cb);
void setOnReady(OnReadyCb&& cb);
IpAddr getLocalAddress() const;
IpAddr getRemoteAddress() const;
void monitor() const;
private:
class Impl;
std::unique_ptr<Impl> pimpl_;
};
} // namespace jami
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment