blob: 086981f6825487729a44d03c46e546d375cff1a4 [file] [log] [blame]
/*
* 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 "ice_transport_factory.h"
#include "turn_cache.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 dhtnet {
class ChannelSocket;
class ConnectionManager;
namespace upnp {
class Controller;
}
namespace tls {
class CertificateStore;
}
enum class ConnectionStatus : int { Connected, TLS, ICE, Connecting, Waiting };
/**
* 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&)>;
using ConnectCallbackLegacy = std::function<void(const std::shared_ptr<ChannelSocket>&, const dht::InfoHash&)>;
/**
* 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:
struct Config;
ConnectionManager(std::shared_ptr<Config> config_);
ConnectionManager(dht::crypto::Identity id);
~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 dht::InfoHash& deviceId,
const std::string& name,
ConnectCallbackLegacy 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;
/**
* Check if we are already connected to a device
* @param deviceId Remote device
* @return if connected
*/
bool isConnected(const DeviceId& deviceId) 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 = {});
/**
* Retrieve the list of connections.
*
* @param device The device ID to filter the connections (optional).
* @return The list of connections as a vector of maps, where each map represents a connection.
*
* Note: The connections are represented as maps with string keys and string values. The map
* contains the following key-value pairs:
* - "id": The unique identifier of the connection.
* - "device": The device URI associated with the connection.
* - "status": The status of the connection, represented as an integer:
* - 0: ConnectionStatus::Connected
* - 1: ConnectionStatus::TLS
* - 2: ConnectionStatus::ICE
* - 3: ConnectionStatus::Connecting (for pending operations)
* - 4: ConnectionStatus::Waiting (for pending operations)
* - "remoteAddress": The remote IP address of the connection (if available).
* - "remotePort": The remote port of the connection (if available).
*
* If a specific device ID is provided, the returned list will only include connections
* associated with that device. Otherwise, connections from all devices will be included.
*/
std::vector<std::map<std::string, std::string>> getConnectionList(
const DeviceId& device = {}) const;
/**
* Retrieve the list of channels associated with a connection.
*
* @param connectionId The ID of the connection to fetch the channels from.
* @return The list of channels as a vector of maps, where each map represents a channel
* and contains key-value pairs of channel ID and channel name.
*
* If the specified connection ID is valid and associated with a connection,
* the method returns the list of channels associated with that connection.
* Otherwise, an empty vector is returned.
*/
std::vector<std::map<std::string, std::string>> getChannelList(
const std::string& connectionId) const;
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;
std::shared_ptr<TurnCache> turnCache;
std::filesystem::path cachePath {};
std::shared_ptr<asio::io_context> ioContext;
std::shared_ptr<dht::DhtRunner> dht;
dht::crypto::Identity id {};
std::shared_ptr<tls::CertificateStore> certStore {nullptr};
std::shared_ptr<dhtnet::IceTransportFactory> factory {nullptr};
/**
* UPnP IGD controller and the mutex to access it
*/
bool upnpEnabled {true};
std::shared_ptr<dhtnet::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;
/** Optional pseudo random generator to be used, allowing to control the seed. */
std::unique_ptr<std::mt19937_64> rng;
};
} // namespace dhtnet