LCOV - code coverage report
Current view: top level - src/jamidht - svc_tunnel_channel_handler.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 4.1 % 368 15
Test Date: 2026-06-13 09:18:46 Functions: 4.6 % 65 3

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3              :  *
       4              :  *  This program is free software: you can redistribute it and/or modify
       5              :  *  it under the terms of the GNU General Public License as published by
       6              :  *  the Free Software Foundation, either version 3 of the License, or
       7              :  *  (at your option) any later version.
       8              :  *
       9              :  *  This program is distributed in the hope that it will be useful,
      10              :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11              :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12              :  *  GNU General Public License for more details.
      13              :  *
      14              :  *  You should have received a copy of the GNU General Public License
      15              :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16              :  */
      17              : #include "jamidht/svc_tunnel_channel_handler.h"
      18              : 
      19              : #include "jamidht/account_manager.h"
      20              : #include "jamidht/contact_list.h"
      21              : #include "jamidht/service_manager.h"
      22              : #include "jamidht/svc_protocol.h"
      23              : #include "logger.h"
      24              : #include "manager.h"
      25              : 
      26              : #include <asio/connect.hpp>
      27              : #include <asio/post.hpp>
      28              : #include <asio/read.hpp>
      29              : #include <asio/write.hpp>
      30              : 
      31              : #include <atomic>
      32              : 
      33              : namespace jami {
      34              : 
      35              : namespace {
      36              : constexpr size_t kRelayBufSize = 16 * 1024;
      37              : }
      38              : 
      39              : struct SvcTunnelChannelHandler::ClientTunnel
      40              : {
      41              :     std::string id;
      42              :     std::string peerUri;
      43              :     DeviceId peerDevice;
      44              :     std::string serviceId;
      45              :     std::string serviceName;
      46              :     std::shared_ptr<asio::ip::tcp::acceptor> acceptor;
      47              :     OnTunnelClosed onClosed;
      48              :     std::atomic_bool closed {false};
      49              :     /// Per-tunnel mutex protecting the active-connection list.
      50              :     std::mutex connsMtx;
      51              :     struct Conn
      52              :     {
      53              :         std::weak_ptr<dhtnet::ChannelSocket> channel;
      54              :         std::weak_ptr<asio::ip::tcp::socket> tcp;
      55              :     };
      56              :     std::vector<Conn> activeConns;
      57              : };
      58              : 
      59          672 : SvcTunnelChannelHandler::SvcTunnelChannelHandler(const std::shared_ptr<JamiAccount>& acc,
      60              :                                                  dhtnet::ConnectionManager& cm,
      61          672 :                                                  std::shared_ptr<asio::io_context> io)
      62              :     : ChannelHandlerInterface()
      63          672 :     , account_(acc)
      64          672 :     , connectionManager_(cm)
      65          672 :     , io_(std::move(io))
      66         1344 :     , rng_(dht::crypto::getDerivedRandomEngine(acc->rand))
      67          672 : {}
      68              : 
      69         1344 : SvcTunnelChannelHandler::~SvcTunnelChannelHandler()
      70              : {
      71          672 :     std::vector<std::shared_ptr<ClientTunnel>> snapshot;
      72              :     {
      73          672 :         std::lock_guard lk(mtx_);
      74          672 :         for (auto& [_id, t] : tunnels_)
      75            0 :             snapshot.push_back(t);
      76          672 :         tunnels_.clear();
      77          672 :     }
      78          672 :     for (auto& t : snapshot) {
      79            0 :         t->closed = true;
      80            0 :         std::error_code ec;
      81            0 :         if (t->acceptor)
      82            0 :             t->acceptor->close(ec);
      83              :     }
      84         1344 : }
      85              : 
      86              : std::string
      87            0 : SvcTunnelChannelHandler::parseServiceId(const std::string& channelName)
      88              : {
      89            0 :     constexpr std::string_view prefix = "svc://";
      90            0 :     if (channelName.size() <= prefix.size())
      91            0 :         return {};
      92            0 :     if (channelName.compare(0, prefix.size(), prefix) != 0)
      93            0 :         return {};
      94            0 :     return channelName.substr(prefix.size());
      95              : }
      96              : 
      97              : void
      98            0 : SvcTunnelChannelHandler::connect(const DeviceId& deviceId,
      99              :                                  const std::string& /*name*/,
     100              :                                  ConnectCb&& cb,
     101              :                                  const std::string& /*connectionType*/,
     102              :                                  bool /*forceNewConnection*/)
     103              : {
     104              :     // Tunnels are managed at a higher level via openTunnel(); the generic
     105              :     // channel-handler entry-point is unused but must not block. Just signal
     106              :     // back asynchronously with a null socket so callers can fall through.
     107            0 :     if (cb && io_)
     108            0 :         asio::post(*io_, [cb = std::move(cb), deviceId]() mutable { cb(nullptr, deviceId); });
     109            0 : }
     110              : 
     111              : bool
     112            0 : SvcTunnelChannelHandler::onRequest(const std::shared_ptr<dht::crypto::Certificate>& peer, const std::string& name)
     113              : {
     114            0 :     if (!peer || !peer->issuer)
     115            0 :         return false;
     116            0 :     auto serviceId = parseServiceId(name);
     117            0 :     if (serviceId.empty())
     118            0 :         return false;
     119            0 :     auto acc = account_.lock();
     120            0 :     if (!acc)
     121            0 :         return false;
     122            0 :     const auto peerUri = peer->issuer->getId().toString();
     123            0 :     auto checker = [&acc](const std::string& uri) {
     124            0 :         return acc->isContact(uri);
     125            0 :     };
     126            0 :     return acc->serviceManager().isAuthorized(serviceId, peerUri, checker);
     127            0 : }
     128              : 
     129              : void
     130            0 : SvcTunnelChannelHandler::onReady(const std::shared_ptr<dht::crypto::Certificate>& peer,
     131              :                                  const std::string& name,
     132              :                                  std::shared_ptr<dhtnet::ChannelSocket> channel)
     133              : {
     134            0 :     if (!channel)
     135            0 :         return;
     136              :     // The initiator side wires its own onClientChannelReady callback via
     137              :     // connectDevice(). Only the receiving (server) side performs the local
     138              :     // TCP connect here.
     139            0 :     if (channel->isInitiator())
     140            0 :         return;
     141            0 :     auto serviceId = parseServiceId(name);
     142            0 :     auto acc = account_.lock();
     143            0 :     if (!acc || serviceId.empty()) {
     144            0 :         channel->shutdown();
     145            0 :         return;
     146              :     }
     147            0 :     auto rec = acc->serviceManager().getService(serviceId);
     148            0 :     if (!rec || !rec->enabled) {
     149            0 :         channel->shutdown();
     150            0 :         return;
     151              :     }
     152            0 :     if (!io_) {
     153            0 :         channel->shutdown();
     154            0 :         return;
     155              :     }
     156            0 :     JAMI_LOG("[SvcTunnel] peer {} opened tunnel to service \"{}\" -> {}:{}",
     157              :              peer && peer->issuer ? peer->issuer->getId().toString() : std::string("<unknown>"),
     158              :              rec->name,
     159              :              rec->localHost,
     160              :              rec->localPort);
     161              : 
     162            0 :     auto tcp = std::make_shared<asio::ip::tcp::socket>(*io_);
     163            0 :     asio::ip::tcp::resolver resolver(*io_);
     164            0 :     std::error_code ec;
     165            0 :     auto endpoints = resolver.resolve(rec->localHost, std::to_string(rec->localPort), ec);
     166            0 :     if (ec) {
     167            0 :         JAMI_WARNING("[SvcTunnel] resolve {}:{} failed: {}", rec->localHost, rec->localPort, ec.message());
     168            0 :         channel->shutdown();
     169            0 :         return;
     170              :     }
     171              : 
     172              :     // Buffer any bytes received from the remote peer before we have a local
     173              :     // TCP connection up; flush them through once async_connect succeeds.
     174              :     struct PreConnectBuf
     175              :     {
     176              :         std::mutex m;
     177              :         std::vector<uint8_t> bytes;
     178              :         bool tcpReady {false};
     179              :         std::shared_ptr<asio::ip::tcp::socket> tcp;
     180              :     };
     181            0 :     auto pre = std::make_shared<PreConnectBuf>();
     182            0 :     pre->tcp = tcp;
     183            0 :     auto channelKeep = channel;
     184            0 :     channel->setOnRecv([pre, channelKeep](const uint8_t* data, size_t size) -> ssize_t {
     185            0 :         std::lock_guard lk(pre->m);
     186            0 :         if (pre->tcpReady) {
     187            0 :             auto buf = std::make_shared<std::vector<uint8_t>>(data, data + size);
     188            0 :             asio::async_write(*pre->tcp,
     189            0 :                               asio::buffer(*buf),
     190            0 :                               [buf, pre, channelKeep](const std::error_code& ec, std::size_t) {
     191            0 :                                   if (ec) {
     192            0 :                                       JAMI_DEBUG("[SvcTunnel] write to TCP failed: {}", ec.message());
     193            0 :                                       channelKeep->shutdown();
     194            0 :                                       std::error_code ig;
     195            0 :                                       pre->tcp->close(ig);
     196              :                                   }
     197            0 :                               });
     198            0 :         } else {
     199            0 :             pre->bytes.insert(pre->bytes.end(), data, data + size);
     200              :         }
     201            0 :         return static_cast<ssize_t>(size);
     202            0 :     });
     203              : 
     204            0 :     asio::async_connect(*tcp,
     205              :                         endpoints,
     206            0 :                         [this, channel = channelKeep, tcp, pre, serviceId](const std::error_code& cec,
     207              :                                                                            const asio::ip::tcp::endpoint&) {
     208            0 :                             if (cec) {
     209            0 :                                 JAMI_WARNING("[SvcTunnel] connect failed: {}", cec.message());
     210            0 :                                 channel->shutdown();
     211            0 :                                 return;
     212              :                             }
     213            0 :                             trackServerChannel(serviceId, channel, tcp);
     214            0 :                             std::vector<uint8_t> drained;
     215              :                             {
     216            0 :                                 std::lock_guard lk(pre->m);
     217            0 :                                 pre->tcpReady = true;
     218            0 :                                 drained.swap(pre->bytes);
     219            0 :                             }
     220            0 :                             if (!drained.empty()) {
     221            0 :                                 auto buf = std::make_shared<std::vector<uint8_t>>(std::move(drained));
     222            0 :                                 asio::async_write(*tcp,
     223            0 :                                                   asio::buffer(*buf),
     224            0 :                                                   [buf, channel, tcp](const std::error_code& ec, std::size_t) {
     225            0 :                                                       if (ec) {
     226            0 :                                                           JAMI_DEBUG("[SvcTunnel] flush failed: {}", ec.message());
     227            0 :                                                           channel->shutdown();
     228            0 :                                                           std::error_code ig;
     229            0 :                                                           tcp->close(ig);
     230              :                                                       }
     231            0 :                                                   });
     232            0 :                             }
     233              :                             // Switch to a hot-path setOnRecv now that tcp is up.
     234            0 :                             channel->setOnRecv([tcp, channel](const uint8_t* data, size_t n) -> ssize_t {
     235            0 :                                 auto buf = std::make_shared<std::vector<uint8_t>>(data, data + n);
     236            0 :                                 asio::async_write(*tcp,
     237            0 :                                                   asio::buffer(*buf),
     238            0 :                                                   [buf, channel, tcp](const std::error_code& ec, std::size_t) {
     239            0 :                                                       if (ec) {
     240            0 :                                                           JAMI_DEBUG("[SvcTunnel] write to TCP failed: {}",
     241              :                                                                      ec.message());
     242            0 :                                                           channel->shutdown();
     243            0 :                                                           std::error_code ig;
     244            0 :                                                           tcp->close(ig);
     245              :                                                       }
     246            0 :                                                   });
     247            0 :                                 return static_cast<ssize_t>(n);
     248            0 :                             });
     249            0 :                             relayTcpToChannel(channel, tcp);
     250            0 :                         });
     251            0 : }
     252              : 
     253              : void
     254            0 : SvcTunnelChannelHandler::relay(std::shared_ptr<dhtnet::ChannelSocket> channel,
     255              :                                std::shared_ptr<asio::ip::tcp::socket> tcp)
     256              : {
     257              :     // ChannelSocket -> TCP
     258            0 :     auto channelHold = channel;
     259            0 :     channel->setOnRecv([tcp, channelHold](const uint8_t* data, size_t size) -> ssize_t {
     260            0 :         auto buf = std::make_shared<std::vector<uint8_t>>(data, data + size);
     261            0 :         asio::async_write(*tcp,
     262            0 :                           asio::buffer(*buf),
     263            0 :                           [buf, channelHold, tcp](const std::error_code& ec, std::size_t /*n*/) {
     264            0 :                               if (ec) {
     265            0 :                                   JAMI_DEBUG("[SvcTunnel] write to TCP failed: {}", ec.message());
     266            0 :                                   channelHold->shutdown();
     267            0 :                                   std::error_code ig;
     268            0 :                                   tcp->close(ig);
     269              :                               }
     270            0 :                           });
     271            0 :         return static_cast<ssize_t>(size);
     272            0 :     });
     273              : 
     274              :     // TCP -> ChannelSocket
     275            0 :     relayTcpToChannel(channel, tcp);
     276            0 : }
     277              : 
     278              : void
     279            0 : SvcTunnelChannelHandler::relayTcpToChannel(std::shared_ptr<dhtnet::ChannelSocket> channel,
     280              :                                            std::shared_ptr<asio::ip::tcp::socket> tcp)
     281              : {
     282            0 :     auto buf = std::make_shared<std::vector<uint8_t>>(kRelayBufSize);
     283            0 :     auto channelKeep = channel;
     284            0 :     auto tcpKeep = tcp;
     285            0 :     auto reader = std::make_shared<std::function<void()>>();
     286            0 :     *reader = [channelKeep, tcpKeep, buf, reader]() {
     287            0 :         tcpKeep->async_read_some(asio::buffer(*buf),
     288            0 :                                  [channelKeep, tcpKeep, buf, reader](const std::error_code& ec, std::size_t n) {
     289            0 :                                      if (ec || n == 0) {
     290            0 :                                          channelKeep->shutdown();
     291            0 :                                          std::error_code ig;
     292            0 :                                          tcpKeep->close(ig);
     293            0 :                                          *reader = nullptr; // break the cycle
     294            0 :                                          return;
     295              :                                      }
     296            0 :                                      std::error_code wec;
     297            0 :                                      channelKeep->write(buf->data(), n, wec);
     298            0 :                                      if (wec) {
     299            0 :                                          channelKeep->shutdown();
     300            0 :                                          std::error_code ig;
     301            0 :                                          tcpKeep->close(ig);
     302            0 :                                          *reader = nullptr; // break the cycle
     303            0 :                                          return;
     304              :                                      }
     305            0 :                                      (*reader)();
     306              :                                  });
     307            0 :     };
     308            0 :     (*reader)();
     309              : 
     310            0 :     channel->onShutdown([tcp = tcpKeep](const std::error_code&) {
     311            0 :         std::error_code ig;
     312            0 :         tcp->close(ig);
     313            0 :     });
     314            0 : }
     315              : 
     316              : std::string
     317            0 : SvcTunnelChannelHandler::openTunnel(std::string peerUri,
     318              :                                     DeviceId peerDevice,
     319              :                                     std::string serviceId,
     320              :                                     std::string serviceName,
     321              :                                     uint16_t localPort,
     322              :                                     OnTunnelOpened onOpened,
     323              :                                     OnTunnelClosed onClosed)
     324              : {
     325            0 :     if (!io_ || serviceId.empty() || peerUri.empty())
     326            0 :         return {};
     327              : 
     328            0 :     auto t = std::make_shared<ClientTunnel>();
     329            0 :     t->id = generateServiceUuid(rng_);
     330            0 :     t->peerUri = std::move(peerUri);
     331            0 :     t->peerDevice = peerDevice;
     332            0 :     t->serviceId = std::move(serviceId);
     333            0 :     t->serviceName = std::move(serviceName);
     334            0 :     t->onClosed = std::move(onClosed);
     335              :     try {
     336              :         // Try dual-stack loopback: prefer IPv4 loopback but fallback to IPv6
     337              :         // for IPv6-only systems where 127.0.0.1 may not be available.
     338            0 :         asio::ip::tcp::endpoint ep(asio::ip::address_v4::loopback(), localPort);
     339            0 :         t->acceptor = std::make_shared<asio::ip::tcp::acceptor>(*io_);
     340            0 :         t->acceptor->open(ep.protocol());
     341            0 :         t->acceptor->set_option(asio::socket_base::reuse_address(true));
     342            0 :         t->acceptor->bind(ep);
     343            0 :         t->acceptor->listen();
     344            0 :     } catch (const std::exception&) {
     345              :         // IPv4 loopback failed, try IPv6 loopback
     346              :         try {
     347            0 :             asio::ip::tcp::endpoint ep6(asio::ip::address_v6::loopback(), localPort);
     348            0 :             t->acceptor = std::make_shared<asio::ip::tcp::acceptor>(*io_);
     349            0 :             t->acceptor->open(ep6.protocol());
     350            0 :             t->acceptor->set_option(asio::socket_base::reuse_address(true));
     351            0 :             t->acceptor->bind(ep6);
     352            0 :             t->acceptor->listen();
     353            0 :         } catch (const std::exception& e) {
     354            0 :             JAMI_WARNING("[SvcTunnel] cannot bind loopback:{}: {}", localPort, e.what());
     355            0 :             return {};
     356            0 :         }
     357            0 :     }
     358              : 
     359            0 :     auto bound = static_cast<uint16_t>(t->acceptor->local_endpoint().port());
     360              : 
     361              :     {
     362            0 :         std::lock_guard lk(mtx_);
     363            0 :         tunnels_[t->id] = t;
     364            0 :     }
     365            0 :     JAMI_LOG("[SvcTunnel] opened tunnel id={} listening on {}:{} -> peer={} service=\"{}\"",
     366              :              t->id,
     367              :              t->acceptor->local_endpoint().address().to_string(),
     368              :              bound,
     369              :              t->peerUri,
     370              :              t->serviceName);
     371            0 :     if (onOpened)
     372            0 :         onOpened(t->id, bound);
     373              : 
     374            0 :     acceptLoop(t);
     375            0 :     return t->id;
     376            0 : }
     377              : 
     378              : void
     379            0 : SvcTunnelChannelHandler::acceptLoop(const std::shared_ptr<ClientTunnel>& tunnel)
     380              : {
     381            0 :     auto self = tunnel;
     382            0 :     auto sock = std::make_shared<asio::ip::tcp::socket>(*io_);
     383            0 :     self->acceptor->async_accept(*sock, [this, self, sock](const std::error_code& ec) {
     384            0 :         if (self->closed) {
     385            0 :             return;
     386              :         }
     387            0 :         if (ec) {
     388            0 :             if (ec == asio::error::operation_aborted)
     389            0 :                 return;
     390            0 :             JAMI_DEBUG("[SvcTunnel] accept error: {}", ec.message());
     391            0 :             return;
     392              :         }
     393              :         // Open a fresh dhtnet channel for this TCP connection.
     394            0 :         std::string channelName = std::string(svc_protocol::TunnelChannelPrefix) + self->serviceId;
     395            0 :         connectionManager_.connectDevice(self->peerDevice,
     396              :                                          channelName,
     397            0 :                                          [this, self, sock](std::shared_ptr<dhtnet::ChannelSocket> channel,
     398              :                                                             const DeviceId&) {
     399            0 :                                              if (!channel) {
     400            0 :                                                  JAMI_WARNING("[SvcTunnel] tunnel id={}: connectDevice to peer "
     401              :                                                               "returned null; closing tunnel",
     402              :                                                               self->id);
     403            0 :                                                  std::error_code ig;
     404            0 :                                                  sock->close(ig);
     405            0 :                                                  closeTunnelInternal(self->id, "connect-failed");
     406            0 :                                                  return;
     407              :                                              }
     408            0 :                                              onClientChannelReady(self, sock, std::move(channel));
     409              :                                          });
     410              :         // Continue accepting.
     411            0 :         acceptLoop(self);
     412            0 :     });
     413            0 : }
     414              : 
     415              : void
     416            0 : SvcTunnelChannelHandler::onClientChannelReady(const std::shared_ptr<ClientTunnel>& tunnel,
     417              :                                               std::shared_ptr<asio::ip::tcp::socket> tcp,
     418              :                                               std::shared_ptr<dhtnet::ChannelSocket> channel)
     419              : {
     420            0 :     if (tunnel->closed) {
     421              :         // The tunnel was torn down while connectDevice was in flight; drop
     422              :         // this late connection instead of wiring up a dangling relay.
     423            0 :         channel->shutdown();
     424            0 :         std::error_code ig;
     425            0 :         tcp->close(ig);
     426            0 :         return;
     427              :     }
     428            0 :     trackClientConnection(tunnel, channel, tcp);
     429            0 :     relay(std::move(channel), std::move(tcp));
     430              : }
     431              : 
     432              : void
     433            0 : SvcTunnelChannelHandler::trackClientConnection(const std::shared_ptr<ClientTunnel>& tunnel,
     434              :                                                const std::shared_ptr<dhtnet::ChannelSocket>& channel,
     435              :                                                const std::shared_ptr<asio::ip::tcp::socket>& tcp)
     436              : {
     437              :     {
     438            0 :         std::lock_guard lk(tunnel->connsMtx);
     439              :         // Compact dead entries opportunistically.
     440            0 :         tunnel->activeConns.erase(std::remove_if(tunnel->activeConns.begin(),
     441            0 :                                                  tunnel->activeConns.end(),
     442            0 :                                                  [](const ClientTunnel::Conn& c) {
     443            0 :                                                      return !c.channel.lock() && !c.tcp.lock();
     444              :                                                  }),
     445            0 :                                   tunnel->activeConns.end());
     446            0 :         tunnel->activeConns.push_back({channel, tcp});
     447            0 :     }
     448            0 :     std::weak_ptr<ClientTunnel> wt = tunnel;
     449            0 :     std::weak_ptr<dhtnet::ChannelSocket> wc = channel;
     450            0 :     channel->onShutdown([wt, wc](const std::error_code&) {
     451            0 :         auto t = wt.lock();
     452            0 :         if (!t)
     453            0 :             return;
     454            0 :         auto c = wc.lock();
     455            0 :         std::lock_guard lk(t->connsMtx);
     456            0 :         t->activeConns.erase(std::remove_if(t->activeConns.begin(),
     457            0 :                                             t->activeConns.end(),
     458            0 :                                             [&](const ClientTunnel::Conn& cn) {
     459            0 :                                                 return cn.channel.lock() == c || !cn.channel.lock();
     460              :                                             }),
     461            0 :                              t->activeConns.end());
     462            0 :     });
     463            0 : }
     464              : 
     465              : bool
     466            0 : SvcTunnelChannelHandler::closeTunnel(const std::string& tunnelId)
     467              : {
     468            0 :     return closeTunnelInternal(tunnelId, "closed");
     469              : }
     470              : 
     471              : bool
     472            0 : SvcTunnelChannelHandler::closeTunnelInternal(const std::string& tunnelId, const std::string& reason)
     473              : {
     474            0 :     std::shared_ptr<ClientTunnel> t;
     475              :     {
     476            0 :         std::lock_guard lk(mtx_);
     477            0 :         auto it = tunnels_.find(tunnelId);
     478            0 :         if (it == tunnels_.end())
     479            0 :             return false;
     480            0 :         t = it->second;
     481            0 :         tunnels_.erase(it);
     482            0 :     }
     483            0 :     t->closed = true;
     484            0 :     std::error_code ec;
     485            0 :     if (t->acceptor)
     486            0 :         t->acceptor->close(ec);
     487              :     // Tear down every per-connection relay (channel + local TCP) currently
     488              :     // serving this tunnel so that close actually severs the byte streams.
     489            0 :     std::vector<ClientTunnel::Conn> conns;
     490              :     {
     491            0 :         std::lock_guard lk(t->connsMtx);
     492            0 :         conns.swap(t->activeConns);
     493            0 :     }
     494            0 :     for (auto& c : conns) {
     495            0 :         if (auto ch = c.channel.lock())
     496            0 :             ch->shutdown();
     497            0 :         if (auto sock = c.tcp.lock()) {
     498            0 :             std::error_code ig;
     499            0 :             sock->close(ig);
     500            0 :         }
     501              :     }
     502            0 :     JAMI_LOG("[SvcTunnel] closed tunnel id={} reason={} ({} live connection(s) torn down)",
     503              :              tunnelId,
     504              :              reason,
     505              :              conns.size());
     506            0 :     if (t->onClosed)
     507            0 :         t->onClosed(tunnelId, reason);
     508            0 :     return true;
     509            0 : }
     510              : 
     511              : void
     512            0 : SvcTunnelChannelHandler::trackServerChannel(const std::string& serviceId,
     513              :                                             const std::shared_ptr<dhtnet::ChannelSocket>& channel,
     514              :                                             const std::shared_ptr<asio::ip::tcp::socket>& tcp)
     515              : {
     516            0 :     if (!channel)
     517            0 :         return;
     518              :     {
     519            0 :         std::lock_guard lk(mtx_);
     520            0 :         auto& vec = serverChannels_[serviceId];
     521              :         // Drop dead entries opportunistically.
     522            0 :         vec.erase(std::remove_if(vec.begin(), vec.end(), [](const ServerConn& c) { return !c.channel.lock(); }),
     523            0 :                   vec.end());
     524            0 :         vec.push_back({channel, tcp});
     525            0 :     }
     526            0 :     std::weak_ptr<dhtnet::ChannelSocket> wc = channel;
     527            0 :     std::weak_ptr<SvcTunnelChannelHandler> wself; // not needed: handler owns map
     528            0 :     auto sid = serviceId;
     529            0 :     channel->onShutdown([this, sid, wc](const std::error_code&) {
     530            0 :         auto c = wc.lock();
     531            0 :         std::lock_guard lk(mtx_);
     532            0 :         auto it = serverChannels_.find(sid);
     533            0 :         if (it == serverChannels_.end())
     534            0 :             return;
     535            0 :         auto& vec = it->second;
     536            0 :         vec.erase(std::remove_if(vec.begin(),
     537              :                                  vec.end(),
     538            0 :                                  [&](const ServerConn& sc) {
     539            0 :                                      auto sch = sc.channel.lock();
     540            0 :                                      return !sch || sch == c;
     541            0 :                                  }),
     542            0 :                   vec.end());
     543            0 :         if (vec.empty())
     544            0 :             serverChannels_.erase(it);
     545            0 :     });
     546            0 : }
     547              : 
     548              : void
     549            0 : SvcTunnelChannelHandler::closeServerChannelsForService(const std::string& serviceId)
     550              : {
     551            0 :     std::vector<ServerConn> conns;
     552              :     {
     553            0 :         std::lock_guard lk(mtx_);
     554            0 :         auto it = serverChannels_.find(serviceId);
     555            0 :         if (it == serverChannels_.end())
     556            0 :             return;
     557            0 :         conns.swap(it->second);
     558            0 :         serverChannels_.erase(it);
     559            0 :     }
     560            0 :     for (auto& c : conns) {
     561            0 :         if (auto ch = c.channel.lock())
     562            0 :             ch->shutdown();
     563            0 :         if (auto sock = c.tcp.lock()) {
     564            0 :             std::error_code ig;
     565            0 :             sock->close(ig);
     566            0 :         }
     567              :     }
     568            0 :     if (!conns.empty())
     569            0 :         JAMI_LOG("[SvcTunnel] closed {} inbound connection(s) for service id={}", conns.size(), serviceId);
     570            0 : }
     571              : 
     572              : std::vector<SvcTunnelChannelHandler::Tunnel>
     573            0 : SvcTunnelChannelHandler::activeTunnels() const
     574              : {
     575            0 :     std::lock_guard lk(mtx_);
     576            0 :     std::vector<Tunnel> out;
     577            0 :     out.reserve(tunnels_.size());
     578            0 :     for (const auto& [_id, t] : tunnels_) {
     579            0 :         Tunnel info;
     580            0 :         info.id = t->id;
     581            0 :         info.peerUri = t->peerUri;
     582            0 :         info.peerDevice = t->peerDevice.toString();
     583            0 :         info.serviceId = t->serviceId;
     584            0 :         info.serviceName = t->serviceName;
     585            0 :         info.localPort = t->acceptor ? static_cast<uint16_t>(t->acceptor->local_endpoint().port()) : 0;
     586            0 :         out.push_back(std::move(info));
     587            0 :     }
     588            0 :     return out;
     589            0 : }
     590              : 
     591              : } // namespace jami
        

Generated by: LCOV version 2.0-1