ringaccount.h 20.3 KB
Newer Older
Adrien Béraud's avatar
Adrien Béraud committed
1
/*
2
 *  Copyright (C) 2014-2017 Savoir-faire Linux Inc.
Adrien Béraud's avatar
Adrien Béraud committed
3 4
 *
 *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
5
 *  Author: Simon Désaulniers <simon.desaulniers@gmail.com>
Adrien Béraud's avatar
Adrien Béraud committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 *  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.
 */

22
#pragma once
Adrien Béraud's avatar
Adrien Béraud committed
23 24 25 26 27

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

28
#include "security/tls_session.h"
Adrien Béraud's avatar
Adrien Béraud committed
29
#include "sip/sipaccountbase.h"
30

Adrien Béraud's avatar
Adrien Béraud committed
31 32
#include "noncopyable.h"
#include "ip_utils.h"
33
#include "ring_types.h" // enable_if_base_of
34 35

#include <opendht/dhtrunner.h>
36
#include <opendht/default_types.h>
Adrien Béraud's avatar
Adrien Béraud committed
37 38 39 40 41

#include <pjsip/sip_types.h>

#include <vector>
#include <map>
42
#include <chrono>
43
#include <list>
Adrien Béraud's avatar
Adrien Béraud committed
44
#include <future>
Adrien Béraud's avatar
Adrien Béraud committed
45

46 47 48 49
#if HAVE_RINGNS
#include "namedirectory.h"
#endif

50
/**
51 52
 * @file ringaccount.h
 * @brief Ring Account is build on top of SIPAccountBase and uses DHT to handle call connectivity.
53 54 55 56 57 58 59
 */

namespace YAML {
class Node;
class Emitter;
}

60 61 62 63 64 65 66 67

namespace dev
{
    template <unsigned N> class FixedHash;
    using h160 = FixedHash<20>;
    using Address = h160;
}

68 69
namespace ring {

Adrien Béraud's avatar
Adrien Béraud committed
70
namespace Conf {
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
constexpr const char* const DHT_PORT_KEY = "dhtPort";
constexpr const char* const DHT_VALUES_PATH_KEY = "dhtValuesPath";
constexpr const char* const DHT_CONTACTS = "dhtContacts";
constexpr const char* const DHT_PUBLIC_PROFILE = "dhtPublicProfile";
constexpr const char* const DHT_PUBLIC_IN_CALLS = "dhtPublicInCalls";
constexpr const char* const DHT_ALLOW_PEERS_FROM_HISTORY = "allowPeersFromHistory";
constexpr const char* const DHT_ALLOW_PEERS_FROM_CONTACT = "allowPeersFromContact";
constexpr const char* const DHT_ALLOW_PEERS_FROM_TRUSTED = "allowPeersFromTrusted";
constexpr const char* const ETH_KEY = "ethKey";
constexpr const char* const ETH_PATH = "ethPath";
constexpr const char* const ETH_ACCOUNT = "ethAccount";
constexpr const char* const RING_CA_KEY = "ringCaKey";
constexpr const char* const RING_ACCOUNT_KEY = "ringAccountKey";
constexpr const char* const RING_ACCOUNT_CERT = "ringAccountCert";
constexpr const char* const RING_ACCOUNT_RECEIPT = "ringAccountReceipt";
constexpr const char* const RING_ACCOUNT_RECEIPT_SIG = "ringAccountReceiptSignature";
87
constexpr const char* const RING_ACCOUNT_CRL = "ringAccountCRL";
Adrien Béraud's avatar
Adrien Béraud committed
88 89
}

90 91
class IceTransport;

92
class RingAccount : public SIPAccountBase {
Adrien Béraud's avatar
Adrien Béraud committed
93
    public:
94
        constexpr static const char* const ACCOUNT_TYPE = "RING";
95
        constexpr static const in_port_t DHT_DEFAULT_PORT = 4222;
96
        constexpr static const char* const DHT_DEFAULT_BOOTSTRAP = "bootstrap.ring.cx";
97 98
        constexpr static const char* const DHT_TYPE_NS = "cx.ring";

99
        /* constexpr */ static const std::pair<uint16_t, uint16_t> DHT_PORT_RANGE;
Adrien Béraud's avatar
Adrien Béraud committed
100

101
        const char* getAccountType() const override {
Adrien Béraud's avatar
Adrien Béraud committed
102 103 104
            return ACCOUNT_TYPE;
        }

105 106 107 108 109 110 111
        std::shared_ptr<RingAccount> shared() {
            return std::static_pointer_cast<RingAccount>(shared_from_this());
        }
        std::shared_ptr<RingAccount const> shared() const {
            return std::static_pointer_cast<RingAccount const>(shared_from_this());
        }

Adrien Béraud's avatar
Adrien Béraud committed
112 113 114 115
        /**
         * Constructor
         * @param accountID The account identifier
         */
116
        RingAccount(const std::string& accountID, bool presenceEnabled);
Adrien Béraud's avatar
Adrien Béraud committed
117

118
        ~RingAccount();
Adrien Béraud's avatar
Adrien Béraud committed
119 120 121 122 123

        /**
         * Serialize internal state of this account for configuration
         * @param YamlEmitter the configuration engine which generate the configuration file
         */
124
        virtual void serialize(YAML::Emitter &out) override;
Adrien Béraud's avatar
Adrien Béraud committed
125 126 127 128 129

        /**
         * Populate the internal state for this account based on info stored in the configuration file
         * @param The configuration node for this account
         */
130
        virtual void unserialize(const YAML::Node &node) override;
Adrien Béraud's avatar
Adrien Béraud committed
131 132 133 134 135 136

        /**
         * Return an map containing the internal state of this account. Client application can use this method to manage
         * account info.
         * @return A map containing the account information.
         */
137
        virtual std::map<std::string, std::string> getAccountDetails() const override;
Adrien Béraud's avatar
Adrien Béraud committed
138

139 140 141 142 143 144
        /**
         * Retrieve volatile details such as recent registration errors
         * @return std::map< std::string, std::string > The account volatile details
         */
        virtual std::map<std::string, std::string> getVolatileAccountDetails() const override;

Adrien Béraud's avatar
Adrien Béraud committed
145 146 147
        /**
         * Actually useless, since config loading is done in init()
         */
148
        void loadConfig() override {}
Adrien Béraud's avatar
Adrien Béraud committed
149

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
        /**
         * Adds an account id to the list of accounts to track on the DHT for
         * buddy presence.
         *
         * @param buddy_id  The buddy id.
         */
        void trackBuddyPresence(const std::string& buddy_id);

        /**
         * Tells for each tracked account id if it has been seen online so far
         * in the last DeviceAnnouncement::TYPE.expiration minutes.
         *
         * @return map of buddy_uri to bool (online or not)
         */
        std::map<std::string, bool> getTrackedBuddyPresence();

Adrien Béraud's avatar
Adrien Béraud committed
166 167 168
        /**
         * Connect to the DHT.
         */
169
        void doRegister() override;
Adrien Béraud's avatar
Adrien Béraud committed
170 171 172 173

        /**
         * Disconnect from the DHT.
         */
174
        void doUnregister(std::function<void(bool)> cb = {}) override;
Adrien Béraud's avatar
Adrien Béraud committed
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194

        /**
         * @return pj_str_t "From" uri based on account information.
         * From RFC3261: "The To header field first and foremost specifies the desired
         * logical" recipient of the request, or the address-of-record of the
         * user or resource that is the target of this request. [...]  As such, it is
         * very important that the From URI not contain IP addresses or the FQDN
         * of the host on which the UA is running, since these are not logical
         * names."
         */
        std::string getFromUri() const;

        /**
         * This method adds the correct scheme, hostname and append
         * the ;transport= parameter at the end of the uri, in accordance with RFC3261.
         * It is expected that "port" is present in the internal hostname_.
         *
         * @return pj_str_t "To" uri based on @param username
         * @param username A string formatted as : "username"
         */
195
        std::string getToUri(const std::string& username) const override;
Adrien Béraud's avatar
Adrien Béraud committed
196 197

        /**
198
         * In the current version of Ring, "srv" uri is obtained in the preformated
Adrien Béraud's avatar
Adrien Béraud committed
199 200 201 202 203 204 205 206 207 208 209 210
         * way: hostname:port. This method adds the correct scheme and append
         * the ;transport= parameter at the end of the uri, in accordance with RFC3261.
         *
         * @return pj_str_t "server" uri based on @param hostPort
         * @param hostPort A string formatted as : "hostname:port"
         */
        std::string getServerUri() const { return ""; };

        /**
         * Get the contact header for
         * @return pj_str_t The contact header based on account information
         */
211
        pj_str_t getContactHeader(pjsip_transport* = nullptr) override;
Adrien Béraud's avatar
Adrien Béraud committed
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228

        void setReceivedParameter(const std::string &received) {
            receivedParameter_ = received;
            via_addr_.host.ptr = (char *) receivedParameter_.c_str();
            via_addr_.host.slen = receivedParameter_.size();
        }

        std::string getReceivedParameter() const {
            return receivedParameter_;
        }

        pjsip_host_port *
        getViaAddr() {
            return &via_addr_;
        }

        /* Returns true if the username and/or hostname match this account */
229
        MatchRank matches(const std::string &username, const std::string &hostname) const override;
Adrien Béraud's avatar
Adrien Béraud committed
230 231 232 233 234

        /**
         * Implementation of Account::newOutgoingCall()
         * Note: keep declaration before newOutgoingCall template.
         */
235
        std::shared_ptr<Call> newOutgoingCall(const std::string& toUrl) override;
Adrien Béraud's avatar
Adrien Béraud committed
236 237 238 239 240 241 242 243

        /**
         * Create outgoing SIPCall.
         * @param[in] toUrl The address to call
         * @return std::shared_ptr<T> A shared pointer on the created call.
         *      The type of this instance is given in template argument.
         *      This type can be any base class of SIPCall class (included).
         */
244 245 246 247 248 249 250 251
#ifndef RING_UWP
        template <class T=SIPCall>
        std::shared_ptr<enable_if_base_of<T, SIPCall> >
        newOutgoingCall(const std::string& toUrl);
#else
        template <class T>
        std::shared_ptr<T>
        newOutgoingCall(const std::string& toUrl);
252
#endif
Adrien Béraud's avatar
Adrien Béraud committed
253 254 255

        /**
         * Create incoming SIPCall.
256
         * @param[in] from The origin of the call
Adrien Béraud's avatar
Adrien Béraud committed
257 258 259 260 261
         * @return std::shared_ptr<T> A shared pointer on the created call.
         *      The type of this instance is given in template argument.
         *      This type can be any base class of SIPCall class (included).
         */
        virtual std::shared_ptr<SIPCall>
262
        newIncomingCall(const std::string& from = {}) override;
Adrien Béraud's avatar
Adrien Béraud committed
263

264
        virtual bool isTlsEnabled() const override {
265
            return true;
Adrien Béraud's avatar
Adrien Béraud committed
266 267
        }

268 269 270 271
        virtual bool isSrtpEnabled() const {
            return true;
        }

272
        virtual sip_utils::KeyExchangeProtocol getSrtpKeyExchange() const override {
Guillaume Roguez's avatar
Guillaume Roguez committed
273
            return sip_utils::KeyExchangeProtocol::SDES;
Adrien Béraud's avatar
Adrien Béraud committed
274 275
        }

276
        virtual bool getSrtpFallback() const override {
Adrien Béraud's avatar
Adrien Béraud committed
277 278 279
            return false;
        }

280 281 282 283
        bool setCertificateStatus(const std::string& cert_id, tls::TrustStore::PermissionStatus status);
        bool setCertificateStatus(const std::string& cert_id, tls::TrustStatus status);

        std::vector<std::string> getCertificatesByStatus(tls::TrustStore::PermissionStatus status);
284 285

        bool findCertificate(const std::string& id);
286
        bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {});
287 288 289 290 291 292

        /* contact requests */
        std::map<std::string, std::string> getTrustRequests() const;
        bool acceptTrustRequest(const std::string& from);
        bool discardTrustRequest(const std::string& from);

293
        void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
Adrien Béraud's avatar
Adrien Béraud committed
294
        virtual void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads, uint64_t id) override;
295

296 297
        void addDevice(const std::string& password);

298 299
        bool revokeDevice(const std::string& password, const std::string& device);

300
        std::map<std::string, std::string> getKnownDevices() const;
301

302
        void connectivityChanged() override;
303

304
        // overloaded methods
305 306
        void flush() override;

307 308 309 310 311 312
#if HAVE_RINGNS
        void lookupName(const std::string& name);
        void lookupAddress(const std::string& address);
        void registerName(const std::string& password, const std::string& name);
#endif

Adrien Béraud's avatar
Adrien Béraud committed
313
    private:
314
        NON_COPYABLE(RingAccount);
315

316
        /**
317
         * Private structures
318
         */
319 320
        struct PendingCall;
        struct PendingMessage;
321
        struct SavedTrustRequest;
322 323 324 325 326
        struct TrustRequest;
        struct KnownDevice;
        struct ArchiveContent;
        struct DeviceAnnouncement;
        struct DeviceSync;
327
        struct BuddyInfo;
328 329 330 331

        void syncDevices();
        void onReceiveDeviceSync(DeviceSync&& sync);

332 333
#if HAVE_RINGNS
        std::reference_wrapper<NameDirectory> nameDir_;
334
        std::string nameServer_;
335 336 337
        std::string registeredName_;
#endif

338 339 340 341 342
        /**
         * Compute archive encryption key and DHT storage location from password and PIN.
         */
        static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false);

343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
        /**
         * Update tracking info when buddy appears offline.
         *
         * @param buddy_info_it  An iterator over the map trackedBuddies_
         */
        void onTrackedBuddyOffline(std::map<dht::InfoHash, BuddyInfo>::iterator& buddy_info_it);

        /**
         * Update tracking info when buddy appears offline.
         *
         * @param buddy_info_it  An iterator over the map trackedBuddies_
         * @param device_id       The device id
         */
        void onTrackedBuddyOnline(std::map<dht::InfoHash, BuddyInfo>::iterator& buddy_info_it, const dht::InfoHash& device_id);

358
        void doRegister_();
359
        void incomingCall(dht::IceCandidates&& msg, const std::shared_ptr<dht::crypto::Certificate>& from);
360

361
        const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)};
362 363

        void handleEvents();
364

365 366
        void forEachDevice(const dht::InfoHash& to, std::function<void(const std::shared_ptr<RingAccount>&, const dht::InfoHash&)> op, std::function<void(bool)> end = {});

367
        void createOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& to_id, IpAddr target);
Adrien Béraud's avatar
Adrien Béraud committed
368 369 370 371 372

        /**
         * Set the internal state for this account, mainly used to manage account details from the client application.
         * @param The map containing the account information.
         */
373
        virtual void setAccountDetails(const std::map<std::string, std::string> &details) override;
Adrien Béraud's avatar
Adrien Béraud committed
374

375
        void startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string toUri);
376

Adrien Béraud's avatar
Adrien Béraud committed
377 378 379 380 381
        /**
         * Start a SIP Call
         * @param call  The current call
         * @return true if all is correct
         */
382
        bool SIPStartCall(const std::shared_ptr<SIPCall>& call, IpAddr target);
Adrien Béraud's avatar
Adrien Béraud committed
383

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
        /**
         * Inform that a potential account device have been found.
         * Returns true if the device have been validated to be part of this account
         */
        bool foundAccountDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, const std::string& name = {});

        /**
         * Inform that a potential peer device have been found.
         * Returns true only if the device certificate is a valid Ring device certificate.
         * In that case (true is returned) the account_id parameter is set to the peer account ID.
         */
        bool foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id);

        /**
         * Check that a peer is authorised to talk to us.
         * If everything is in order, calls the callback with the
         * peer certificate chain (down to the peer device certificate),
         * and the peer account id.
         */
        void onPeerMessage(const dht::InfoHash& peer_device, std::function<void(const std::shared_ptr<dht::crypto::Certificate>& crt, const dht::InfoHash& account_id)>);
404

405 406 407 408 409
        /**
         * Maps require port via UPnP
         */
        bool mapPortUPnP();

410 411
        void igdChanged();

Adrien Béraud's avatar
Adrien Béraud committed
412
        dht::DhtRunner dht_ {};
413
        dht::crypto::Identity identity_ {};
Adrien Béraud's avatar
Adrien Béraud committed
414

415 416
        dht::InfoHash callKey_;

417 418 419
        void handlePendingCallList();
        bool handlePendingCall(PendingCall& pc, bool incoming);

420
        /**
421
         * DHT calls waiting for ICE negotiation
422
         */
423
        std::list<PendingCall> pendingCalls_;
424

425
        /**
426
         * Incoming DHT calls that are not yet actual SIP calls.
427
         */
428
        std::list<PendingCall> pendingSipCalls_;
429
        std::set<dht::Value::Id> treatedCalls_ {};
430
        mutable std::mutex callsMutex_ {};
431

432
        std::map<dht::Value::Id, PendingMessage> sentMessages_;
Adrien Béraud's avatar
Adrien Béraud committed
433 434
        std::set<dht::Value::Id> treatedMessages_ {};

435 436
        std::string ringAccountId_ {};
        std::string ringDeviceId_ {};
437
        std::string ringDeviceName_ {};
438
        std::string idPath_ {};
439
        std::string cachePath_ {};
440
        std::string dataPath_ {};
441 442
        std::string ethPath_ {};
        std::string ethAccount_ {};
Adrien Béraud's avatar
Adrien Béraud committed
443

444 445 446 447 448
        std::string archivePath_ {};

        std::string receipt_ {};
        std::vector<uint8_t> receiptSignature_ {};
        dht::Value announceVal_;
449

450 451 452
        std::map<dht::InfoHash, TrustRequest> trustRequests_;
        void loadTrustRequests();
        void saveTrustRequests();
453 454 455

        tls::TrustStore trust_;

456
        std::shared_ptr<dht::Value> announce_;
457

458
        /* this ring account associated devices */
459
        std::map<dht::InfoHash, KnownDevice> knownDevices_;
460

461 462 463 464
        /* tracked buddies presence */
        std::recursive_mutex buddyInfoMtx;
        std::map<dht::InfoHash, BuddyInfo> trackedBuddies_;

465 466 467 468 469 470
        void loadAccount(const std::string& archive_password = {}, const std::string& archive_pin = {});
        void loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin);

        bool hasCertificate() const;
        bool hasPrivateKey() const;
        bool hasSignedReceipt();
471
        bool needsMigration() const;
472 473 474 475

        std::string makeReceipt(const dht::crypto::Identity& id);
        void createRingDevice(const dht::crypto::Identity& id);
        void initRingDevice(const ArchiveContent& a);
476 477
        bool migrateAccount(const std::string& pwd);
        static bool updateCertificates(ArchiveContent& archive, dht::crypto::Identity& device);
478 479 480 481 482 483 484

        void createAccount(const std::string& archive_password);
        std::vector<uint8_t> makeArchive(const ArchiveContent& content) const;
        void saveArchive(const ArchiveContent& content, const std::string& pwd);
        ArchiveContent readArchive(const std::string& pwd) const;
        static ArchiveContent loadArchive(const std::vector<uint8_t>& data);
        std::vector<std::pair<sockaddr_storage, socklen_t>> loadBootstrap() const;
485

486
        std::pair<std::string, std::string> saveIdentity(const dht::crypto::Identity id, const std::string& path) const;
Adrien Béraud's avatar
Adrien Béraud committed
487 488
        void saveNodes(const std::vector<dht::NodeExport>&) const;
        void saveValues(const std::vector<dht::ValuesExport>&) const;
489

490 491 492
        void loadTreatedCalls();
        void saveTreatedCalls() const;

493 494 495
        void loadTreatedMessages();
        void saveTreatedMessages() const;

496
        void loadKnownDevicesOld();
497 498 499
        void loadKnownDevices();
        void saveKnownDevices() const;

500 501
        void replyToIncomingIceMsg(const std::shared_ptr<SIPCall>&,
                                   const std::shared_ptr<IceTransport>&,
502
                                   const dht::IceCandidates&,
503
                                   const std::shared_ptr<dht::crypto::Certificate>&);
504

Adrien Béraud's avatar
Adrien Béraud committed
505 506
        static tls::DhParams loadDhParams(const std::string path);

507 508 509 510 511
        /**
         * If privkeyPath_ is a valid private key file (PEM or DER),
         * and certPath_ a valid certificate file, load and returns them.
         * Otherwise, generate a new identity and returns it.
         */
512
        dht::crypto::Identity loadIdentity();
Adrien Béraud's avatar
Adrien Béraud committed
513 514
        std::vector<dht::NodeExport> loadNodes() const;
        std::vector<dht::ValuesExport> loadValues() const;
Adrien Béraud's avatar
Adrien Béraud committed
515

516 517
        bool dhtPublicInCalls_ {true};

Adrien Béraud's avatar
Adrien Béraud committed
518
        /**
519
         * DHT port preference
Adrien Béraud's avatar
Adrien Béraud committed
520
         */
521
        in_port_t dhtPort_ {};
Adrien Béraud's avatar
Adrien Béraud committed
522

523 524 525 526 527 528
        /**
         * DHT port actually used,
         * this holds the actual port used for DHT, which may not be the port
         * selected in the configuration in the case that UPnP is used and the
         * configured port is already used by another client
         */
529
        UsedPort dhtPortUsed_ {};
530

Adrien Béraud's avatar
Adrien Béraud committed
531 532 533
        /**
         * The TLS settings, used only if tls is chosen as a sip transport.
         */
534
        void generateDhParams();
535

Adrien Béraud's avatar
Adrien Béraud committed
536
        std::shared_future<tls::DhParams> dhParams_;
537 538
        std::mutex dhParamsMtx_;
        std::condition_variable dhParamsCv_;
539 540 541 542

        bool allowPeersFromHistory_ {true};
        bool allowPeersFromContact_ {true};
        bool allowPeersFromTrusted_ {true};
Adrien Béraud's avatar
Adrien Béraud committed
543 544 545 546 547 548 549 550 551 552 553 554 555 556

        /**
         * Optional: "received" parameter from VIA header
         */
        std::string receivedParameter_ {};

        /**
         * Optional: "rport" parameter from VIA header
         */
        int rPort_ {-1};

        /**
         * Optional: via_addr construct from received parameters
         */
557
        pjsip_host_port via_addr_ {};
Adrien Béraud's avatar
Adrien Béraud committed
558 559 560

        char contactBuffer_[PJSIP_MAX_URL_SIZE] {};
        pj_str_t contact_ {contactBuffer_, 0};
561
        pjsip_transport* via_tp_ {nullptr};
562 563

        template <class... Args>
564
        std::shared_ptr<IceTransport> createIceTransport(const Args&... args);
565 566

        void registerDhtAddress(IceTransport&);
Adrien Béraud's avatar
Adrien Béraud committed
567 568
};

569
} // namespace ring