Coverage Report

Created: 2026-01-10 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libtorrent/src/ip_notifier.cpp
Line
Count
Source
1
/*
2
3
Copyright (c) 2016-2017, Alden Torres
4
Copyright (c) 2016, 2020, Steven Siloti
5
Copyright (c) 2017-2020, Arvid Norberg
6
Copyright (c) 2017, Tim Niederhausen
7
Copyright (c) 2020, Tiger Wang
8
All rights reserved.
9
10
Redistribution and use in source and binary forms, with or without
11
modification, are permitted provided that the following conditions
12
are met:
13
14
    * Redistributions of source code must retain the above copyright
15
      notice, this list of conditions and the following disclaimer.
16
    * Redistributions in binary form must reproduce the above copyright
17
      notice, this list of conditions and the following disclaimer in
18
      the documentation and/or other materials provided with the distribution.
19
    * Neither the name of the author nor the names of its
20
      contributors may be used to endorse or promote products derived
21
      from this software without specific prior written permission.
22
23
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
POSSIBILITY OF SUCH DAMAGE.
34
35
*/
36
37
#include "libtorrent/aux_/ip_notifier.hpp"
38
#include "libtorrent/assert.hpp"
39
40
#if defined TORRENT_BUILD_SIMULATOR
41
// TODO: simulator support
42
#elif TORRENT_USE_NETLINK
43
#include "libtorrent/netlink.hpp"
44
#include "libtorrent/socket.hpp"
45
#include <array>
46
#include <unordered_map>
47
#elif TORRENT_USE_SYSTEMCONFIGURATION || TORRENT_USE_SC_NETWORK_REACHABILITY
48
#include <SystemConfiguration/SystemConfiguration.h>
49
#elif defined TORRENT_WINDOWS
50
#include "libtorrent/aux_/throw.hpp"
51
#include "libtorrent/aux_/disable_warnings_push.hpp"
52
#include <iphlpapi.h>
53
#ifdef TORRENT_WINRT
54
#include <netioapi.h>
55
#endif
56
#include <mutex>
57
#include "libtorrent/aux_/disable_warnings_pop.hpp"
58
#endif
59
60
#include "libtorrent/aux_/netlink_utils.hpp"
61
62
namespace libtorrent { namespace aux {
63
64
namespace {
65
66
#if (TORRENT_USE_SYSTEMCONFIGURATION || TORRENT_USE_SC_NETWORK_REACHABILITY) && \
67
  !defined TORRENT_BUILD_SIMULATOR
68
69
// common utilities for Mac and iOS
70
template <typename T> void CFRefRetain(T h) { CFRetain(h); }
71
template <typename T> void CFRefRelease(T h) { CFRelease(h); }
72
73
template <typename T
74
  , void (*Retain)(T) = CFRefRetain<T>, void (*Release)(T) = CFRefRelease<T>>
75
struct CFRef
76
{
77
  CFRef() = default;
78
  explicit CFRef(T h) : m_h(h) {} // take ownership
79
  ~CFRef() { release(); }
80
81
  CFRef(CFRef&& rhs) : m_h(rhs.m_h) { rhs.m_h = nullptr; }
82
  CFRef& operator=(CFRef&& rhs) &
83
  {
84
    if (m_h == rhs.m_h) return *this;
85
    release();
86
    m_h = rhs.m_h;
87
    rhs.m_h = nullptr;
88
    return *this;
89
  }
90
91
  CFRef(CFRef const& rhs) : m_h(rhs.m_h) { retain(); }
92
  CFRef& operator=(CFRef const& rhs) &
93
  {
94
    if (m_h == rhs.m_h) return *this;
95
    release();
96
    m_h = rhs.m_h;
97
    retain();
98
    return *this;
99
  }
100
101
  CFRef& operator=(T h) & { m_h = h; return *this;}
102
  CFRef& operator=(std::nullptr_t) & { release(); return *this;}
103
104
  T get() const { return m_h; }
105
  explicit operator bool() const { return m_h != nullptr; }
106
107
private:
108
  T m_h = nullptr; // handle
109
110
  void retain() { if (m_h != nullptr) Retain(m_h); }
111
  void release() { if (m_h != nullptr) Release(m_h); m_h = nullptr; }
112
};
113
114
void CFDispatchRetain(dispatch_queue_t q) { dispatch_retain(q); }
115
void CFDispatchRelease(dispatch_queue_t q) { dispatch_release(q); }
116
using CFDispatchRef = CFRef<dispatch_queue_t, CFDispatchRetain, CFDispatchRelease>;
117
#endif
118
119
#if defined TORRENT_BUILD_SIMULATOR
120
struct ip_change_notifier_impl final : ip_change_notifier
121
{
122
  explicit ip_change_notifier_impl(io_context& ios)
123
    : m_ios(ios) {}
124
125
  void async_wait(std::function<void(error_code const&)> cb) override
126
  {
127
    post(m_ios, [cb1=std::move(cb)]()
128
    { cb1(make_error_code(boost::system::errc::not_supported)); });
129
  }
130
131
  void cancel() override {}
132
133
private:
134
  io_context& m_ios;
135
};
136
#elif TORRENT_USE_NETLINK
137
struct ip_change_notifier_impl final : ip_change_notifier
138
{
139
  explicit ip_change_notifier_impl(io_context& ios)
140
0
    : m_socket(ios
141
0
      , netlink::endpoint(netlink(NETLINK_ROUTE), RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR))
142
0
  {
143
    // Linux can generate ENOBUFS if the socket's buffers are full
144
    // don't treat it as an error
145
0
    error_code ec;
146
0
    m_socket.set_option(libtorrent::no_enobufs(true), ec);
147
0
  }
148
149
  // non-copyable
150
  ip_change_notifier_impl(ip_change_notifier_impl const&) = delete;
151
  ip_change_notifier_impl& operator=(ip_change_notifier_impl const&) = delete;
152
153
  void async_wait(std::function<void(error_code const&)> cb) override
154
0
  {
155
0
    m_socket.async_receive(boost::asio::buffer(m_buf)
156
0
      , [cb1=std::move(cb), this] (error_code const& ec, std::size_t const bytes_transferred)
157
0
      {
158
0
        if (ec) cb1(ec);
159
0
        else this->on_notify(int(bytes_transferred), std::move(cb1));
160
0
      });
161
0
  }
162
163
  void cancel() override
164
0
  { m_socket.cancel();}
165
166
private:
167
  netlink::socket m_socket;
168
  std::array<char, 4096> m_buf;
169
170
  void on_notify(int len, std::function<void(error_code const& ec)> cb)
171
0
  {
172
0
    bool pertinent = false;
173
174
0
    for (auto const* nh = reinterpret_cast<nlmsghdr const*>(this->m_buf.data());
175
0
      nlmsg_ok (nh, len);
176
0
      nh = nlmsg_next(nh, len))
177
0
    {
178
0
      if (nh->nlmsg_type != RTM_NEWADDR &&
179
0
        nh->nlmsg_type != RTM_DELADDR)
180
0
        continue;
181
0
      pertinent = true;
182
0
    }
183
184
0
    if (!pertinent)
185
0
    {
186
0
      m_socket.async_receive(boost::asio::buffer(m_buf)
187
0
        , [cb1=std::move(cb), this] (error_code const& ec, std::size_t const bytes_transferred)
188
0
        {
189
0
          if (ec) cb1(ec);
190
0
          else this->on_notify(int(bytes_transferred), std::move(cb1));
191
0
        });
192
0
    }
193
0
    else
194
0
    {
195
      // on linux we could parse the message to get information about the
196
      // change but Windows requires the application to enumerate the
197
      // interfaces after a notification so do that for Linux as well to
198
      // minimize the difference between platforms
199
0
      cb(error_code());
200
0
    }
201
0
  }
202
};
203
204
#elif TORRENT_USE_SC_NETWORK_REACHABILITY
205
206
CFRef<SCNetworkReachabilityRef> create_reachability(SCNetworkReachabilityCallBack callback
207
  , void* context_info)
208
{
209
  TORRENT_ASSERT(callback != nullptr);
210
211
  sockaddr_in addr = {};
212
  addr.sin_len = sizeof(addr);
213
  addr.sin_family = AF_INET;
214
215
  CFRef<SCNetworkReachabilityRef> reach{SCNetworkReachabilityCreateWithAddress(nullptr
216
    , reinterpret_cast<sockaddr const*>(&addr))};
217
  if (!reach)
218
    return CFRef<SCNetworkReachabilityRef>();
219
220
  SCNetworkReachabilityContext context = {0, nullptr, nullptr, nullptr, nullptr};
221
  context.info = context_info;
222
223
  return SCNetworkReachabilitySetCallback(reach.get(), callback, &context)
224
    ? reach : CFRef<SCNetworkReachabilityRef>();
225
}
226
227
struct ip_change_notifier_impl final : ip_change_notifier
228
{
229
  explicit ip_change_notifier_impl(io_context& ios)
230
    : m_ios(ios)
231
  {
232
    m_queue = dispatch_queue_create("libtorrent.IPChangeNotifierQueue", nullptr);
233
    m_reach = create_reachability(
234
      [](SCNetworkReachabilityRef /*target*/, SCNetworkReachabilityFlags /*flags*/, void *info)
235
      {
236
        auto obj = static_cast<ip_change_notifier_impl*>(info);
237
        post(obj->m_ios, [obj]()
238
        {
239
          if (!obj->m_cb) return;
240
          auto cb = std::move(obj->m_cb);
241
          obj->m_cb = nullptr;
242
          cb(error_code());
243
        });
244
      }, this);
245
246
    if (!m_queue || !m_reach
247
      || !SCNetworkReachabilitySetDispatchQueue(m_reach.get(), m_queue.get()))
248
      cancel();
249
  }
250
251
  // non-copyable
252
  ip_change_notifier_impl(ip_change_notifier_impl const&) = delete;
253
  ip_change_notifier_impl& operator=(ip_change_notifier_impl const&) = delete;
254
255
  ~ip_change_notifier_impl() override
256
  { cancel(); }
257
258
  void async_wait(std::function<void(error_code const&)> cb) override
259
  {
260
    if (m_queue)
261
      m_cb = std::move(cb);
262
    else
263
      post(m_ios, [cb]()
264
      { cb(make_error_code(boost::system::errc::not_supported)); });
265
  }
266
267
  void cancel() override
268
  {
269
    if (m_reach)
270
      SCNetworkReachabilitySetDispatchQueue(m_reach.get(), nullptr);
271
272
    m_cb = nullptr;
273
    m_reach = nullptr;
274
    m_queue = nullptr;
275
  }
276
277
private:
278
  io_context& m_ios;
279
  CFDispatchRef m_queue;
280
  CFRef<SCNetworkReachabilityRef> m_reach;
281
  std::function<void(error_code const&)> m_cb = nullptr;
282
};
283
#elif TORRENT_USE_SYSTEMCONFIGURATION
284
// see https://developer.apple.com/library/content/technotes/tn1145/_index.html
285
CFRef<CFMutableArrayRef> create_keys_array()
286
{
287
  CFRef<CFMutableArrayRef> keys{CFArrayCreateMutable(nullptr
288
    , 0, &kCFTypeArrayCallBacks)};
289
290
  // "State:/Network/Interface/[^/]+/IPv4"
291
  CFRef<CFStringRef> key{SCDynamicStoreKeyCreateNetworkInterfaceEntity(nullptr
292
    , kSCDynamicStoreDomainState, kSCCompAnyRegex, kSCEntNetIPv4)};
293
  CFArrayAppendValue(keys.get(), key.get());
294
295
  // NOTE: for IPv6, you can replicate the above setup with kSCEntNetIPv6
296
  // but due to the current state of most common configurations, where
297
  // IPv4 is used alongside with IPv6, you will end up with twice the
298
  // notifications for the same change
299
300
  return keys;
301
}
302
303
CFRef<SCDynamicStoreRef> create_dynamic_store(SCDynamicStoreCallBack callback, void* context_info)
304
{
305
  TORRENT_ASSERT(callback != nullptr);
306
307
  SCDynamicStoreContext context = {0, nullptr, nullptr, nullptr, nullptr};
308
  context.info = context_info;
309
310
#if defined __clang__
311
#pragma clang diagnostic push
312
#pragma clang diagnostic ignored "-Wold-style-cast"
313
#endif
314
  CFRef<SCDynamicStoreRef> store{SCDynamicStoreCreate(nullptr
315
    , CFSTR("libtorrent.IPChangeNotifierStore"), callback, &context)};
316
#if defined __clang__
317
#pragma clang diagnostic pop
318
#endif
319
  if (!store)
320
    return CFRef<SCDynamicStoreRef>();
321
322
  CFRef<CFMutableArrayRef> keys = create_keys_array();
323
  return SCDynamicStoreSetNotificationKeys(store.get(), nullptr, keys.get())
324
    ? store : CFRef<SCDynamicStoreRef>();
325
}
326
327
struct ip_change_notifier_impl final : ip_change_notifier
328
{
329
  explicit ip_change_notifier_impl(io_context& ios)
330
    : m_ios(ios)
331
  {
332
    m_queue = dispatch_queue_create("libtorrent.IPChangeNotifierQueue", nullptr);
333
    m_store = create_dynamic_store(
334
      [](SCDynamicStoreRef /*store*/, CFArrayRef /*changedKeys*/, void *info)
335
      {
336
        auto obj = static_cast<ip_change_notifier_impl*>(info);
337
        post(obj->m_ios, [obj]()
338
        {
339
          if (!obj->m_cb) return;
340
          auto cb = std::move(obj->m_cb);
341
          obj->m_cb = nullptr;
342
          cb(error_code());
343
        });
344
      }, this);
345
346
    if (!m_queue || !m_store
347
      || !SCDynamicStoreSetDispatchQueue(m_store.get(), m_queue.get()))
348
      cancel();
349
  }
350
351
  // non-copyable
352
  ip_change_notifier_impl(ip_change_notifier_impl const&) = delete;
353
  ip_change_notifier_impl& operator=(ip_change_notifier_impl const&) = delete;
354
355
  ~ip_change_notifier_impl() override
356
  { cancel(); }
357
358
  void async_wait(std::function<void(error_code const&)> cb) override
359
  {
360
    if (m_queue)
361
      m_cb = std::move(cb);
362
    else
363
      post(m_ios, [cb]()
364
      { cb(make_error_code(boost::system::errc::not_supported)); });
365
  }
366
367
  void cancel() override
368
  {
369
    if (m_store)
370
      SCDynamicStoreSetDispatchQueue(m_store.get(), nullptr);
371
372
    m_cb = nullptr;
373
    m_store = nullptr;
374
    m_queue = nullptr;
375
  }
376
377
private:
378
  io_context& m_ios;
379
  CFDispatchRef m_queue;
380
  CFRef<SCDynamicStoreRef> m_store;
381
  std::function<void(error_code const&)> m_cb = nullptr;
382
};
383
384
#elif defined TORRENT_WINDOWS
385
struct ip_change_notifier_impl final : ip_change_notifier
386
{
387
  explicit ip_change_notifier_impl(io_context& ios)
388
    : m_ios(ios)
389
  {
390
    NotifyUnicastIpAddressChange(AF_UNSPEC, address_change_cb, this, false, &m_hnd);
391
  }
392
393
  // non-copyable
394
  ip_change_notifier_impl(ip_change_notifier_impl const&) = delete;
395
  ip_change_notifier_impl& operator=(ip_change_notifier_impl const&) = delete;
396
397
  // non-moveable
398
  ip_change_notifier_impl(ip_change_notifier_impl&&) = delete;
399
  ip_change_notifier_impl& operator=(ip_change_notifier_impl&&) = delete;
400
401
  ~ip_change_notifier_impl() override
402
  {
403
    if (m_hnd != nullptr)
404
    {
405
      CancelMibChangeNotify2(m_hnd);
406
      m_hnd = nullptr;
407
    }
408
  }
409
410
  void async_wait(std::function<void(error_code const&)> cb) override
411
  {
412
    if (m_hnd == nullptr)
413
    {
414
      cb(make_error_code(boost::system::errc::not_supported));
415
      return;
416
    }
417
418
    std::lock_guard<std::mutex> l(m_cb_mutex);
419
    m_cb.emplace_back(std::move(cb));
420
  }
421
422
  void cancel() override
423
  {
424
    std::vector<std::function<void(error_code const&)>> cbs;
425
    {
426
      std::lock_guard<std::mutex> l(m_cb_mutex);
427
      cbs = std::move(m_cb);
428
    }
429
    for (auto& cb : cbs) cb(make_error_code(boost::asio::error::operation_aborted));
430
  }
431
432
private:
433
  static void WINAPI address_change_cb(void* ctx, MIB_UNICASTIPADDRESS_ROW*, MIB_NOTIFICATION_TYPE)
434
  {
435
    ip_change_notifier_impl* impl = static_cast<ip_change_notifier_impl*>(ctx);
436
    std::vector<std::function<void(error_code const&)>> cbs;
437
    {
438
      std::lock_guard<std::mutex> l(impl->m_cb_mutex);
439
      cbs = std::move(impl->m_cb);
440
    }
441
    post(impl->m_ios, [c = std::move(cbs)]()
442
    {
443
      for (auto& cb : c) cb(error_code());
444
    });
445
  }
446
447
  io_context& m_ios;
448
  HANDLE m_hnd = nullptr;
449
  // address_change_cb gets invoked from a separate worker thread so the callbacks
450
  // vector must be protected by a mutex
451
  std::mutex m_cb_mutex;
452
  std::vector<std::function<void(error_code const&)>> m_cb;
453
};
454
#else
455
struct ip_change_notifier_impl final : ip_change_notifier
456
{
457
  explicit ip_change_notifier_impl(io_context& ios)
458
    : m_ios(ios) {}
459
460
  void async_wait(std::function<void(error_code const&)> cb) override
461
  {
462
    post(m_ios, [cb]()
463
    { cb(make_error_code(boost::system::errc::not_supported)); });
464
  }
465
466
  void cancel() override {}
467
468
private:
469
  io_context& m_ios;
470
};
471
#endif
472
473
} // anonymous namespace
474
475
  std::unique_ptr<ip_change_notifier> create_ip_notifier(io_context& ios)
476
0
  {
477
0
    return std::make_unique<ip_change_notifier_impl>(ios);
478
0
  }
479
}}