Coverage Report

Created: 2025-07-01 07:09

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