Coverage Report

Created: 2025-06-13 06:55

/src/glib/gio/gnetworkmonitorbase.c
Line
Count
Source (jump to first uncovered line)
1
/* GIO - GLib Input, Output and Streaming Library
2
 *
3
 * Copyright 2011 Red Hat, Inc.
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General
18
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "config.h"
22
23
#include "gnetworkmonitorbase.h"
24
#include "ginetaddress.h"
25
#include "ginetaddressmask.h"
26
#include "ginetsocketaddress.h"
27
#include "ginitable.h"
28
#include "gioerror.h"
29
#include "giomodule-priv.h"
30
#include "gnetworkmonitor.h"
31
#include "gsocketaddressenumerator.h"
32
#include "gsocketconnectable.h"
33
#include "gtask.h"
34
#include "glibintl.h"
35
36
static void g_network_monitor_base_iface_init (GNetworkMonitorInterface *iface);
37
static void g_network_monitor_base_initable_iface_init (GInitableIface *iface);
38
39
enum
40
{
41
  PROP_0,
42
43
  PROP_NETWORK_AVAILABLE,
44
  PROP_NETWORK_METERED,
45
  PROP_CONNECTIVITY
46
};
47
48
struct _GNetworkMonitorBasePrivate
49
{
50
  GHashTable   *networks  /* (element-type GInetAddressMask) (owned) */;
51
  gboolean      have_ipv4_default_route;
52
  gboolean      have_ipv6_default_route;
53
  gboolean      is_available;
54
55
  GMainContext *context;
56
  GSource      *network_changed_source;
57
  gboolean      initializing;
58
};
59
60
static guint network_changed_signal = 0;
61
62
static void queue_network_changed (GNetworkMonitorBase *monitor);
63
static guint inet_address_mask_hash (gconstpointer key);
64
static gboolean inet_address_mask_equal (gconstpointer a,
65
                                         gconstpointer b);
66
67
G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorBase, g_network_monitor_base, G_TYPE_OBJECT,
68
                         G_ADD_PRIVATE (GNetworkMonitorBase)
69
                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
70
                                                g_network_monitor_base_initable_iface_init)
71
                         G_IMPLEMENT_INTERFACE (G_TYPE_NETWORK_MONITOR,
72
                                                g_network_monitor_base_iface_init)
73
                         _g_io_modules_ensure_extension_points_registered ();
74
                         g_io_extension_point_implement (G_NETWORK_MONITOR_EXTENSION_POINT_NAME,
75
                                                         g_define_type_id,
76
                                                         "base",
77
                                                         0))
78
79
static void
80
g_network_monitor_base_init (GNetworkMonitorBase *monitor)
81
0
{
82
0
  monitor->priv = g_network_monitor_base_get_instance_private (monitor);
83
0
  monitor->priv->networks = g_hash_table_new_full (inet_address_mask_hash,
84
0
                                                   inet_address_mask_equal,
85
0
                                                   g_object_unref, NULL);
86
0
  monitor->priv->context = g_main_context_get_thread_default ();
87
0
  if (monitor->priv->context)
88
0
    g_main_context_ref (monitor->priv->context);
89
90
0
  monitor->priv->initializing = TRUE;
91
0
}
92
93
static void
94
g_network_monitor_base_constructed (GObject *object)
95
0
{
96
0
  GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object);
97
98
0
  if (G_OBJECT_TYPE (monitor) == G_TYPE_NETWORK_MONITOR_BASE)
99
0
    {
100
0
      GInetAddressMask *mask;
101
102
      /* We're the dumb base class, not a smarter subclass. So just
103
       * assume that the network is available.
104
       */
105
0
      mask = g_inet_address_mask_new_from_string ("0.0.0.0/0", NULL);
106
0
      g_network_monitor_base_add_network (monitor, mask);
107
0
      g_object_unref (mask);
108
109
0
      mask = g_inet_address_mask_new_from_string ("::/0", NULL);
110
0
      if (mask)
111
0
        {
112
          /* On some environments (for example Windows without IPv6 support
113
           * enabled) the string "::/0" can't be processed and causes
114
           * g_inet_address_mask_new_from_string to return NULL */
115
0
          g_network_monitor_base_add_network (monitor, mask);
116
0
          g_object_unref (mask);
117
0
        }
118
0
    }
119
0
}
120
121
static void
122
g_network_monitor_base_get_property (GObject    *object,
123
                                     guint       prop_id,
124
                                     GValue     *value,
125
                                     GParamSpec *pspec)
126
0
{
127
0
  GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object);
128
129
0
  switch (prop_id)
130
0
    {
131
0
    case PROP_NETWORK_AVAILABLE:
132
0
      g_value_set_boolean (value, monitor->priv->is_available);
133
0
      break;
134
135
0
    case PROP_NETWORK_METERED:
136
      /* Default to FALSE in the unknown case. */
137
0
      g_value_set_boolean (value, FALSE);
138
0
      break;
139
140
0
    case PROP_CONNECTIVITY:
141
0
      g_value_set_enum (value,
142
0
                        monitor->priv->is_available ?
143
0
                        G_NETWORK_CONNECTIVITY_FULL :
144
0
                        G_NETWORK_CONNECTIVITY_LOCAL);
145
0
      break;
146
147
0
    default:
148
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
149
0
      break;
150
0
    }
151
152
0
}
153
154
static void
155
g_network_monitor_base_finalize (GObject *object)
156
0
{
157
0
  GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object);
158
159
0
  g_hash_table_unref (monitor->priv->networks);
160
0
  if (monitor->priv->network_changed_source)
161
0
    {
162
0
      g_source_destroy (monitor->priv->network_changed_source);
163
0
      g_source_unref (monitor->priv->network_changed_source);
164
0
    }
165
0
  if (monitor->priv->context)
166
0
    g_main_context_unref (monitor->priv->context);
167
168
0
  G_OBJECT_CLASS (g_network_monitor_base_parent_class)->finalize (object);
169
0
}
170
171
static void
172
g_network_monitor_base_class_init (GNetworkMonitorBaseClass *monitor_class)
173
0
{
174
0
  GObjectClass *gobject_class = G_OBJECT_CLASS (monitor_class);
175
176
0
  gobject_class->constructed  = g_network_monitor_base_constructed;
177
0
  gobject_class->get_property = g_network_monitor_base_get_property;
178
0
  gobject_class->finalize     = g_network_monitor_base_finalize;
179
180
0
  g_object_class_override_property (gobject_class, PROP_NETWORK_AVAILABLE, "network-available");
181
0
  g_object_class_override_property (gobject_class, PROP_NETWORK_METERED, "network-metered");
182
0
  g_object_class_override_property (gobject_class, PROP_CONNECTIVITY, "connectivity");
183
0
}
184
185
static gboolean
186
g_network_monitor_base_can_reach_sockaddr (GNetworkMonitorBase *base,
187
                                           GSocketAddress *sockaddr)
188
0
{
189
0
  GInetAddress *iaddr;
190
0
  GHashTableIter iter;
191
0
  gpointer key;
192
193
0
  if (!G_IS_INET_SOCKET_ADDRESS (sockaddr))
194
0
    return FALSE;
195
196
0
  iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sockaddr));
197
0
  g_hash_table_iter_init (&iter, base->priv->networks);
198
0
  while (g_hash_table_iter_next (&iter, &key, NULL))
199
0
    {
200
0
      GInetAddressMask *mask = key;
201
0
      if (g_inet_address_mask_matches (mask, iaddr))
202
0
        return TRUE;
203
0
    }
204
205
0
  return FALSE;
206
0
}
207
208
static gboolean
209
g_network_monitor_base_can_reach (GNetworkMonitor      *monitor,
210
                                  GSocketConnectable   *connectable,
211
                                  GCancellable         *cancellable,
212
                                  GError              **error)
213
0
{
214
0
  GNetworkMonitorBase *base = G_NETWORK_MONITOR_BASE (monitor);
215
0
  GSocketAddressEnumerator *enumerator;
216
0
  GSocketAddress *addr;
217
218
0
  if (g_hash_table_size (base->priv->networks) == 0)
219
0
    {
220
0
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE,
221
0
                           _("Network unreachable"));
222
0
      return FALSE;
223
0
    }
224
225
0
  enumerator = g_socket_connectable_proxy_enumerate (connectable);
226
0
  addr = g_socket_address_enumerator_next (enumerator, cancellable, error);
227
0
  if (!addr)
228
0
    {
229
      /* Either the user cancelled, or DNS resolution failed */
230
0
      g_object_unref (enumerator);
231
0
      return FALSE;
232
0
    }
233
234
0
  if (base->priv->have_ipv4_default_route &&
235
0
      base->priv->have_ipv6_default_route)
236
0
    {
237
0
      g_object_unref (enumerator);
238
0
      g_object_unref (addr);
239
0
      return TRUE;
240
0
    }
241
242
0
  while (addr)
243
0
    {
244
0
      if (g_network_monitor_base_can_reach_sockaddr (base, addr))
245
0
        {
246
0
          g_object_unref (addr);
247
0
          g_object_unref (enumerator);
248
0
          return TRUE;
249
0
        }
250
251
0
      g_object_unref (addr);
252
0
      addr = g_socket_address_enumerator_next (enumerator, cancellable, error);
253
0
    }
254
0
  g_object_unref (enumerator);
255
256
0
  if (error && !*error)
257
0
    {
258
0
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE,
259
0
                           _("Host unreachable"));
260
0
    }
261
0
  return FALSE;
262
0
}
263
264
static void
265
can_reach_async_got_address (GObject      *object,
266
                             GAsyncResult *result,
267
                             gpointer      user_data)
268
0
{
269
0
  GSocketAddressEnumerator *enumerator = G_SOCKET_ADDRESS_ENUMERATOR (object);
270
0
  GTask *task = user_data;
271
0
  GNetworkMonitorBase *base = g_task_get_source_object (task);
272
0
  GSocketAddress *addr;
273
0
  GError *error = NULL;
274
275
0
  addr = g_socket_address_enumerator_next_finish (enumerator, result, &error);
276
0
  if (!addr)
277
0
    {
278
0
      if (error)
279
0
        {
280
          /* Either the user cancelled, or DNS resolution failed */
281
0
          g_task_return_error (task, error);
282
0
          g_object_unref (task);
283
0
          return;
284
0
        }
285
0
      else
286
0
        {
287
          /* Resolved all addresses, none matched */
288
0
          g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE,
289
0
                                   _("Host unreachable"));
290
0
          g_object_unref (task);
291
0
          return;
292
0
        }
293
0
    }
294
295
0
  if (g_network_monitor_base_can_reach_sockaddr (base, addr))
296
0
    {
297
0
      g_object_unref (addr);
298
0
      g_task_return_boolean (task, TRUE);
299
0
      g_object_unref (task);
300
0
      return;
301
0
    }
302
0
  g_object_unref (addr);
303
304
0
  g_socket_address_enumerator_next_async (enumerator,
305
0
                                          g_task_get_cancellable (task),
306
0
                                          can_reach_async_got_address, task);
307
0
}
308
309
static void
310
g_network_monitor_base_can_reach_async (GNetworkMonitor     *monitor,
311
                                        GSocketConnectable  *connectable,
312
                                        GCancellable        *cancellable,
313
                                        GAsyncReadyCallback  callback,
314
                                        gpointer             user_data)
315
0
{
316
0
  GTask *task;
317
0
  GSocketAddressEnumerator *enumerator;
318
319
0
  task = g_task_new (monitor, cancellable, callback, user_data);
320
0
  g_task_set_source_tag (task, g_network_monitor_base_can_reach_async);
321
322
0
  if (g_hash_table_size (G_NETWORK_MONITOR_BASE (monitor)->priv->networks) == 0)
323
0
    {
324
0
      g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE,
325
0
                               _("Network unreachable"));
326
0
      g_object_unref (task);
327
0
      return;
328
0
    }
329
330
0
  enumerator = g_socket_connectable_proxy_enumerate (connectable);
331
0
  g_socket_address_enumerator_next_async (enumerator, cancellable,
332
0
                                          can_reach_async_got_address, task);
333
0
  g_object_unref (enumerator);
334
0
}
335
336
static gboolean
337
g_network_monitor_base_can_reach_finish (GNetworkMonitor  *monitor,
338
                                         GAsyncResult     *result,
339
                                         GError          **error)
340
0
{
341
0
  g_return_val_if_fail (g_task_is_valid (result, monitor), FALSE);
342
343
0
  return g_task_propagate_boolean (G_TASK (result), error);
344
0
}
345
346
static void
347
g_network_monitor_base_iface_init (GNetworkMonitorInterface *monitor_iface)
348
0
{
349
0
  monitor_iface->can_reach = g_network_monitor_base_can_reach;
350
0
  monitor_iface->can_reach_async = g_network_monitor_base_can_reach_async;
351
0
  monitor_iface->can_reach_finish = g_network_monitor_base_can_reach_finish;
352
353
0
  network_changed_signal = g_signal_lookup ("network-changed", G_TYPE_NETWORK_MONITOR);
354
0
}
355
356
static gboolean
357
g_network_monitor_base_initable_init (GInitable     *initable,
358
                                      GCancellable  *cancellable,
359
                                      GError       **error)
360
0
{
361
0
  GNetworkMonitorBase *base = G_NETWORK_MONITOR_BASE (initable);
362
363
0
  base->priv->initializing = FALSE;
364
365
0
  return TRUE;
366
0
}
367
368
static void
369
g_network_monitor_base_initable_iface_init (GInitableIface *iface)
370
0
{
371
0
  iface->init = g_network_monitor_base_initable_init;
372
0
}
373
374
static guint
375
inet_address_mask_hash (gconstpointer key)
376
0
{
377
0
  GInetAddressMask *mask = G_INET_ADDRESS_MASK (key);
378
0
  guint addr_hash;
379
0
  guint mask_length = g_inet_address_mask_get_length (mask);
380
0
  GInetAddress *addr = g_inet_address_mask_get_address (mask);
381
0
  const guint8 *bytes = g_inet_address_to_bytes (addr);
382
0
  gsize bytes_length = g_inet_address_get_native_size (addr);
383
384
0
  union
385
0
    {
386
0
      const guint8 *bytes;
387
0
      guint32 *hash32;
388
0
      guint64 *hash64;
389
0
    } integerifier;
390
391
  /* If we can fit the entire address into the hash key, do it. Don’t worry
392
   * about endianness; the address should always be in network endianness. */
393
0
  if (bytes_length == sizeof (guint32))
394
0
    {
395
0
      integerifier.bytes = bytes;
396
0
      addr_hash = *integerifier.hash32;
397
0
    }
398
0
  else if (bytes_length == sizeof (guint64))
399
0
    {
400
0
      integerifier.bytes = bytes;
401
0
      addr_hash = *integerifier.hash64;
402
0
    }
403
0
  else
404
0
    {
405
0
      gsize i;
406
407
      /* Otherwise, fall back to adding the bytes together. We do this, rather
408
       * than XORing them, as routes often have repeated tuples which would
409
       * cancel out under XOR. */
410
0
      addr_hash = 0;
411
0
      for (i = 0; i < bytes_length; i++)
412
0
        addr_hash += bytes[i];
413
0
    }
414
415
0
  return addr_hash + mask_length;;
416
0
}
417
418
static gboolean
419
inet_address_mask_equal (gconstpointer a,
420
                         gconstpointer b)
421
0
{
422
0
  GInetAddressMask *mask_a = G_INET_ADDRESS_MASK (a);
423
0
  GInetAddressMask *mask_b = G_INET_ADDRESS_MASK (b);
424
425
0
  return g_inet_address_mask_equal (mask_a, mask_b);
426
0
}
427
428
static gboolean
429
emit_network_changed (gpointer user_data)
430
0
{
431
0
  GNetworkMonitorBase *monitor = user_data;
432
0
  gboolean is_available;
433
434
0
  if (g_source_is_destroyed (g_main_current_source ()))
435
0
    return FALSE;
436
437
0
  g_object_ref (monitor);
438
439
0
  is_available = (monitor->priv->have_ipv4_default_route ||
440
0
                  monitor->priv->have_ipv6_default_route);
441
0
  if (monitor->priv->is_available != is_available)
442
0
    {
443
0
      monitor->priv->is_available = is_available;
444
0
      g_object_notify (G_OBJECT (monitor), "network-available");
445
0
    }
446
447
0
  g_signal_emit (monitor, network_changed_signal, 0, is_available);
448
449
0
  g_source_unref (monitor->priv->network_changed_source);
450
0
  monitor->priv->network_changed_source = NULL;
451
452
0
  g_object_unref (monitor);
453
0
  return FALSE;
454
0
}
455
456
static void
457
queue_network_changed (GNetworkMonitorBase *monitor)
458
0
{
459
0
  if (!monitor->priv->network_changed_source &&
460
0
      !monitor->priv->initializing)
461
0
    {
462
0
      GSource *source;
463
464
0
      source = g_idle_source_new ();
465
      /* Use G_PRIORITY_HIGH_IDLE priority so that multiple
466
       * network-change-related notifications coming in at
467
       * G_PRIORITY_DEFAULT will get coalesced into one signal
468
       * emission.
469
       */
470
0
      g_source_set_priority (source, G_PRIORITY_HIGH_IDLE);
471
0
      g_source_set_callback (source, emit_network_changed, monitor, NULL);
472
0
      g_source_set_static_name (source, "[gio] emit_network_changed");
473
0
      g_source_attach (source, monitor->priv->context);
474
0
      monitor->priv->network_changed_source = source;
475
0
    }
476
477
  /* Normally we wait to update is_available until we emit the signal,
478
   * to keep things consistent. But when we're first creating the
479
   * object, we want it to be correct right away.
480
   */
481
0
  if (monitor->priv->initializing)
482
0
    {
483
0
      monitor->priv->is_available = (monitor->priv->have_ipv4_default_route ||
484
0
                                     monitor->priv->have_ipv6_default_route);
485
0
    }
486
0
}
487
488
/**
489
 * g_network_monitor_base_add_network:
490
 * @monitor: the #GNetworkMonitorBase
491
 * @network: (transfer none): a #GInetAddressMask
492
 *
493
 * Adds @network to @monitor's list of available networks.
494
 *
495
 * Since: 2.32
496
 */
497
void
498
g_network_monitor_base_add_network (GNetworkMonitorBase *monitor,
499
                                    GInetAddressMask    *network)
500
0
{
501
0
  if (!g_hash_table_add (monitor->priv->networks, g_object_ref (network)))
502
0
    return;
503
504
0
  if (g_inet_address_mask_get_length (network) == 0)
505
0
    {
506
0
      switch (g_inet_address_mask_get_family (network))
507
0
        {
508
0
        case G_SOCKET_FAMILY_IPV4:
509
0
          monitor->priv->have_ipv4_default_route = TRUE;
510
0
          break;
511
0
        case G_SOCKET_FAMILY_IPV6:
512
0
          monitor->priv->have_ipv6_default_route = TRUE;
513
0
          break;
514
0
        default:
515
0
          break;
516
0
        }
517
0
    }
518
519
  /* Don't emit network-changed when multicast-link-local routing
520
   * changes. This rather arbitrary decision is mostly because it
521
   * seems to change quite often...
522
   */
523
0
  if (g_inet_address_get_is_mc_link_local (g_inet_address_mask_get_address (network)))
524
0
    return;
525
526
0
  queue_network_changed (monitor);
527
0
}
528
529
/**
530
 * g_network_monitor_base_remove_network:
531
 * @monitor: the #GNetworkMonitorBase
532
 * @network: a #GInetAddressMask
533
 *
534
 * Removes @network from @monitor's list of available networks.
535
 *
536
 * Since: 2.32
537
 */
538
void
539
g_network_monitor_base_remove_network (GNetworkMonitorBase *monitor,
540
                                       GInetAddressMask    *network)
541
0
{
542
0
  if (!g_hash_table_remove (monitor->priv->networks, network))
543
0
    return;
544
545
0
  if (g_inet_address_mask_get_length (network) == 0)
546
0
    {
547
0
      switch (g_inet_address_mask_get_family (network))
548
0
        {
549
0
        case G_SOCKET_FAMILY_IPV4:
550
0
          monitor->priv->have_ipv4_default_route = FALSE;
551
0
          break;
552
0
        case G_SOCKET_FAMILY_IPV6:
553
0
          monitor->priv->have_ipv6_default_route = FALSE;
554
0
          break;
555
0
        default:
556
0
          break;
557
0
        }
558
0
    }
559
560
0
  queue_network_changed (monitor);
561
0
}
562
563
/**
564
 * g_network_monitor_base_set_networks:
565
 * @monitor: the #GNetworkMonitorBase
566
 * @networks: (array length=length): an array of #GInetAddressMask
567
 * @length: length of @networks
568
 *
569
 * Drops @monitor's current list of available networks and replaces
570
 * it with @networks.
571
 */
572
void
573
g_network_monitor_base_set_networks (GNetworkMonitorBase  *monitor,
574
                                     GInetAddressMask    **networks,
575
                                     gint                  length)
576
0
{
577
0
  int i;
578
579
0
  g_hash_table_remove_all (monitor->priv->networks);
580
0
  monitor->priv->have_ipv4_default_route = FALSE;
581
0
  monitor->priv->have_ipv6_default_route = FALSE;
582
583
0
  for (i = 0; i < length; i++)
584
0
    g_network_monitor_base_add_network (monitor, networks[i]);
585
0
}