core: provide additional network connectivity information

NM_STATE_CONNECTED_SITE doesn't distinguish between "behind a captive
portal" and "limited network connectivity" (ie, connected to a router
that has lost its upstream connection). Add a new NMManager
:connectivity property to provide this information.

Also add a CheckConnectivity method, which can be used to force NM to
re-check the connectivity state, which could be called by a client
after it completed a portal login, or fixed a network problem.
This commit is contained in:
Dan Winship 2013-07-30 16:31:31 -04:00
parent 8732914815
commit 07521da591
6 changed files with 237 additions and 51 deletions

View file

@ -101,6 +101,27 @@ typedef enum {
/* For backwards compat */
#define NM_STATE_CONNECTED NM_STATE_CONNECTED_GLOBAL
/**
* NMConnectivityState:
* @NM_CONNECTIVITY_UNKNOWN: Network connectivity is unknown.
* @NM_CONNECTIVITY_NONE: The host is not connected to any network.
* @NM_CONNECTIVITY_PORTAL: The host is behind a captive portal and
* cannot reach the full Internet.
* @NM_CONNECTIVITY_LIMITED: The host is connected to a network, but
* does not appear to be able to reach the full Internet.
* @NM_CONNECTIVITY_FULL: The host is connected to a network, and
* appears to be able to reach the full Internet.
*
* Since: 0.9.10
*/
typedef enum {
NM_CONNECTIVITY_UNKNOWN,
NM_CONNECTIVITY_NONE,
NM_CONNECTIVITY_PORTAL,
NM_CONNECTIVITY_LIMITED,
NM_CONNECTIVITY_FULL
} NMConnectivityState;
/**
* NMDeviceType:
* @NM_DEVICE_TYPE_UNKNOWN: unknown device

View file

@ -244,6 +244,19 @@
</arg>
</method>
<method name="CheckConnectivity">
<annotation name="org.freedesktop.DBus.GLib.CSymbol" value="impl_manager_check_connectivity"/>
<annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
<tp:docstring>
Re-check the network connectivity state.
</tp:docstring>
<arg name="connectivity" type="u" tp:type="NM_CONNECTIVITY" direction="out">
<tp:docstring>
The current connectivity state.
</tp:docstring>
</arg>
</method>
<method name="state">
<tp:docstring>
The overall networking state as determined by the NetworkManager daemon,
@ -333,6 +346,12 @@
</arg>
</signal>
<property name="Connectivity" type="u" access="read" tp:type="NM_CONNECTIVITY">
<tp:docstring>
The network connectivity state.
</tp:docstring>
</property>
<signal name="PropertiesChanged">
<tp:docstring>
NetworkManager's properties changed.
@ -413,5 +432,39 @@
</tp:enumvalue>
</tp:enum>
<tp:enum name="NM_CONNECTIVITY" type="u">
<tp:docstring>
Describes the network-connectivity state.
</tp:docstring>
<tp:enumvalue suffix="UNKNOWN" value="0">
<tp:docstring>
Network connectivity is unknown.
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="NONE" value="1">
<tp:docstring>
The host is not connected to any network.
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="PORTAL" value="2">
<tp:docstring>
The host is behind a captive portal and cannot reach the
full Internet.
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="LIMITED" value="3">
<tp:docstring>
The host is connected to a network, but does not appear to
be able to reach the full Internet.
</tp:docstring>
</tp:enumvalue>
<tp:enumvalue suffix="FULL" value="4">
<tp:docstring>
The host is connected to a network, and appears to be able
to reach the full Internet
</tp:docstring>
</tp:enumvalue>
</tp:enum>
</interface>
</node>

View file

@ -48,7 +48,7 @@ typedef struct {
guint check_id;
#endif
gboolean connected;
NMConnectivityState state;
} NMConnectivityPrivate;
enum {
@ -56,28 +56,28 @@ enum {
PROP_URI,
PROP_INTERVAL,
PROP_RESPONSE,
PROP_CONNECTED,
PROP_STATE,
LAST_PROP
};
gboolean
nm_connectivity_get_connected (NMConnectivity *connectivity)
NMConnectivityState
nm_connectivity_get_state (NMConnectivity *connectivity)
{
g_return_val_if_fail (NM_IS_CONNECTIVITY (connectivity), FALSE);
g_return_val_if_fail (NM_IS_CONNECTIVITY (connectivity), NM_CONNECTIVITY_UNKNOWN);
return NM_CONNECTIVITY_GET_PRIVATE (connectivity)->connected;
return NM_CONNECTIVITY_GET_PRIVATE (connectivity)->state;
}
static void
update_connected (NMConnectivity *self, gboolean connected)
update_state (NMConnectivity *self, NMConnectivityState state)
{
NMConnectivityPrivate *priv = NM_CONNECTIVITY_GET_PRIVATE (self);
gboolean old_connected = priv->connected;
priv->connected = connected;
if (priv->connected != old_connected)
g_object_notify (G_OBJECT (self), NM_CONNECTIVITY_CONNECTED);
if (priv->state != state) {
priv->state = state;
g_object_notify (G_OBJECT (self), NM_CONNECTIVITY_STATE);
}
}
#if WITH_CONCHECK
@ -87,37 +87,47 @@ nm_connectivity_check_cb (SoupSession *session, SoupMessage *msg, gpointer user_
GSimpleAsyncResult *simple = user_data;
NMConnectivity *self;
NMConnectivityPrivate *priv;
gboolean connected_new = FALSE;
NMConnectivityState new_state;
const char *nm_header;
self = NM_CONNECTIVITY (g_async_result_get_source_object (G_ASYNC_RESULT (simple)));
g_object_unref (self);
priv = NM_CONNECTIVITY_GET_PRIVATE (self);
if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
nm_log_info (LOGD_CONCHECK, "Connectivity check for uri '%s' failed with '%s'.",
priv->uri, msg->reason_phrase);
new_state = NM_CONNECTIVITY_LIMITED;
goto done;
}
/* Check headers; if we find the NM-specific one we're done */
nm_header = soup_message_headers_get_one (msg->response_headers, "X-NetworkManager-Status");
if (g_strcmp0 (nm_header, "online") == 0) {
nm_log_dbg (LOGD_CONCHECK, "Connectivity check for uri '%s' with Status header successful.", priv->uri);
connected_new = TRUE;
new_state = NM_CONNECTIVITY_FULL;
} else if (msg->status_code == SOUP_STATUS_OK) {
/* check response */
if (msg->response_body->data && (g_str_has_prefix (msg->response_body->data, priv->response))) {
nm_log_dbg (LOGD_CONCHECK, "Connectivity check for uri '%s' successful.",
priv->uri);
connected_new = TRUE;
new_state = NM_CONNECTIVITY_FULL;
} else {
nm_log_info (LOGD_CONCHECK, "Connectivity check for uri '%s' did not match expected response '%s'.",
nm_log_info (LOGD_CONCHECK, "Connectivity check for uri '%s' did not match expected response '%s'; assuming captive portal.",
priv->uri, priv->response);
new_state = NM_CONNECTIVITY_PORTAL;
}
} else {
nm_log_info (LOGD_CONCHECK, "Connectivity check for uri '%s' returned status '%d %s'.",
nm_log_info (LOGD_CONCHECK, "Connectivity check for uri '%s' returned status '%d %s'; assuming captive portal.",
priv->uri, msg->status_code, msg->reason_phrase);
new_state = NM_CONNECTIVITY_PORTAL;
}
g_simple_async_result_set_op_res_gboolean (simple, connected_new);
done:
g_simple_async_result_set_op_res_gssize (simple, new_state);
g_simple_async_result_complete (simple);
update_connected (self, connected_new);
update_state (self, new_state);
}
static void
@ -174,7 +184,7 @@ nm_connectivity_set_online (NMConnectivity *self,
/* Either @online is %TRUE but we aren't checking connectivity, or
* @online is %FALSE. Either way we can update our status immediately.
*/
update_connected (self, online);
update_state (self, online ? NM_CONNECTIVITY_FULL : NM_CONNECTIVITY_NONE);
}
void
@ -207,23 +217,23 @@ nm_connectivity_check_async (NMConnectivity *self,
}
#endif
g_simple_async_result_set_op_res_gboolean (simple, TRUE);
g_simple_async_result_set_op_res_gssize (simple, priv->state);
g_simple_async_result_complete_in_idle (simple);
}
gboolean
NMConnectivityState
nm_connectivity_check_finish (NMConnectivity *self,
GAsyncResult *result,
GError **error)
{
GSimpleAsyncResult *simple;
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), nm_connectivity_check_async), FALSE);
g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (self), nm_connectivity_check_async), NM_CONNECTIVITY_UNKNOWN);
simple = G_SIMPLE_ASYNC_RESULT (result);
if (g_simple_async_result_propagate_error (simple, error))
return FALSE;
return g_simple_async_result_get_op_res_gboolean (simple);
return NM_CONNECTIVITY_UNKNOWN;
return (NMConnectivityState) g_simple_async_result_get_op_res_gssize (simple);
}
@ -243,7 +253,7 @@ nm_connectivity_new (void)
NM_CONNECTIVITY_RESPONSE, check_response ? check_response : DEFAULT_RESPONSE,
NULL);
g_return_val_if_fail (self != NULL, NULL);
update_connected (self, FALSE);
update_state (self, NM_CONNECTIVITY_NONE);
return self;
}
@ -314,8 +324,8 @@ get_property (GObject *object, guint property_id,
case PROP_RESPONSE:
g_value_set_string (value, priv->response);
break;
case PROP_CONNECTED:
g_value_set_boolean (value, priv->connected);
case PROP_STATE:
g_value_set_uint (value, priv->state);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@ -395,11 +405,11 @@ nm_connectivity_class_init (NMConnectivityClass *klass)
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
g_object_class_install_property
(object_class, PROP_CONNECTED,
g_param_spec_boolean (NM_CONNECTIVITY_CONNECTED,
"Connected",
"Is connected",
FALSE,
G_PARAM_READABLE));
(object_class, PROP_STATE,
g_param_spec_uint (NM_CONNECTIVITY_STATE,
"State",
"Connectivity state",
NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
G_PARAM_READABLE));
}

View file

@ -38,7 +38,7 @@
#define NM_CONNECTIVITY_URI "uri"
#define NM_CONNECTIVITY_INTERVAL "interval"
#define NM_CONNECTIVITY_RESPONSE "response"
#define NM_CONNECTIVITY_CONNECTED "connected"
#define NM_CONNECTIVITY_STATE "state"
typedef struct {
GObject parent;
@ -50,18 +50,18 @@ typedef struct {
GType nm_connectivity_get_type (void);
NMConnectivity *nm_connectivity_new (void);
NMConnectivity *nm_connectivity_new (void);
void nm_connectivity_set_online (NMConnectivity *self,
gboolean online);
void nm_connectivity_set_online (NMConnectivity *self,
gboolean online);
gboolean nm_connectivity_get_connected (NMConnectivity *self);
NMConnectivityState nm_connectivity_get_state (NMConnectivity *self);
void nm_connectivity_check_async (NMConnectivity *self,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean nm_connectivity_check_finish (NMConnectivity *self,
GAsyncResult *result,
GError **error);
void nm_connectivity_check_async (NMConnectivity *self,
GAsyncReadyCallback callback,
gpointer user_data);
NMConnectivityState nm_connectivity_check_finish (NMConnectivity *self,
GAsyncResult *result,
GError **error);
#endif /* NM_CONNECTIVITY_H */

View file

@ -132,6 +132,9 @@ static void impl_manager_get_logging (NMManager *manager,
char **level,
char **domains);
static void impl_manager_check_connectivity (NMManager *manager,
DBusGMethodInvocation *context);
#include "nm-manager-glue.h"
static void bluez_manager_bdaddr_added_cb (NMBluezManager *bluez_mgr,
@ -291,6 +294,7 @@ enum {
PROP_WIMAX_ENABLED,
PROP_WIMAX_HARDWARE_ENABLED,
PROP_ACTIVE_CONNECTIONS,
PROP_CONNECTIVITY,
/* Not exported */
PROP_HOSTNAME,
@ -577,11 +581,15 @@ checked_connectivity (GObject *object, GAsyncResult *result, gpointer user_data)
{
NMManager *manager = user_data;
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
NMConnectivityState connectivity;
if (priv->state == NM_STATE_CONNECTING || priv->state == NM_STATE_CONNECTED_SITE) {
if (nm_connectivity_check_finish (priv->connectivity, result, NULL))
connectivity = nm_connectivity_check_finish (priv->connectivity, result, NULL);
if (connectivity == NM_CONNECTIVITY_FULL)
set_state (manager, NM_STATE_CONNECTED_GLOBAL);
else
else if ( connectivity == NM_CONNECTIVITY_PORTAL
|| connectivity == NM_CONNECTIVITY_LIMITED)
set_state (manager, NM_STATE_CONNECTED_SITE);
}
@ -609,7 +617,7 @@ nm_manager_update_state (NMManager *manager)
if (state == NM_DEVICE_STATE_ACTIVATED) {
nm_connectivity_set_online (priv->connectivity, TRUE);
if (!nm_connectivity_get_connected (priv->connectivity)) {
if (nm_connectivity_get_state (priv->connectivity) != NM_CONNECTIVITY_FULL) {
new_state = NM_STATE_CONNECTING;
want_connectivity_check = TRUE;
} else {
@ -3953,6 +3961,87 @@ impl_manager_get_logging (NMManager *manager,
*domains = g_strdup (nm_logging_domains_to_string ());
}
static void
connectivity_check_done (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
DBusGMethodInvocation *context = user_data;
NMConnectivityState state;
GError *error = NULL;
state = nm_connectivity_check_finish (NM_CONNECTIVITY (object), result, &error);
if (error) {
dbus_g_method_return_error (context, error);
g_error_free (error);
} else
dbus_g_method_return (context, state);
}
static void
check_connectivity_auth_done_cb (NMAuthChain *chain,
GError *auth_error,
DBusGMethodInvocation *context,
gpointer user_data)
{
NMManager *self = NM_MANAGER (user_data);
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self);
GError *error = NULL;
NMAuthCallResult result;
priv->auth_chains = g_slist_remove (priv->auth_chains, chain);
result = nm_auth_chain_get_result (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL);
if (auth_error) {
nm_log_dbg (LOGD_CORE, "CheckConnectivity request failed: %s", auth_error->message);
error = g_error_new (NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
"Connectivity check request failed: %s",
auth_error->message);
} else if (result != NM_AUTH_CALL_RESULT_YES) {
error = g_error_new_literal (NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
"Not authorized to recheck connectivity");
} else {
/* it's allowed */
nm_connectivity_check_async (priv->connectivity,
connectivity_check_done,
context);
}
if (error) {
dbus_g_method_return_error (context, error);
g_error_free (error);
}
nm_auth_chain_unref (chain);
}
static void
impl_manager_check_connectivity (NMManager *manager,
DBusGMethodInvocation *context)
{
NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (manager);
NMAuthChain *chain;
const char *error_desc = NULL;
GError *error;
/* Validate the user request */
chain = nm_auth_chain_new (context, check_connectivity_auth_done_cb, manager, &error_desc);
if (chain) {
priv->auth_chains = g_slist_append (priv->auth_chains, chain);
nm_auth_chain_add_call (chain, NM_AUTH_PERMISSION_NETWORK_CONTROL, TRUE);
} else {
error = g_error_new_literal (NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
error_desc);
dbus_g_method_return_error (context, error);
g_error_free (error);
}
}
void
nm_manager_start (NMManager *self)
{
@ -4050,11 +4139,12 @@ connectivity_changed (NMConnectivity *connectivity,
gpointer user_data)
{
NMManager *self = NM_MANAGER (user_data);
gboolean connected;
NMConnectivityState state;
static const char *connectivity_states[] = { "UNKNOWN", "NONE", "PORTAL", "LIMITED", "FULL" };
connected = nm_connectivity_get_connected (connectivity);
state = nm_connectivity_get_state (connectivity);
nm_log_dbg (LOGD_CORE, "connectivity checking indicates %s",
connected ? "CONNECTED" : "NOT CONNECTED");
connectivity_states[state]);
nm_manager_update_state (self);
}
@ -4267,7 +4357,7 @@ nm_manager_new (NMSettings *settings,
priv = NM_MANAGER_GET_PRIVATE (singleton);
priv->connectivity = nm_connectivity_new ();
g_signal_connect (priv->connectivity, "notify::" NM_CONNECTIVITY_CONNECTED,
g_signal_connect (priv->connectivity, "notify::" NM_CONNECTIVITY_STATE,
G_CALLBACK (connectivity_changed), singleton);
bus = nm_dbus_manager_get_connection (priv->dbus_mgr);
@ -4639,6 +4729,9 @@ get_property (GObject *object, guint prop_id,
}
g_value_take_boxed (value, active);
break;
case PROP_CONNECTIVITY:
g_value_set_uint (value, nm_connectivity_get_state (priv->connectivity));
break;
case PROP_HOSTNAME:
g_value_set_string (value, priv->hostname);
break;
@ -4903,6 +4996,14 @@ nm_manager_class_init (NMManagerClass *manager_class)
DBUS_TYPE_G_ARRAY_OF_OBJECT_PATH,
G_PARAM_READABLE));
g_object_class_install_property
(object_class, PROP_CONNECTIVITY,
g_param_spec_uint (NM_MANAGER_CONNECTIVITY,
"Connectivity",
"Connectivity state",
NM_CONNECTIVITY_UNKNOWN, NM_CONNECTIVITY_FULL, NM_CONNECTIVITY_UNKNOWN,
G_PARAM_READABLE));
/* Hostname is not exported over D-Bus */
g_object_class_install_property
(object_class, PROP_HOSTNAME,

View file

@ -60,6 +60,7 @@ typedef enum {
#define NM_MANAGER_WIMAX_ENABLED "wimax-enabled"
#define NM_MANAGER_WIMAX_HARDWARE_ENABLED "wimax-hardware-enabled"
#define NM_MANAGER_ACTIVE_CONNECTIONS "active-connections"
#define NM_MANAGER_CONNECTIVITY "connectivity"
/* Not exported */
#define NM_MANAGER_HOSTNAME "hostname"