Branch data Line data Source code
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 : 1376 : 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 : 67 : g_network_monitor_base_init (GNetworkMonitorBase *monitor)
81 : : {
82 : 67 : monitor->priv = g_network_monitor_base_get_instance_private (monitor);
83 : 67 : monitor->priv->networks = g_hash_table_new_full (inet_address_mask_hash,
84 : : inet_address_mask_equal,
85 : : g_object_unref, NULL);
86 : 67 : monitor->priv->context = g_main_context_get_thread_default ();
87 : 67 : if (monitor->priv->context)
88 : 0 : g_main_context_ref (monitor->priv->context);
89 : :
90 : 67 : monitor->priv->initializing = TRUE;
91 : 67 : }
92 : :
93 : : static void
94 : 67 : g_network_monitor_base_constructed (GObject *object)
95 : : {
96 : 67 : GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object);
97 : :
98 : 67 : if (G_OBJECT_TYPE (monitor) == G_TYPE_NETWORK_MONITOR_BASE)
99 : : {
100 : : 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 : 4 : mask = g_inet_address_mask_new_from_string ("0.0.0.0/0", NULL);
106 : 4 : g_network_monitor_base_add_network (monitor, mask);
107 : 4 : g_object_unref (mask);
108 : :
109 : 4 : mask = g_inet_address_mask_new_from_string ("::/0", NULL);
110 : 4 : if (mask)
111 : : {
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 : 4 : g_network_monitor_base_add_network (monitor, mask);
116 : 4 : g_object_unref (mask);
117 : : }
118 : : }
119 : 67 : }
120 : :
121 : : static void
122 : 25 : g_network_monitor_base_get_property (GObject *object,
123 : : guint prop_id,
124 : : GValue *value,
125 : : GParamSpec *pspec)
126 : : {
127 : 25 : GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object);
128 : :
129 : 25 : switch (prop_id)
130 : : {
131 : 25 : case PROP_NETWORK_AVAILABLE:
132 : 25 : g_value_set_boolean (value, monitor->priv->is_available);
133 : 25 : 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 : : G_NETWORK_CONNECTIVITY_FULL :
144 : : 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 : : }
151 : :
152 : 25 : }
153 : :
154 : : static void
155 : 46 : g_network_monitor_base_finalize (GObject *object)
156 : : {
157 : 46 : GNetworkMonitorBase *monitor = G_NETWORK_MONITOR_BASE (object);
158 : :
159 : 46 : g_hash_table_unref (monitor->priv->networks);
160 : 46 : if (monitor->priv->network_changed_source)
161 : : {
162 : 0 : g_source_destroy (monitor->priv->network_changed_source);
163 : 0 : g_source_unref (monitor->priv->network_changed_source);
164 : : }
165 : 46 : if (monitor->priv->context)
166 : 0 : g_main_context_unref (monitor->priv->context);
167 : :
168 : 46 : G_OBJECT_CLASS (g_network_monitor_base_parent_class)->finalize (object);
169 : 46 : }
170 : :
171 : : static void
172 : 21 : g_network_monitor_base_class_init (GNetworkMonitorBaseClass *monitor_class)
173 : : {
174 : 21 : GObjectClass *gobject_class = G_OBJECT_CLASS (monitor_class);
175 : :
176 : 21 : gobject_class->constructed = g_network_monitor_base_constructed;
177 : 21 : gobject_class->get_property = g_network_monitor_base_get_property;
178 : 21 : gobject_class->finalize = g_network_monitor_base_finalize;
179 : :
180 : 21 : g_object_class_override_property (gobject_class, PROP_NETWORK_AVAILABLE, "network-available");
181 : 21 : g_object_class_override_property (gobject_class, PROP_NETWORK_METERED, "network-metered");
182 : 21 : g_object_class_override_property (gobject_class, PROP_CONNECTIVITY, "connectivity");
183 : 21 : }
184 : :
185 : : static gboolean
186 : 399 : g_network_monitor_base_can_reach_sockaddr (GNetworkMonitorBase *base,
187 : : GSocketAddress *sockaddr)
188 : : {
189 : : GInetAddress *iaddr;
190 : : GHashTableIter iter;
191 : : gpointer key;
192 : :
193 : 399 : if (!G_IS_INET_SOCKET_ADDRESS (sockaddr))
194 : 0 : return FALSE;
195 : :
196 : 399 : iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (sockaddr));
197 : 399 : g_hash_table_iter_init (&iter, base->priv->networks);
198 : 927 : while (g_hash_table_iter_next (&iter, &key, NULL))
199 : : {
200 : 775 : GInetAddressMask *mask = key;
201 : 775 : if (g_inet_address_mask_matches (mask, iaddr))
202 : 247 : return TRUE;
203 : : }
204 : :
205 : 152 : return FALSE;
206 : : }
207 : :
208 : : static gboolean
209 : 247 : g_network_monitor_base_can_reach (GNetworkMonitor *monitor,
210 : : GSocketConnectable *connectable,
211 : : GCancellable *cancellable,
212 : : GError **error)
213 : : {
214 : 247 : GNetworkMonitorBase *base = G_NETWORK_MONITOR_BASE (monitor);
215 : : GSocketAddressEnumerator *enumerator;
216 : : GSocketAddress *addr;
217 : :
218 : 247 : if (g_hash_table_size (base->priv->networks) == 0)
219 : : {
220 : 38 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE,
221 : : _("Network unreachable"));
222 : 38 : return FALSE;
223 : : }
224 : :
225 : 209 : enumerator = g_socket_connectable_proxy_enumerate (connectable);
226 : 209 : addr = g_socket_address_enumerator_next (enumerator, cancellable, error);
227 : 209 : if (!addr)
228 : : {
229 : : /* Either the user cancelled, or DNS resolution failed */
230 : 0 : g_object_unref (enumerator);
231 : 0 : return FALSE;
232 : : }
233 : :
234 : 209 : if (base->priv->have_ipv4_default_route &&
235 : 19 : base->priv->have_ipv6_default_route)
236 : : {
237 : 19 : g_object_unref (enumerator);
238 : 19 : g_object_unref (addr);
239 : 19 : return TRUE;
240 : : }
241 : :
242 : 266 : while (addr)
243 : : {
244 : 190 : if (g_network_monitor_base_can_reach_sockaddr (base, addr))
245 : : {
246 : 114 : g_object_unref (addr);
247 : 114 : g_object_unref (enumerator);
248 : 114 : return TRUE;
249 : : }
250 : :
251 : 76 : g_object_unref (addr);
252 : 76 : addr = g_socket_address_enumerator_next (enumerator, cancellable, error);
253 : : }
254 : 76 : g_object_unref (enumerator);
255 : :
256 : 76 : if (error && !*error)
257 : : {
258 : 76 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE,
259 : : _("Host unreachable"));
260 : : }
261 : 76 : return FALSE;
262 : : }
263 : :
264 : : static void
265 : 285 : can_reach_async_got_address (GObject *object,
266 : : GAsyncResult *result,
267 : : gpointer user_data)
268 : : {
269 : 285 : GSocketAddressEnumerator *enumerator = G_SOCKET_ADDRESS_ENUMERATOR (object);
270 : 285 : GTask *task = user_data;
271 : 285 : GNetworkMonitorBase *base = g_task_get_source_object (task);
272 : : GSocketAddress *addr;
273 : 285 : GError *error = NULL;
274 : :
275 : 285 : addr = g_socket_address_enumerator_next_finish (enumerator, result, &error);
276 : 285 : if (!addr)
277 : : {
278 : 76 : if (error)
279 : : {
280 : : /* Either the user cancelled, or DNS resolution failed */
281 : 0 : g_task_return_error (task, error);
282 : 0 : g_object_unref (task);
283 : 209 : return;
284 : : }
285 : : else
286 : : {
287 : : /* Resolved all addresses, none matched */
288 : 76 : g_task_return_new_error_literal (task, G_IO_ERROR,
289 : : G_IO_ERROR_HOST_UNREACHABLE,
290 : 76 : _("Host unreachable"));
291 : 76 : g_object_unref (task);
292 : 76 : return;
293 : : }
294 : : }
295 : :
296 : 209 : if (g_network_monitor_base_can_reach_sockaddr (base, addr))
297 : : {
298 : 133 : g_object_unref (addr);
299 : 133 : g_task_return_boolean (task, TRUE);
300 : 133 : g_object_unref (task);
301 : 133 : return;
302 : : }
303 : 76 : g_object_unref (addr);
304 : :
305 : 76 : g_socket_address_enumerator_next_async (enumerator,
306 : : g_task_get_cancellable (task),
307 : : can_reach_async_got_address, task);
308 : : }
309 : :
310 : : static void
311 : 247 : g_network_monitor_base_can_reach_async (GNetworkMonitor *monitor,
312 : : GSocketConnectable *connectable,
313 : : GCancellable *cancellable,
314 : : GAsyncReadyCallback callback,
315 : : gpointer user_data)
316 : : {
317 : : GTask *task;
318 : : GSocketAddressEnumerator *enumerator;
319 : :
320 : 247 : task = g_task_new (monitor, cancellable, callback, user_data);
321 : 247 : g_task_set_source_tag (task, g_network_monitor_base_can_reach_async);
322 : :
323 : 247 : if (g_hash_table_size (G_NETWORK_MONITOR_BASE (monitor)->priv->networks) == 0)
324 : : {
325 : 38 : g_task_return_new_error_literal (task, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE,
326 : 38 : _("Network unreachable"));
327 : 38 : g_object_unref (task);
328 : 38 : return;
329 : : }
330 : :
331 : 209 : enumerator = g_socket_connectable_proxy_enumerate (connectable);
332 : 209 : g_socket_address_enumerator_next_async (enumerator, cancellable,
333 : : can_reach_async_got_address, task);
334 : 209 : g_object_unref (enumerator);
335 : : }
336 : :
337 : : static gboolean
338 : 247 : g_network_monitor_base_can_reach_finish (GNetworkMonitor *monitor,
339 : : GAsyncResult *result,
340 : : GError **error)
341 : : {
342 : 247 : g_return_val_if_fail (g_task_is_valid (result, monitor), FALSE);
343 : :
344 : 247 : return g_task_propagate_boolean (G_TASK (result), error);
345 : : }
346 : :
347 : : static void
348 : 21 : g_network_monitor_base_iface_init (GNetworkMonitorInterface *monitor_iface)
349 : : {
350 : 21 : monitor_iface->can_reach = g_network_monitor_base_can_reach;
351 : 21 : monitor_iface->can_reach_async = g_network_monitor_base_can_reach_async;
352 : 21 : monitor_iface->can_reach_finish = g_network_monitor_base_can_reach_finish;
353 : :
354 : 21 : network_changed_signal = g_signal_lookup ("network-changed", G_TYPE_NETWORK_MONITOR);
355 : 21 : }
356 : :
357 : : static gboolean
358 : 46 : g_network_monitor_base_initable_init (GInitable *initable,
359 : : GCancellable *cancellable,
360 : : GError **error)
361 : : {
362 : 46 : GNetworkMonitorBase *base = G_NETWORK_MONITOR_BASE (initable);
363 : :
364 : 46 : base->priv->initializing = FALSE;
365 : :
366 : 46 : return TRUE;
367 : : }
368 : :
369 : : static void
370 : 21 : g_network_monitor_base_initable_iface_init (GInitableIface *iface)
371 : : {
372 : 21 : iface->init = g_network_monitor_base_initable_init;
373 : 21 : }
374 : :
375 : : static guint
376 : 491 : inet_address_mask_hash (gconstpointer key)
377 : : {
378 : 491 : GInetAddressMask *mask = G_INET_ADDRESS_MASK (key);
379 : : guint addr_hash;
380 : 491 : guint mask_length = g_inet_address_mask_get_length (mask);
381 : 491 : GInetAddress *addr = g_inet_address_mask_get_address (mask);
382 : 491 : const guint8 *bytes = g_inet_address_to_bytes (addr);
383 : 491 : gsize bytes_length = g_inet_address_get_native_size (addr);
384 : :
385 : : union
386 : : {
387 : : const guint8 *bytes;
388 : : guint32 *hash32;
389 : : guint64 *hash64;
390 : : } integerifier;
391 : :
392 : : /* If we can fit the entire address into the hash key, do it. Don’t worry
393 : : * about endianness; the address should always be in network endianness. */
394 : 491 : if (bytes_length == sizeof (guint32))
395 : : {
396 : 310 : integerifier.bytes = bytes;
397 : 310 : addr_hash = *integerifier.hash32;
398 : : }
399 : 181 : else if (bytes_length == sizeof (guint64))
400 : : {
401 : 0 : integerifier.bytes = bytes;
402 : 0 : addr_hash = *integerifier.hash64;
403 : : }
404 : : else
405 : : {
406 : : gsize i;
407 : :
408 : : /* Otherwise, fall back to adding the bytes together. We do this, rather
409 : : * than XORing them, as routes often have repeated tuples which would
410 : : * cancel out under XOR. */
411 : 181 : addr_hash = 0;
412 : 3077 : for (i = 0; i < bytes_length; i++)
413 : 2896 : addr_hash += bytes[i];
414 : : }
415 : :
416 : 491 : return addr_hash + mask_length;;
417 : : }
418 : :
419 : : static gboolean
420 : 15 : inet_address_mask_equal (gconstpointer a,
421 : : gconstpointer b)
422 : : {
423 : 15 : GInetAddressMask *mask_a = G_INET_ADDRESS_MASK (a);
424 : 15 : GInetAddressMask *mask_b = G_INET_ADDRESS_MASK (b);
425 : :
426 : 15 : return g_inet_address_mask_equal (mask_a, mask_b);
427 : : }
428 : :
429 : : static gboolean
430 : 21 : emit_network_changed (gpointer user_data)
431 : : {
432 : 21 : GNetworkMonitorBase *monitor = user_data;
433 : : gboolean is_available;
434 : :
435 : 21 : if (g_source_is_destroyed (g_main_current_source ()))
436 : 0 : return FALSE;
437 : :
438 : 21 : g_object_ref (monitor);
439 : :
440 : 42 : is_available = (monitor->priv->have_ipv4_default_route ||
441 : 21 : monitor->priv->have_ipv6_default_route);
442 : 21 : if (monitor->priv->is_available != is_available)
443 : : {
444 : 3 : monitor->priv->is_available = is_available;
445 : 3 : g_object_notify (G_OBJECT (monitor), "network-available");
446 : : }
447 : :
448 : 21 : g_signal_emit (monitor, network_changed_signal, 0, is_available);
449 : :
450 : 21 : g_source_unref (monitor->priv->network_changed_source);
451 : 21 : monitor->priv->network_changed_source = NULL;
452 : :
453 : 21 : g_object_unref (monitor);
454 : 21 : return FALSE;
455 : : }
456 : :
457 : : static void
458 : 491 : queue_network_changed (GNetworkMonitorBase *monitor)
459 : : {
460 : 491 : if (!monitor->priv->network_changed_source &&
461 : 491 : !monitor->priv->initializing)
462 : : {
463 : : GSource *source;
464 : :
465 : 21 : source = g_idle_source_new ();
466 : : /* Use G_PRIORITY_HIGH_IDLE priority so that multiple
467 : : * network-change-related notifications coming in at
468 : : * G_PRIORITY_DEFAULT will get coalesced into one signal
469 : : * emission.
470 : : */
471 : 21 : g_source_set_priority (source, G_PRIORITY_HIGH_IDLE);
472 : 21 : g_source_set_callback (source, emit_network_changed, monitor, NULL);
473 : 21 : g_source_set_static_name (source, "[gio] emit_network_changed");
474 : 21 : g_source_attach (source, monitor->priv->context);
475 : 21 : monitor->priv->network_changed_source = source;
476 : : }
477 : :
478 : : /* Normally we wait to update is_available until we emit the signal,
479 : : * to keep things consistent. But when we're first creating the
480 : : * object, we want it to be correct right away.
481 : : */
482 : 491 : if (monitor->priv->initializing)
483 : : {
484 : 470 : monitor->priv->is_available = (monitor->priv->have_ipv4_default_route ||
485 : 0 : monitor->priv->have_ipv6_default_route);
486 : : }
487 : 491 : }
488 : :
489 : : /**
490 : : * g_network_monitor_base_add_network:
491 : : * @monitor: the #GNetworkMonitorBase
492 : : * @network: (transfer none): a #GInetAddressMask
493 : : *
494 : : * Adds @network to @monitor's list of available networks.
495 : : *
496 : : * Since: 2.32
497 : : */
498 : : void
499 : 480 : g_network_monitor_base_add_network (GNetworkMonitorBase *monitor,
500 : : GInetAddressMask *network)
501 : : {
502 : 480 : if (!g_hash_table_add (monitor->priv->networks, g_object_ref (network)))
503 : 0 : return;
504 : :
505 : 480 : if (g_inet_address_mask_get_length (network) == 0)
506 : : {
507 : 50 : switch (g_inet_address_mask_get_family (network))
508 : : {
509 : 46 : case G_SOCKET_FAMILY_IPV4:
510 : 46 : monitor->priv->have_ipv4_default_route = TRUE;
511 : 46 : break;
512 : 4 : case G_SOCKET_FAMILY_IPV6:
513 : 4 : monitor->priv->have_ipv6_default_route = TRUE;
514 : 4 : break;
515 : 0 : default:
516 : 0 : break;
517 : : }
518 : : }
519 : :
520 : : /* Don't emit network-changed when multicast-link-local routing
521 : : * changes. This rather arbitrary decision is mostly because it
522 : : * seems to change quite often...
523 : : */
524 : 480 : if (g_inet_address_get_is_mc_link_local (g_inet_address_mask_get_address (network)))
525 : 0 : return;
526 : :
527 : 480 : queue_network_changed (monitor);
528 : : }
529 : :
530 : : /**
531 : : * g_network_monitor_base_remove_network:
532 : : * @monitor: the #GNetworkMonitorBase
533 : : * @network: a #GInetAddressMask
534 : : *
535 : : * Removes @network from @monitor's list of available networks.
536 : : *
537 : : * Since: 2.32
538 : : */
539 : : void
540 : 11 : g_network_monitor_base_remove_network (GNetworkMonitorBase *monitor,
541 : : GInetAddressMask *network)
542 : : {
543 : 11 : if (!g_hash_table_remove (monitor->priv->networks, network))
544 : 0 : return;
545 : :
546 : 11 : if (g_inet_address_mask_get_length (network) == 0)
547 : : {
548 : 6 : switch (g_inet_address_mask_get_family (network))
549 : : {
550 : 3 : case G_SOCKET_FAMILY_IPV4:
551 : 3 : monitor->priv->have_ipv4_default_route = FALSE;
552 : 3 : break;
553 : 3 : case G_SOCKET_FAMILY_IPV6:
554 : 3 : monitor->priv->have_ipv6_default_route = FALSE;
555 : 3 : break;
556 : 0 : default:
557 : 0 : break;
558 : : }
559 : : }
560 : :
561 : 11 : queue_network_changed (monitor);
562 : : }
563 : :
564 : : /**
565 : : * g_network_monitor_base_set_networks:
566 : : * @monitor: the #GNetworkMonitorBase
567 : : * @networks: (array length=length): an array of #GInetAddressMask
568 : : * @length: length of @networks
569 : : *
570 : : * Drops @monitor's current list of available networks and replaces
571 : : * it with @networks.
572 : : */
573 : : void
574 : 42 : g_network_monitor_base_set_networks (GNetworkMonitorBase *monitor,
575 : : GInetAddressMask **networks,
576 : : gint length)
577 : : {
578 : : int i;
579 : :
580 : 42 : g_hash_table_remove_all (monitor->priv->networks);
581 : 42 : monitor->priv->have_ipv4_default_route = FALSE;
582 : 42 : monitor->priv->have_ipv6_default_route = FALSE;
583 : :
584 : 504 : for (i = 0; i < length; i++)
585 : 462 : g_network_monitor_base_add_network (monitor, networks[i]);
586 : 42 : }
|