device: renew dhcp leases on awake for software devices
[NetworkManager.git] / src / nm-connectivity.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager -- Network link manager
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 2 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 along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * Copyright (C) 2011 Thomas Bechtold <thomasbechtold@jpberlin.de>
19  * Copyright (C) 2011 Dan Williams <dcbw@redhat.com>
20  */
21
22 #include "nm-default.h"
23
24 #include <string.h>
25 #if WITH_CONCHECK
26 #include <libsoup/soup.h>
27 #endif
28
29 #include "nm-connectivity.h"
30 #include "nm-config.h"
31 #include "NetworkManagerUtils.h"
32
33 G_DEFINE_TYPE (NMConnectivity, nm_connectivity, G_TYPE_OBJECT)
34
35 #define NM_CONNECTIVITY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_CONNECTIVITY, NMConnectivityPrivate))
36
37 #define _NMLOG_DOMAIN  LOGD_CONCHECK
38 #define _NMLOG(level, ...) \
39     G_STMT_START { \
40         nm_log ((level), (_NMLOG_DOMAIN), \
41                 "%s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
42                 "connectivity: " \
43                 _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
44     } G_STMT_END
45
46 typedef struct {
47         char *uri;
48         char *response;
49         guint interval;
50         gboolean online; /* whether periodic connectivity checking is enabled. */
51
52 #if WITH_CONCHECK
53         SoupSession *soup_session;
54         gboolean initial_check_obsoleted;
55         guint check_id;
56 #endif
57
58         NMConnectivityState state;
59 } NMConnectivityPrivate;
60
61 enum {
62         PROP_0,
63         PROP_URI,
64         PROP_INTERVAL,
65         PROP_RESPONSE,
66         PROP_STATE,
67         LAST_PROP
68 };
69
70
71 NMConnectivityState
72 nm_connectivity_get_state (NMConnectivity *connectivity)
73 {
74         g_return_val_if_fail (NM_IS_CONNECTIVITY (connectivity), NM_CONNECTIVITY_UNKNOWN);
75
76         return NM_CONNECTIVITY_GET_PRIVATE (connectivity)->state;
77 }
78
79 NM_UTILS_LOOKUP_STR_DEFINE (nm_connectivity_state_to_string, NMConnectivityState,
80         NM_UTILS_LOOKUP_DEFAULT_WARN ("???"),
81         NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_UNKNOWN,  "UNKNOWN"),
82         NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_NONE,     "NONE"),
83         NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_LIMITED,  "LIMITED"),
84         NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_PORTAL,   "PORTAL"),
85         NM_UTILS_LOOKUP_STR_ITEM (NM_CONNECTIVITY_FULL,     "FULL"),
86 );
87
88 static void
89 update_state (NMConnectivity *self, NMConnectivityState state)
90 {
91         NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
92
93         if (priv->state != state) {
94                 _LOGD ("state changed from %s to %s",
95                        nm_connectivity_state_to_string (priv->state),
96                        nm_connectivity_state_to_string (state));
97                 priv->state = state;
98                 g_object_notify (G_OBJECT (self), NM_CONNECTIVITY_STATE);
99         }
100 }
101
102 #if WITH_CONCHECK
103 typedef struct {
104         GSimpleAsyncResult *simple;
105         char *uri;
106         char *response;
107         guint check_id_when_scheduled;
108 } ConCheckCbData;
109
110 static void
111 nm_connectivity_check_cb (SoupSession *session, SoupMessage *msg, gpointer user_data)
112 {
113         NMConnectivity *self;
114         NMConnectivityPrivate *priv;
115         ConCheckCbData *cb_data = user_data;
116         GSimpleAsyncResult *simple = cb_data->simple;
117         NMConnectivityState new_state;
118         const char *nm_header;
119         const char *uri = cb_data->uri;
120         const char *response = cb_data->response ? cb_data->response : NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE;
121
122         self = NM_CONNECTIVITY (g_async_result_get_source_object (G_ASYNC_RESULT (simple)));
123         /* it is safe to unref @self here, @simple holds yet another reference. */
124         g_object_unref (self);
125         priv = NM_CONNECTIVITY_GET_PRIVATE (self);
126
127         if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
128                 _LOGI ("check for uri '%s' failed with '%s'", uri, msg->reason_phrase);
129                 new_state = NM_CONNECTIVITY_LIMITED;
130                 goto done;
131         }
132
133         if (msg->status_code == 511) {
134                 _LOGD ("check for uri '%s' returned status '%d %s'; captive portal present.",
135                        uri, msg->status_code, msg->reason_phrase);
136                 new_state = NM_CONNECTIVITY_PORTAL;
137         } else {
138                 /* Check headers; if we find the NM-specific one we're done */
139                 nm_header = soup_message_headers_get_one (msg->response_headers, "X-NetworkManager-Status");
140                 if (g_strcmp0 (nm_header, "online") == 0) {
141                         _LOGD ("check for uri '%s' with Status header successful.", uri);
142                         new_state = NM_CONNECTIVITY_FULL;
143                 } else if (msg->status_code == SOUP_STATUS_OK) {
144                         /* check response */
145                         if (msg->response_body->data && g_str_has_prefix (msg->response_body->data, response)) {
146                                 _LOGD ("check for uri '%s' successful.", uri);
147                                 new_state = NM_CONNECTIVITY_FULL;
148                         } else {
149                                 _LOGI ("check for uri '%s' did not match expected response '%s'; assuming captive portal.",
150                                            uri, response);
151                                 new_state = NM_CONNECTIVITY_PORTAL;
152                         }
153                 } else {
154                         _LOGI ("check for uri '%s' returned status '%d %s'; assuming captive portal.",
155                                uri, msg->status_code, msg->reason_phrase);
156                         new_state = NM_CONNECTIVITY_PORTAL;
157                 }
158         }
159
160  done:
161         /* Only update the state, if the call was done from external, or if the periodic check
162          * is still the one that called this async check. */
163         if (!cb_data->check_id_when_scheduled || cb_data->check_id_when_scheduled == priv->check_id) {
164                 /* Only update the state, if the URI and response parameters did not change
165                  * since invocation.
166                  * The interval does not matter for exernal calls, and for internal calls
167                  * we don't reach this line if the interval changed. */
168                 if (   !g_strcmp0 (cb_data->uri, priv->uri)
169                     && !g_strcmp0 (cb_data->response, priv->response))
170                         update_state (self, new_state);
171         }
172
173         g_simple_async_result_set_op_res_gssize (simple, new_state);
174         g_simple_async_result_complete (simple);
175         g_object_unref (simple);
176
177         g_free (cb_data->uri);
178         g_free (cb_data->response);
179         g_slice_free (ConCheckCbData, cb_data);
180 }
181
182 #define IS_PERIODIC_CHECK(callback)  (callback == run_check_complete)
183
184 static void
185 run_check_complete (GObject      *object,
186                     GAsyncResult *result,
187                     gpointer      user_data)
188 {
189         NMConnectivity *self = NM_CONNECTIVITY (object);
190         GError *error = NULL;
191
192         nm_connectivity_check_finish (self, result, &error);
193         if (error) {
194                 _LOGE ("check failed: %s", error->message);
195                 g_error_free (error);
196         }
197 }
198
199 static gboolean
200 run_check (gpointer user_data)
201 {
202         NMConnectivity *self = NM_CONNECTIVITY (user_data);
203
204         nm_connectivity_check_async (self, run_check_complete, NULL);
205         return TRUE;
206 }
207
208 static gboolean
209 idle_start_periodic_checks (gpointer user_data)
210 {
211         NMConnectivity *self = user_data;
212         NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
213
214         priv->check_id = g_timeout_add_seconds (priv->interval, run_check, self);
215         if (!priv->initial_check_obsoleted)
216                 run_check (self);
217
218         return FALSE;
219 }
220 #endif
221
222 static void
223 _reschedule_periodic_checks (NMConnectivity *self, gboolean force_reschedule)
224 {
225         NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
226
227 #if WITH_CONCHECK
228         if (priv->online && priv->uri && priv->interval) {
229                 if (force_reschedule || !priv->check_id) {
230                         if (priv->check_id)
231                                 g_source_remove (priv->check_id);
232                         priv->check_id = g_timeout_add (0, idle_start_periodic_checks, self);
233                         priv->initial_check_obsoleted = FALSE;
234                 }
235         } else {
236                 nm_clear_g_source (&priv->check_id);
237         }
238         if (priv->check_id)
239                 return;
240 #endif
241
242         /* Either @online is %TRUE but we aren't checking connectivity, or
243          * @online is %FALSE. Either way we can update our status immediately.
244          */
245         update_state (self, priv->online ? NM_CONNECTIVITY_FULL : NM_CONNECTIVITY_NONE);
246 }
247
248 void
249 nm_connectivity_set_online (NMConnectivity *self,
250                             gboolean        online)
251 {
252         NMConnectivityPrivate *priv= NM_CONNECTIVITY_GET_PRIVATE (self);
253
254         online = !!online;
255         if (priv->online != online) {
256                 _LOGD ("set %s", online ? "online" : "offline");
257                 priv->online = online;
258                 _reschedule_periodic_checks (self, FALSE);
259         }
260 }
261
262 void
263 nm_connectivity_check_async (NMConnectivity      *self,
264                              GAsyncReadyCallback  callback,
265                              gpointer             user_data)
266 {
267         NMConnectivityPrivate *priv;
268         GSimpleAsyncResult *simple;
269
270         g_return_if_fail (NM_IS_CONNECTIVITY (self));
271         priv = NM_CONNECTIVITY_GET_PRIVATE (self);
272
273         simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
274                                             nm_connectivity_check_async);
275
276 #if WITH_CONCHECK
277         if (priv->uri && priv->interval) {
278                 SoupMessage *msg;
279                 ConCheckCbData *cb_data = g_slice_new (ConCheckCbData);
280
281                 msg = soup_message_new ("GET", priv->uri);
282                 soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
283                 /* Disable HTTP/1.1 keepalive; the connection should not persist */
284                 soup_message_headers_append (msg->request_headers, "Connection", "close");
285                 cb_data->simple = simple;
286                 cb_data->uri = g_strdup (priv->uri);
287                 cb_data->response = g_strdup (priv->response);
288
289                 /* For internal calls (periodic), remember the check-id at time of scheduling. */
290                 cb_data->check_id_when_scheduled = IS_PERIODIC_CHECK (callback) ? priv->check_id : 0;
291
292                 soup_session_queue_message (priv->soup_session,
293                                             msg,
294                                             nm_connectivity_check_cb,
295                                             cb_data);
296                 priv->initial_check_obsoleted = TRUE;
297
298                 _LOGD ("check: send %srequest to '%s'", IS_PERIODIC_CHECK (callback) ? "periodic " : "", priv->uri);
299                 return;
300         } else {
301                 g_warn_if_fail (!IS_PERIODIC_CHECK (callback));
302                 _LOGD ("check: faking request. Connectivity check disabled");
303         }
304 #else
305         _LOGD ("check: faking request. Compiled without connectivity-check support");
306 #endif
307
308         g_simple_async_result_set_op_res_gssize (simple, priv->state);
309         g_simple_async_result_complete_in_idle (simple);
310         g_object_unref (simple);
311 }
312
313 NMConnectivityState
314 nm_connectivity_check_finish (NMConnectivity  *self,
315                               GAsyncResult    *result,
316                               GError         **error)
317 {
318         GSimpleAsyncResult *simple;
319
320         g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), nm_connectivity_check_async), NM_CONNECTIVITY_UNKNOWN);
321
322         simple = G_SIMPLE_ASYNC_RESULT (result);
323         if (g_simple_async_result_propagate_error (simple, error))
324                 return NM_CONNECTIVITY_UNKNOWN;
325         return (NMConnectivityState) g_simple_async_result_get_op_res_gssize (simple);
326 }
327
328 /**************************************************************************/
329
330 NMConnectivity *
331 nm_connectivity_new (const char *uri,
332                      guint interval,
333                      const char *response)
334 {
335         return g_object_new (NM_TYPE_CONNECTIVITY,
336                              NM_CONNECTIVITY_URI, uri,
337                              NM_CONNECTIVITY_INTERVAL, interval,
338                              NM_CONNECTIVITY_RESPONSE, response,
339                              NULL);
340 }
341
342 static void
343 set_property (GObject *object, guint property_id,
344               const GValue *value, GParamSpec *pspec)
345 {
346         NMConnectivity *self = NM_CONNECTIVITY (object);
347         NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
348         const char *uri, *response;
349         guint interval;
350         gboolean changed;
351
352         switch (property_id) {
353         case PROP_URI:
354                 uri = g_value_get_string (value);
355                 if (uri && !*uri)
356                         uri = NULL;
357                 changed = g_strcmp0 (uri, priv->uri) != 0;
358 #if WITH_CONCHECK
359                 if (uri) {
360                         SoupURI *soup_uri = soup_uri_new (uri);
361
362                         if (!soup_uri || !SOUP_URI_VALID_FOR_HTTP (soup_uri)) {
363                                 _LOGE ("invalid uri '%s' for connectivity check.", uri);
364                                 uri = NULL;
365                         }
366                         if (uri && soup_uri && changed &&
367                             soup_uri_get_scheme(soup_uri) == SOUP_URI_SCHEME_HTTPS)
368                                 _LOGW ("use of HTTPS for connectivity checking is not reliable and is discouraged (URI: %s)", uri);
369                         if (soup_uri)
370                                 soup_uri_free (soup_uri);
371                 }
372 #endif
373                 if (changed) {
374                         g_free (priv->uri);
375                         priv->uri = g_strdup (uri);
376                         _reschedule_periodic_checks (self, TRUE);
377                 }
378                 break;
379         case PROP_INTERVAL:
380                 interval = g_value_get_uint (value);
381                 if (priv->interval != interval) {
382                         priv->interval = interval;
383                         _reschedule_periodic_checks (self, TRUE);
384                 }
385                 break;
386         case PROP_RESPONSE:
387                 response = g_value_get_string (value);
388                 if (g_strcmp0 (response, priv->response) != 0) {
389                         /* a response %NULL means, NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE. Any other response
390                          * (including "") is accepted. */
391                         g_free (priv->response);
392                         priv->response = g_strdup (response);
393                         _reschedule_periodic_checks (self, TRUE);
394                 }
395                 break;
396         default:
397                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
398                 break;
399         }
400 }
401
402 static void
403 get_property (GObject *object, guint property_id,
404               GValue *value, GParamSpec *pspec)
405 {
406         NMConnectivity *self = NM_CONNECTIVITY (object);
407         NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
408
409         switch (property_id) {
410         case PROP_URI:
411                 g_value_set_string (value, priv->uri);
412                 break;
413         case PROP_INTERVAL:
414                 g_value_set_uint (value, priv->interval);
415                 break;
416         case PROP_RESPONSE:
417                 if (priv->response)
418                         g_value_set_string (value, priv->response);
419                 else
420                         g_value_set_static_string (value, NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE);
421                 break;
422         case PROP_STATE:
423                 g_value_set_uint (value, priv->state);
424                 break;
425         default:
426                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
427                 break;
428         }
429 }
430
431
432 static void
433 nm_connectivity_init (NMConnectivity *self)
434 {
435         NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
436
437 #if WITH_CONCHECK
438         priv->soup_session = soup_session_async_new_with_options (SOUP_SESSION_TIMEOUT, 15, NULL);
439 #endif
440         priv->state = NM_CONNECTIVITY_NONE;
441 }
442
443
444 static void
445 dispose (GObject *object)
446 {
447         NMConnectivity *self = NM_CONNECTIVITY (object);
448         NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
449
450         g_clear_pointer (&priv->uri, g_free);
451         g_clear_pointer (&priv->response, g_free);
452
453 #if WITH_CONCHECK
454         if (priv->soup_session) {
455                 soup_session_abort (priv->soup_session);
456                 g_clear_object (&priv->soup_session);
457         }
458
459         nm_clear_g_source (&priv->check_id);
460 #endif
461
462         G_OBJECT_CLASS (nm_connectivity_parent_class)->dispose (object);
463 }
464
465
466 static void
467 nm_connectivity_class_init (NMConnectivityClass *klass)
468 {
469         GObjectClass *object_class = G_OBJECT_CLASS (klass);
470         g_type_class_add_private (klass, sizeof (NMConnectivityPrivate));
471
472         /* virtual methods */
473         object_class->set_property = set_property;
474         object_class->get_property = get_property;
475         object_class->dispose = dispose;
476
477         /* properties */
478         g_object_class_install_property
479             (object_class, PROP_URI,
480              g_param_spec_string (NM_CONNECTIVITY_URI, "", "",
481                                   NULL,
482                                   G_PARAM_READWRITE |
483                                   G_PARAM_CONSTRUCT |
484                                   G_PARAM_STATIC_STRINGS));
485
486         g_object_class_install_property
487             (object_class, PROP_INTERVAL,
488              g_param_spec_uint (NM_CONNECTIVITY_INTERVAL, "", "",
489                                 0, G_MAXUINT, NM_CONFIG_DEFAULT_CONNECTIVITY_INTERVAL,
490                                 G_PARAM_READWRITE |
491                                 G_PARAM_CONSTRUCT |
492                                 G_PARAM_STATIC_STRINGS));
493
494         g_object_class_install_property
495             (object_class, PROP_RESPONSE,
496              g_param_spec_string (NM_CONNECTIVITY_RESPONSE, "", "",
497                                   NM_CONFIG_DEFAULT_CONNECTIVITY_RESPONSE,
498                                   G_PARAM_READWRITE |
499                                   G_PARAM_CONSTRUCT |
500                                   G_PARAM_STATIC_STRINGS));
501
502         g_object_class_install_property
503             (object_class, PROP_STATE,
504              g_param_spec_uint (NM_CONNECTIVITY_STATE, "", "",
505                                 NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
506                                 G_PARAM_READABLE |
507                                 G_PARAM_STATIC_STRINGS));
508 }
509