From f4befaab00cb365456b25954c60faecd180e64c5 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 3 Sep 2013 15:34:56 -0500 Subject: [PATCH 1/5] core: also check specific object when determining available connections --- src/devices/nm-device-bt.c | 4 ++- src/devices/nm-device-wifi.c | 11 ++++++- src/devices/nm-device.c | 46 +++++++++++++++++++++++++++-- src/devices/nm-device.h | 11 +++++-- src/devices/wimax/nm-device-wimax.c | 26 +++++++++++++++- 5 files changed, 89 insertions(+), 9 deletions(-) diff --git a/src/devices/nm-device-bt.c b/src/devices/nm-device-bt.c index 08c0f6dfad..e22c96eee3 100644 --- a/src/devices/nm-device-bt.c +++ b/src/devices/nm-device-bt.c @@ -221,7 +221,9 @@ check_connection_compatible (NMDevice *device, } static gboolean -check_connection_available (NMDevice *device, NMConnection *connection) +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) { NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE (device); guint32 bt_type; diff --git a/src/devices/nm-device-wifi.c b/src/devices/nm-device-wifi.c index e4c16613d9..855c1e7a51 100644 --- a/src/devices/nm-device-wifi.c +++ b/src/devices/nm-device-wifi.c @@ -1021,13 +1021,22 @@ check_connection_compatible (NMDevice *device, static gboolean -check_connection_available (NMDevice *device, NMConnection *connection) +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) { NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE (device); NMSettingWireless *s_wifi; const char *mode; GSList *ap_iter = NULL; + if (specific_object) { + NMAccessPoint *ap; + + ap = get_ap_by_path (NM_DEVICE_WIFI (device), specific_object); + return ap ? nm_ap_check_compatible (ap, connection) : FALSE; + } + s_wifi = nm_connection_get_setting_wireless (connection); /* Ad-Hoc and AP connections are always available because they may be diff --git a/src/devices/nm-device.c b/src/devices/nm-device.c index 660e26a59d..7fda6332a7 100644 --- a/src/devices/nm-device.c +++ b/src/devices/nm-device.c @@ -320,7 +320,9 @@ static gboolean nm_device_set_ip6_config (NMDevice *dev, static gboolean nm_device_activate_ip6_config_commit (gpointer user_data); -static gboolean check_connection_available (NMDevice *device, NMConnection *connection); +static gboolean check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object); static gboolean spec_match_list (NMDevice *device, const GSList *specs); @@ -6613,7 +6615,7 @@ _try_add_available_connection (NMDevice *self, NMConnection *connection) if (nm_device_check_connection_compatible (self, connection, NULL)) { /* Let subclasses implement additional checks on the connection */ if ( NM_DEVICE_GET_CLASS (self)->check_connection_available - && NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection)) { + && NM_DEVICE_GET_CLASS (self)->check_connection_available (self, connection, NULL)) { g_hash_table_insert (NM_DEVICE_GET_PRIVATE (self)->available_connections, g_object_ref (connection), @@ -6631,7 +6633,9 @@ _del_available_connection (NMDevice *device, NMConnection *connection) } static gboolean -check_connection_available (NMDevice *device, NMConnection *connection) +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) { /* Default is to assume the connection is available unless a subclass * overrides this with more specific checks. @@ -6658,6 +6662,42 @@ nm_device_recheck_available_connections (NMDevice *device) _signal_available_connections_changed (device); } +/** + * nm_device_get_available_connections: + * @device: the #NMDevice + * @specific_object: a specific object path if any + * + * Returns a list of connections available to activate on the device, taking + * into account any device-specific details given by @specific_object (like + * WiFi access point path). + * + * Returns: caller-owned #GPtrArray of #NMConnections + */ +GPtrArray * +nm_device_get_available_connections (NMDevice *device, const char *specific_object) +{ + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (device); + GHashTableIter iter; + guint num_available; + NMConnection *connection = NULL; + GPtrArray *array = NULL; + + num_available = g_hash_table_size (priv->available_connections); + if (num_available > 0) { + array = g_ptr_array_sized_new (num_available); + g_hash_table_iter_init (&iter, priv->available_connections); + while (g_hash_table_iter_next (&iter, (gpointer) &connection, NULL)) { + /* If a specific object is given, only include connections that are + * compatible with it. + */ + if ( !specific_object + || NM_DEVICE_GET_CLASS (device)->check_connection_available (device, connection, specific_object)) + g_ptr_array_add (array, connection); + } + } + return array; +} + static void cp_connection_added (NMConnectionProvider *cp, NMConnection *connection, gpointer user_data) { diff --git a/src/devices/nm-device.h b/src/devices/nm-device.h index 9993ec5439..b62f45ddc7 100644 --- a/src/devices/nm-device.h +++ b/src/devices/nm-device.h @@ -140,11 +140,13 @@ typedef struct { GError **error); /* Checks whether the connection is likely available to be activated, - * including any live network information like scan lists. Returns - * TRUE if the connection is available; FALSE if not. + * including any live network information like scan lists. The connection + * is checked against the object defined by @specific_object, if given. + * Returns TRUE if the connection is available; FALSE if not. */ gboolean (* check_connection_available) (NMDevice *self, - NMConnection *connection); + NMConnection *connection, + const char *specific_object); gboolean (* complete_connection) (NMDevice *self, NMConnection *connection, @@ -337,6 +339,9 @@ void nm_device_add_pending_action (NMDevice *device, const char *action); void nm_device_remove_pending_action (NMDevice *device, const char *action); gboolean nm_device_has_pending_action (NMDevice *device); +GPtrArray *nm_device_get_available_connections (NMDevice *device, + const char *specific_object); + G_END_DECLS /* For testing only */ diff --git a/src/devices/wimax/nm-device-wimax.c b/src/devices/wimax/nm-device-wimax.c index da92e014c4..0061e79db2 100644 --- a/src/devices/wimax/nm-device-wimax.c +++ b/src/devices/wimax/nm-device-wimax.c @@ -261,6 +261,22 @@ get_nsp_by_name (NMDeviceWimax *self, const char *name) return NULL; } +static NMWimaxNsp * +get_nsp_by_path (NMDeviceWimax *self, const char *path) +{ + NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (self); + GSList *iter; + + for (iter = priv->nsp_list; iter; iter = iter->next) { + NMWimaxNsp *nsp = NM_WIMAX_NSP (iter->data); + + if (!g_strcmp0 (nm_wimax_nsp_get_dbus_path (nsp), path)) + return nsp; + } + + return NULL; +} + static gboolean update_availability (NMDeviceWimax *self, gboolean old_available) { @@ -375,10 +391,18 @@ check_connection_compatible (NMDevice *device, } static gboolean -check_connection_available (NMDevice *device, NMConnection *connection) +check_connection_available (NMDevice *device, + NMConnection *connection, + const char *specific_object) { NMDeviceWimaxPrivate *priv = NM_DEVICE_WIMAX_GET_PRIVATE (device); const GSList *ns_iter = NULL; + NMWimaxNsp *nsp; + + if (specific_object) { + nsp = get_nsp_by_path (NM_DEVICE_WIMAX (device), specific_object); + return nsp ? nm_wimax_nsp_check_compatible (nsp, connection) : FALSE; + } /* Ensure the connection applies to an NSP in the scan list */ for (ns_iter = priv->nsp_list; ns_iter; ns_iter = ns_iter->next) { From 35124dbb1432693d650d835dde95d2d4a6e0b65f Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 24 Jul 2013 10:41:39 -0500 Subject: [PATCH 2/5] core: extend ActivateConnection to allow NULL connection paths When called with a connection path, activates that connection. When called without a connection path, picks the best available connection to activate for that device. Doesn't work with VPN connections because they don't have devices. --- introspection/nm-manager.xml | 11 ++++++-- src/nm-manager.c | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/introspection/nm-manager.xml b/introspection/nm-manager.xml index a3d6647926..e076e0516e 100644 --- a/introspection/nm-manager.xml +++ b/introspection/nm-manager.xml @@ -41,12 +41,17 @@ - The connection to activate the devices with. + The connection to activate. If "/" is given, a valid device path must + be given, and NetworkManager picks the best connection to activate for + the given device. VPN connections must always pass a valid connection + path. - The object path of device to be activated for physical connections. This parameter is ignored for VPN connections, because the specific_object (if provided) specifies the device to use. + The object path of device to be activated for physical connections. + This parameter is ignored for VPN connections, because the + specific_object (if provided) specifies the device to use. @@ -55,7 +60,7 @@ This parameter is currently ignored for wired and mobile broadband connections, and the value of "/" should be used (ie, no specific object). For WiFi connections, pass the object path of a specific AP from the card's scan - list, or "/" to pick and AP automatically. For VPN connections, pass + list, or "/" to pick an AP automatically. For VPN connections, pass the object path of an ActiveConnection object that should serve as the "base" connection (to which the VPN connections lifetime will be tied), or pass "/" and NM will automatically use the current default device. diff --git a/src/nm-manager.c b/src/nm-manager.c index af663c95ab..9b6a48fac2 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -3110,6 +3110,61 @@ impl_manager_activate_connection (NMManager *self, gboolean is_vpn = FALSE; GError *error = NULL; + /* Normalize object paths */ + if (g_strcmp0 (connection_path, "/") == 0) + connection_path = NULL; + if (g_strcmp0 (specific_object_path, "/") == 0) + specific_object_path = NULL; + if (g_strcmp0 (device_path, "/") == 0) + device_path = NULL; + + /* If the connection path is given and valid, that connection is activated. + * Otherwise the "best" connection for the device is chosen and activated, + * regardless of whether that connection is autoconnect-enabled or not + * (since this is an explicit request, not an auto-activation request). + */ + if (!connection_path) { + GPtrArray *available; + guint64 best_timestamp = 0; + guint i; + + /* If no connection is given, find a suitable connection for the given device path */ + if (!device_path) { + error = g_error_new_literal (NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Only devices may be activated without a specifying a connection"); + goto error; + } + device = nm_manager_get_device_by_path (self, device_path); + if (!device) { + error = g_error_new (NM_MANAGER_ERROR, NM_MANAGER_ERROR_UNKNOWN_DEVICE, + "Cannot activate unknown device %s", device_path); + goto error; + } + + available = nm_device_get_available_connections (device, specific_object_path); + for (i = 0; available && i < available->len; i++) { + NMSettingsConnection *candidate = g_ptr_array_index (available, i); + guint64 candidate_timestamp = 0; + + nm_settings_connection_get_timestamp (candidate, &candidate_timestamp); + if (!connection_path || (candidate_timestamp > best_timestamp)) { + connection_path = nm_connection_get_path (NM_CONNECTION (candidate)); + best_timestamp = candidate_timestamp; + } + } + + if (available) + g_ptr_array_free (available, TRUE); + + if (!connection_path) { + error = g_error_new_literal (NM_MANAGER_ERROR, + NM_MANAGER_ERROR_UNKNOWN_CONNECTION, + "The device has no connections available."); + goto error; + } + } + + g_assert (connection_path); connection = (NMConnection *) nm_settings_get_connection_by_path (priv->settings, connection_path); if (!connection) { error = g_error_new_literal (NM_MANAGER_ERROR, From 4ec5f5c8e3025b5221737624ddc06d6bc7f6d00d Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Tue, 3 Sep 2013 14:54:24 -0500 Subject: [PATCH 3/5] libnm-glib: add support for NULL connections to nm_client_activate_connection() Pass along to NetworkManager, which picks the best available connection for the device and activates it. --- libnm-glib/nm-client.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libnm-glib/nm-client.c b/libnm-glib/nm-client.c index c424037bc0..1ffb8668f7 100644 --- a/libnm-glib/nm-client.c +++ b/libnm-glib/nm-client.c @@ -594,7 +594,7 @@ activate_nm_not_running (gpointer user_data) /** * nm_client_activate_connection: * @client: a #NMClient - * @connection: an #NMConnection + * @connection: (allow-none): an #NMConnection * @device: (allow-none): the #NMDevice * @specific_object: (allow-none): the object path of a connection-type-specific * object this activation should use. This parameter is currently ignored for @@ -613,6 +613,9 @@ activate_nm_not_running (gpointer user_data) * #NMWimaxNsp for WiMAX connections, to which you wish to connect. If the * specific object is not given, NetworkManager can, in some cases, automatically * determine which network to connect to given the settings in @connection. + * + * If @connection is not given for a device-based activation, NetworkManager + * picks the best available connection for the device and activates it. **/ void nm_client_activate_connection (NMClient *client, @@ -628,7 +631,8 @@ nm_client_activate_connection (NMClient *client, g_return_if_fail (NM_IS_CLIENT (client)); if (device) g_return_if_fail (NM_IS_DEVICE (device)); - g_return_if_fail (NM_IS_CONNECTION (connection)); + if (connection) + g_return_if_fail (NM_IS_CONNECTION (connection)); info = g_slice_new0 (ActivateInfo); info->act_fn = callback; @@ -645,7 +649,7 @@ nm_client_activate_connection (NMClient *client, dbus_g_proxy_begin_call (priv->client_proxy, "ActivateConnection", activate_cb, info, NULL, - DBUS_TYPE_G_OBJECT_PATH, nm_connection_get_path (connection), + DBUS_TYPE_G_OBJECT_PATH, connection ? nm_connection_get_path (connection) : "/", DBUS_TYPE_G_OBJECT_PATH, device ? nm_object_get_path (NM_OBJECT (device)) : "/", DBUS_TYPE_G_OBJECT_PATH, specific_object ? specific_object : "/", G_TYPE_INVALID); From 42c7ea85a17a7e7459942f356399e36308011c86 Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Fri, 6 Sep 2013 10:05:47 -0500 Subject: [PATCH 4/5] cli: add support for 'nmcli con up ifname XXX' Passes a NULL connection to nm_client_activate_connection() allowing NetworkManager to pick the best available connection for the interface. --- cli/completion/nmcli | 19 ++++++++++++--- cli/src/connections.c | 54 +++++++++++++++++++++++-------------------- man/nmcli.1.in | 17 ++++++++++---- 3 files changed, 57 insertions(+), 33 deletions(-) diff --git a/cli/completion/nmcli b/cli/completion/nmcli index e2aff2969b..8fdc36eb07 100644 --- a/cli/completion/nmcli +++ b/cli/completion/nmcli @@ -502,6 +502,13 @@ _nmcli_complete_COMMAND_CONNECTION() fi words=("${words[@]:2}") ;; + ifname) + if [[ ${#words[@]} -eq 2 ]]; then + _nmcli_list_nl "$(_nmcli_NM_devices)" + return 0 + fi + words=("${words[@]:2}") + ;; *) COMMAND_CONNECTION_TYPE= COMMAND_CONNECTION_ID="${words[0]}" @@ -601,12 +608,18 @@ _nmcli() ;; u|up) if [[ ${#words[@]} -eq 3 ]]; then - _nmcli_list_nl "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_id)")" + _nmcli_list_nl "$(printf "ifname\nid\nuuid\npath\n%s" "$(_nmcli_con_id)")" elif [[ ${#words[@]} -gt 3 ]]; then + local COMMAND_CONNECTION_TYPE='' words=("${words[@]:2}") - OPTIONS=(id uuid path) + OPTIONS=(ifname id uuid path) _nmcli_complete_COMMAND_CONNECTION && return 0 - OPTIONS=(ifname ap nsp) + + if [[ "$COMMAND_CONNECTION_TYPE" = "ifname" ]]; then + OPTIONS=(ap nsp) + else + OPTIONS=(ifname ap nsp) + fi _nmcli_complete_COMMAND_ARGS fi ;; diff --git a/cli/src/connections.c b/cli/src/connections.c index aea6ecd36c..8dac4f10c9 100644 --- a/cli/src/connections.c +++ b/cli/src/connections.c @@ -218,9 +218,9 @@ usage (void) " show configured [[ id | uuid | path ] ]\n\n" " show active [[ id | uuid | path | apath ] ]\n\n" #if WITH_WIMAX - " up [ id | uuid | path ] [ifname ] [ap ] [nsp ]\n\n" + " up [[ id | uuid | path ] ] [ifname ] [ap ] [nsp ]\n\n" #else - " up [ id | uuid | path ] [ifname ] [ap ]\n\n" + " up [[ id | uuid | path ] ] [ifname ] [ap ]\n\n" #endif " down [ id | uuid | path | apath ] \n\n" " add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS IP_OPTIONS\n\n" @@ -1517,18 +1517,31 @@ nmc_activate_connection (NmCli *nmc, GError *local = NULL; g_return_val_if_fail (nmc != NULL, FALSE); - g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE); + g_return_val_if_fail (NM_IS_CONNECTION (connection) || ifname, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); - if (nm_connection_get_virtual_iface_name (connection)) - is_virtual = TRUE; + if (connection) { + if (nm_connection_get_virtual_iface_name (connection)) + is_virtual = TRUE; - device_found = find_device_for_connection (nmc, connection, ifname, ap, nsp, &device, &spec_object, &local); - /* Virtual connection may not have their interfaces created yet */ - if (!device_found && !is_virtual) { - g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_CON_ACTIVATION, - "%s", local && local->message ? local->message : _("unknown error")); - g_clear_error (&local); + device_found = find_device_for_connection (nmc, connection, ifname, ap, nsp, &device, &spec_object, &local); + /* Virtual connection may not have their interfaces created yet */ + if (!device_found && !is_virtual) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_CON_ACTIVATION, + "%s", local && local->message ? local->message : _("unknown error")); + g_clear_error (&local); + return FALSE; + } + } else if (ifname) { + device = nm_client_get_device_by_iface (nmc->client, ifname); + if (!device) { + g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_CON_ACTIVATION, + _("unknown device '%s'."), ifname); + return FALSE; + } + } else { + g_set_error_literal (error, NMCLI_ERROR, NMC_RESULT_ERROR_CON_ACTIVATION, + _("no connection and no device given.")); return FALSE; } @@ -1554,7 +1567,7 @@ do_connection_up (NmCli *nmc, int argc, char **argv) const char *nsp = NULL; GError *error = NULL; const char *selector = NULL; - const char *name; + const char *name = NULL; char *line = NULL; /* @@ -1570,12 +1583,8 @@ do_connection_up (NmCli *nmc, int argc, char **argv) name = line ? line : ""; // TODO: enhancement: when just Enter is pressed (line is NULL), list // available connections so that the user can select one - } else { - g_string_printf (nmc->return_text, _("Error: No connection specified.")); - nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; - goto error; } - } else { + } else if (strcmp (*argv, "ifname") != 0) { if ( strcmp (*argv, "id") == 0 || strcmp (*argv, "uuid") == 0 || strcmp (*argv, "path") == 0) { @@ -1589,16 +1598,11 @@ do_connection_up (NmCli *nmc, int argc, char **argv) name = *argv; } name = *argv; + next_arg (&argc, &argv); } - connection = find_connection (nmc->system_connections, selector, name); - - if (!connection) { - g_string_printf (nmc->return_text, _("Error: Unknown connection: %s."), name); - nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; - goto error; - } - next_arg (&argc, &argv); + if (name) + connection = find_connection (nmc->system_connections, selector, name); while (argc > 0) { if (strcmp (*argv, "ifname") == 0) { diff --git a/man/nmcli.1.in b/man/nmcli.1.in index 351453229d..c65e328f79 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -304,14 +304,20 @@ When no command is given to the \fIconnection\fP object, the default action is 'nmcli connection show configured'. .TP .B up [ id | uuid | path ] [ifname ] [ap ] [nsp ] +.RE +.RS +.B up ifname [ap ] [nsp ] +.RS .br Activate a connection. The connection is identified by its name, UUID or D-Bus path. If is ambiguous, a keyword \fIid\fP, \fIuuid\fP or \fIpath\fP can be -used. When requiring a particular device to activate the connection on, the -\fIifname\fP option with interface name should be given. In case of a VPN -connection, the \fIifname\fP option specify the device of the base connection. -The \fIap\fP option specify what particular AP should be used in case of -a Wi\(hyFi connection. +used. When requiring a particular device to activate the connection on, the +\fIifname\fP option with interface name should be given. If the is not +given an \fIifname\fP is required, and NetworkManager will activate the best +available connection for the given \fIifname\fP. In case of a VPN connection, +the \fIifname\fP option specifies the device of the base connection. The +\fIap\fP option specify what particular AP should be used in case of a Wi\(hyFi +connection. .br If '--wait' option is not specified, the default timeout will be 90 seconds. .br @@ -326,6 +332,7 @@ Available options are: .IP \fInsp\fP 13 \(en NSP (Network Service Provider) which the command should connect to (for WiMAX connections) .RE +.RE .TP .B down [ id | uuid | path | apath ] .br From 7d57386e04906ce2e0d54f09081169f1ac21142c Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Mon, 16 Sep 2013 21:03:34 -0500 Subject: [PATCH 5/5] cli: add support for 'nmcli dev connect ifname XXX' --- cli/completion/nmcli | 3 +- cli/src/devices.c | 178 ++++++++++++++++++++++++++++++++++++++----- man/nmcli.1.in | 9 ++- 3 files changed, 171 insertions(+), 19 deletions(-) diff --git a/cli/completion/nmcli b/cli/completion/nmcli index 8fdc36eb07..86e42fec5a 100644 --- a/cli/completion/nmcli +++ b/cli/completion/nmcli @@ -854,12 +854,13 @@ _nmcli() ;; d|de|dev|devi|devic|device) if [[ ${#words[@]} -eq 2 ]]; then - _nmcli_list "status show disconnect wifi wimax help" + _nmcli_list "status show connect disconnect wifi wimax help" elif [[ ${#words[@]} -gt 2 ]]; then case "$command" in s|st|sta|stat|statu|status) ;; sh|sho|show| \ + c|co|con|conn|conne|connec|connect| \ d|di|dis|disc|disco|discon|disconn|disconne|disconnec|disconnect) if [[ ${#words[@]} -eq 3 ]]; then _nmcli_list_nl "$(_nmcli_NM_devices)" diff --git a/cli/src/devices.c b/cli/src/devices.c index 47ae792046..b0e9f510e6 100644 --- a/cli/src/devices.c +++ b/cli/src/devices.c @@ -262,12 +262,13 @@ usage (void) fprintf (stderr, _("Usage: nmcli device { COMMAND | help }\n\n" #if WITH_WIMAX - " COMMAND := { status | show | disconnect | wifi | wimax }\n\n" + " COMMAND := { status | show | connect | disconnect | wifi | wimax }\n\n" #else - " COMMAND := { status | show | disconnect | wifi }\n\n" + " COMMAND := { status | show | connect | disconnect | wifi }\n\n" #endif " status\n\n" " show []\n\n" + " connect \n\n" " disconnect \n\n" " wifi [list [ifname ] [bssid ]]\n\n" " wifi connect <(B)SSID> [password ] [wep-key-type key|phrase] [ifname ] [bssid ] [name ]\n\n" @@ -1142,20 +1143,6 @@ error: return nmc->return_value; } -static void -device_state_cb (NMDevice *device, GParamSpec *pspec, gpointer user_data) -{ - NmCli *nmc = (NmCli *) user_data; - NMDeviceState state; - - state = nm_device_get_state (device); - - if (state == NM_DEVICE_STATE_DISCONNECTED) { - g_string_printf (nmc->return_text, _("Success: Device '%s' successfully disconnected."), nm_device_get_iface (device)); - quit (); - } -} - static gboolean timeout_cb (gpointer user_data) { @@ -1179,6 +1166,160 @@ progress_cb (gpointer user_data) return TRUE; } +static void +connected_state_cb (NMDevice *device, GParamSpec *pspec, gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + NMDeviceState state; + + state = nm_device_get_state (device); + + if (state == NM_DEVICE_STATE_ACTIVATED) { + g_string_printf (nmc->return_text, _("Success: Device '%s' successfully activated."), nm_device_get_iface (device)); + quit (); + } +} + +static void +connect_device_cb (NMClient *client, NMActiveConnection *active, GError *error, gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + const GPtrArray *devices; + NMDevice *device; + NMDeviceState state; + + if (error) { + g_string_printf (nmc->return_text, _("Error: Device activation failed: %s"), + error->message ? error->message : _("(unknown)")); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + quit (); + } else { + g_assert (active); + devices = nm_active_connection_get_devices (active); + g_assert (devices && devices->len); + device = g_ptr_array_index (devices, 0); + g_assert (device); + + state = nm_device_get_state (device); + + if (nmc->nowait_flag || state == NM_DEVICE_STATE_ACTIVATED) { + /* Don't want to wait or device already activated */ + if (state == NM_DEVICE_STATE_ACTIVATED && nmc->print_output == NMC_PRINT_PRETTY) { + nmc_terminal_erase_line (); + printf (_("Device '%s' has been connected.\n"), nm_device_get_iface (device)); + } + quit (); + } else { + g_signal_connect (device, "notify::state", G_CALLBACK (connected_state_cb), nmc); + /* Start timer not to loop forever if "notify::state" signal is not issued */ + g_timeout_add_seconds (nmc->timeout, timeout_cb, nmc); + } + + } +} + +static NMCResultCode +do_device_connect (NmCli *nmc, int argc, char **argv) +{ + NMDevice **devices; + NMDevice *device = NULL; + const char *ifname = NULL; + char *ifname_ask = NULL; + int i; + + /* Set default timeout for connect operation. */ + if (nmc->timeout == -1) + nmc->timeout = 90; + + if (argc == 0) { + if (nmc->ask) { + ifname = ifname_ask = nmc_get_user_input (_("Interface: ")); + // TODO: list available devices when just Enter is pressed ? + } + if (!ifname_ask) { + g_string_printf (nmc->return_text, _("Error: No interface specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + } else { + ifname = *argv; + } + + if (!ifname) { + g_string_printf (nmc->return_text, _("Error: No interface specified.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + if (next_arg (&argc, &argv) == 0) { + g_string_printf (nmc->return_text, _("Error: extra argument not allowed: '%s'."), *argv); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto error; + } + + nmc->get_client (nmc); + if (!nm_client_get_manager_running (nmc->client)) { + g_string_printf (nmc->return_text, _("Error: NetworkManager is not running.")); + nmc->return_value = NMC_RESULT_ERROR_NM_NOT_RUNNING; + goto error; + } + + if (!nmc_versions_match (nmc)) + goto error; + + devices = get_devices_sorted (nmc->client); + for (i = 0; devices[i]; i++) { + NMDevice *candidate = devices[i]; + const char *dev_iface = nm_device_get_iface (candidate); + + if (!g_strcmp0 (dev_iface, ifname)) + device = candidate; + } + g_free (devices); + + if (!device) { + g_string_printf (nmc->return_text, _("Error: Device '%s' not found."), ifname); + nmc->return_value = NMC_RESULT_ERROR_UNKNOWN; + goto error; + } + + /* + * Use nowait_flag instead of should_wait, because exiting has to be postponed + * till connect_device_cb() is called, giving NM time to check our permissions. + */ + nmc->nowait_flag = (nmc->timeout == 0); + nmc->should_wait = TRUE; + nm_client_activate_connection (nmc->client, + NULL, /* let NM find a connection automatically */ + device, + NULL, + connect_device_cb, + nmc); + + /* Start progress indication */ + if (nmc->print_output == NMC_PRINT_PRETTY) + progress_id = g_timeout_add (120, progress_cb, device); + +error: + g_free (ifname_ask); + + return nmc->return_value; +} + +static void +disconnect_state_cb (NMDevice *device, GParamSpec *pspec, gpointer user_data) +{ + NmCli *nmc = (NmCli *) user_data; + NMDeviceState state; + + state = nm_device_get_state (device); + + if (state == NM_DEVICE_STATE_DISCONNECTED) { + g_string_printf (nmc->return_text, _("Success: Device '%s' successfully disconnected."), nm_device_get_iface (device)); + quit (); + } +} + static void disconnect_device_cb (NMDevice *device, GError *error, gpointer user_data) { @@ -1204,7 +1345,7 @@ disconnect_device_cb (NMDevice *device, GError *error, gpointer user_data) } quit (); } else { - g_signal_connect (device, "notify::state", G_CALLBACK (device_state_cb), nmc); + g_signal_connect (device, "notify::state", G_CALLBACK (disconnect_state_cb), nmc); /* Start timer not to loop forever if "notify::state" signal is not issued */ g_timeout_add_seconds (nmc->timeout, timeout_cb, nmc); } @@ -2296,6 +2437,9 @@ do_devices (NmCli *nmc, int argc, char **argv) nmc->multiline_output = TRUE; /* multiline mode is default for 'device show' */ nmc->return_value = do_devices_show (nmc, argc-1, argv+1); } + else if (matches (*argv, "connect") == 0) { + nmc->return_value = do_device_connect (nmc, argc-1, argv+1); + } else if (matches (*argv, "disconnect") == 0) { nmc->return_value = do_device_disconnect (nmc, argc-1, argv+1); } diff --git a/man/nmcli.1.in b/man/nmcli.1.in index c65e328f79..df2e74d872 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -615,7 +615,7 @@ then \fINetworkManager\fP will reload connection files any time they change .B device - show and manage network interfaces .br .TP -.SS \fICOMMAND\fP := { status | show | disconnect | wifi | wimax } +.SS \fICOMMAND\fP := { status | show | connect | disconnect | wifi | wimax } .sp .RS .TP @@ -631,6 +631,13 @@ Show detailed information about devices. Without an argument, all devices are examined. To get information for a specific device, the interface name has to be provided. .TP +.B connect +.br +Connect the device. NetworkManager will try to find a suitable connection that +will be activated. It will also consider connections that are not set to auto connect. +.br +If '--wait' option is not specified, the default timeout will be 90 seconds. +.TP .B disconnect .br Disconnect a device and prevent the device from automatically activating further