merge: add 'nmcli device wifi hotspot' command (bgo #756081)

Synopsis:
nmcli device wifi hotspot [ifname <ifname>] [con-name <name>] [ssid <SSID>]
                          [band a|bg] [channel <channel>]
                          [password <password>] [--show-password]

https://bugzilla.gnome.org/show_bug.cgi?id=756081
This commit is contained in:
Jiří Klimeš 2015-11-10 09:30:39 +01:00
commit 2b4b78eba1
6 changed files with 451 additions and 33 deletions

View file

@ -1011,6 +1011,29 @@ nmc_secrets_requested (NMSecretAgentSimple *agent,
}
}
char *
nmc_unique_connection_name (const GPtrArray *connections, const char *try_name)
{
NMConnection *connection;
const char *name;
char *new_name;
unsigned int num = 1;
int i = 0;
new_name = g_strdup (try_name);
while (i < connections->len) {
connection = NM_CONNECTION (connections->pdata[i]);
name = nm_connection_get_id (connection);
if (g_strcmp0 (new_name, name) == 0) {
g_free (new_name);
new_name = g_strdup_printf ("%s-%d", try_name, num++);
i = 0;
} else
i++;
}
return new_name;
}
/**
* nmc_cleanup_readline:

View file

@ -57,6 +57,9 @@ void nmc_secrets_requested (NMSecretAgentSimple *agent,
GPtrArray *secrets,
gpointer user_data);
char *nmc_unique_connection_name (const GPtrArray *connections,
const char *try_name);
void nmc_cleanup_readline (void);
char *nmc_readline (const char *prompt_fmt, ...) G_GNUC_PRINTF (1, 2);
char *nmc_rl_gen_func_basic (const char *text, int state, const char **words);

View file

@ -5810,30 +5810,6 @@ cleanup_bridge_slave:
return TRUE;
}
static char *
unique_connection_name (const GPtrArray *connections, const char *try_name)
{
NMConnection *connection;
const char *name;
char *new_name;
unsigned int num = 1;
int i = 0;
new_name = g_strdup (try_name);
while (i < connections->len) {
connection = NM_CONNECTION (connections->pdata[i]);
name = nm_connection_get_id (connection);
if (g_strcmp0 (new_name, name) == 0) {
g_free (new_name);
new_name = g_strdup_printf ("%s-%d", try_name, num++);
i = 0;
} else
i++;
}
return new_name;
}
typedef struct {
NmCli *nmc;
char *con_name;
@ -6180,7 +6156,7 @@ do_connection_add (NmCli *nmc, int argc, char **argv)
char *try_name = ifname ?
g_strdup_printf ("%s-%s", get_name_alias (setting_name, nmc_valid_connection_types), ifname)
: g_strdup (get_name_alias (setting_name, nmc_valid_connection_types));
default_name = unique_connection_name (nmc->connections, try_name);
default_name = nmc_unique_connection_name (nmc->connections, try_name);
g_free (try_name);
}
@ -9015,8 +8991,8 @@ do_connection_edit (NmCli *nmc, int argc, char **argv)
if (con_name)
default_name = g_strdup (con_name);
else
default_name = unique_connection_name (nmc->connections,
get_name_alias (connection_type, nmc_valid_connection_types));
default_name = nmc_unique_connection_name (nmc->connections,
get_name_alias (connection_type, nmc_valid_connection_types));
g_object_set (s_con,
NM_SETTING_CONNECTION_ID, default_name,

View file

@ -267,6 +267,8 @@ usage (void)
" wifi [list [ifname <ifname>] [bssid <BSSID>]]\n\n"
" wifi connect <(B)SSID> [password <password>] [wep-key-type key|phrase] [ifname <ifname>]\n"
" [bssid <BSSID>] [name <name>] [private yes|no] [hidden yes|no]\n\n"
" wifi hotspot [ifname <ifname>] [con-name <name>] [ssid <SSID>] [band a|bg] [channel <channel>]\n\n"
" [password <password>] [--show-password]\n\n"
" wifi rescan [ifname <ifname>] [[ssid <SSID to scan>] ...]\n\n"
));
}
@ -371,6 +373,21 @@ usage_device_wifi (void)
"only open, WEP and WPA-PSK networks are supported at the moment. It is also\n"
"assumed that IP configuration is obtained via DHCP.\n"
"\n"
"ARGUMENTS := wifi hotspot [ifname <ifname>] [con-name <name>] [ssid <SSID>]\n"
" [band a|bg] [channel <channel>] [password <password>]\n"
" [--show-password]\n"
"\n"
"Create a Wi-Fi hotspot. Use 'connection down' or 'device disconnect'\n"
"to stop the hotspot.\n"
"Parameters of the hotspot can be influenced by the optional parameters:\n"
"ifname - Wi-Fi device to use\n"
"con-name - name of the created hotspot connection profile\n"
"ssid - SSID of the hotspot\n"
"band - Wi-Fi band to use\n"
"channel - Wi-Fi channel to use\n"
"password - password to use for the hotspot\n"
"--show-password - tell nmcli to print password to stdout\n"
"\n"
"ARGUMENTS := rescan [ifname <ifname>] [[ssid <SSID to scan>] ...]\n"
"\n"
"Request that NetworkManager immediately re-scan for available access points.\n"
@ -1349,6 +1366,7 @@ connected_state_cb (NMDevice *device, NMActiveConnection *active)
typedef struct {
NmCli *nmc;
NMDevice *device;
gboolean hotspot;
} AddAndActivateInfo;
static void
@ -1366,8 +1384,12 @@ add_and_activate_cb (GObject *client,
active = nm_client_add_and_activate_connection_finish (NM_CLIENT (client), result, &error);
if (error) {
g_string_printf (nmc->return_text, _("Error: Failed to add/activate new connection: %s"),
error->message);
if (info->hotspot)
g_string_printf (nmc->return_text, _("Error: Failed to setup a Wi-Fi hotspot: %s"),
error->message);
else
g_string_printf (nmc->return_text, _("Error: Failed to add/activate new connection: %s"),
error->message);
g_error_free (error);
nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION;
quit ();
@ -1375,7 +1397,10 @@ add_and_activate_cb (GObject *client,
state = nm_active_connection_get_state (active);
if (state == NM_ACTIVE_CONNECTION_STATE_UNKNOWN) {
g_string_printf (nmc->return_text, _("Error: Failed to add/activate new connection: Unknown error"));
if (info->hotspot)
g_string_printf (nmc->return_text, _("Error: Failed to setup a Wi-Fi hotspot"));
else
g_string_printf (nmc->return_text, _("Error: Failed to add/activate new connection: Unknown error"));
nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION;
g_object_unref (active);
quit ();
@ -1386,8 +1411,12 @@ add_and_activate_cb (GObject *client,
if (state == NM_ACTIVE_CONNECTION_STATE_ACTIVATED) {
if (nmc->print_output == NMC_PRINT_PRETTY)
nmc_terminal_erase_line ();
g_print (_("Connection with UUID '%s' created and activated on device '%s'\n"),
nm_active_connection_get_uuid (active), nm_device_get_iface (device));
if (info->hotspot)
g_print (_("Connection with UUID '%s' created and activated on device '%s'\n"),
nm_active_connection_get_uuid (active), nm_device_get_iface (device));
else
g_print (_("Hotspot '%s' activated on device '%s'\n"),
nm_active_connection_get_id (active), nm_device_get_iface (device));
}
g_object_unref (active);
quit ();
@ -1566,6 +1595,7 @@ do_device_connect (NmCli *nmc, int argc, char **argv)
info = g_malloc0 (sizeof (AddAndActivateInfo));
info->nmc = nmc;
info->device = device;
info->hotspot = FALSE;
nm_client_activate_connection_async (nmc->client,
NULL, /* let NM find a connection automatically */
@ -2659,6 +2689,7 @@ do_device_wifi_connect_network (NmCli *nmc, int argc, char **argv)
info = g_malloc0 (sizeof (AddAndActivateInfo));
info->nmc = nmc;
info->device = device;
info->hotspot = FALSE;
nm_client_add_and_activate_connection_async (nmc->client,
connection,
@ -2679,6 +2710,342 @@ error:
return nmc->return_value;
}
static GBytes *
generate_ssid_for_hotspot (const char *ssid)
{
GBytes *ssid_bytes;
char *hotspot_ssid = NULL;
if (!ssid) {
hotspot_ssid = g_strdup_printf ("Hotspot-%s", g_get_host_name ());
if (strlen (hotspot_ssid) > 32)
hotspot_ssid[32] = '\0';
ssid = hotspot_ssid;
}
ssid_bytes = g_bytes_new (ssid, strlen (ssid));
g_free (hotspot_ssid);
return ssid_bytes;
}
#define WPA_PASSKEY_SIZE 8
static void
generate_wpa_key (char *key, size_t len)
{
guint i;
g_return_if_fail (key);
g_return_if_fail (len > WPA_PASSKEY_SIZE);
/* generate a 8-chars ASCII WPA key */
for (i = 0; i < WPA_PASSKEY_SIZE; i++) {
int c;
c = g_random_int_range (33, 126);
/* too many non alphanumeric characters are hard to remember for humans */
while (!g_ascii_isalnum (c))
c = g_random_int_range (33, 126);
key[i] = (gchar) c;
}
key[WPA_PASSKEY_SIZE] = '\0';
}
static void
generate_wep_key (char *key, size_t len)
{
int i;
const char *hexdigits = "0123456789abcdef";
g_return_if_fail (key);
g_return_if_fail (len > 10);
/* generate a 10-digit hex WEP key */
for (i = 0; i < 10; i++) {
int digit;
digit = g_random_int_range (0, 16);
key[i] = hexdigits[digit];
}
key[10] = '\0';
}
static gboolean
set_wireless_security_for_hotspot (NMSettingWirelessSecurity *s_wsec,
const char *wifi_mode,
NMDeviceWifiCapabilities caps,
const char *password,
gboolean show_password,
GError **error)
{
char generated_key[11];
const char *key;
const char *key_mgmt;
if (g_strcmp0 (wifi_mode, NM_SETTING_WIRELESS_MODE_AP) == 0) {
if (caps & NM_WIFI_DEVICE_CAP_RSN) {
nm_setting_wireless_security_add_proto (s_wsec, "rsn");
nm_setting_wireless_security_add_pairwise (s_wsec, "ccmp");
nm_setting_wireless_security_add_group (s_wsec, "ccmp");
key_mgmt = "wpa-psk";
} else if (caps & NM_WIFI_DEVICE_CAP_WPA) {
nm_setting_wireless_security_add_proto (s_wsec, "wpa");
nm_setting_wireless_security_add_pairwise (s_wsec, "tkip");
nm_setting_wireless_security_add_group (s_wsec, "tkip");
key_mgmt = "wpa-psk";
} else
key_mgmt = "none";
} else
key_mgmt = "none";
if (g_strcmp0 (key_mgmt, "wpa-psk") == 0) {
/* use WPA */
if (password) {
if (!nm_utils_wpa_psk_valid (password)) {
g_set_error (error, NMCLI_ERROR, 0, _("'%s' is not valid WPA PSK"), password);
return FALSE;
}
key = password;
} else {
generate_wpa_key (generated_key, sizeof (generated_key));
key = generated_key;
}
g_object_set (s_wsec,
NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, key_mgmt,
NM_SETTING_WIRELESS_SECURITY_PSK, key,
NULL);
} else {
/* use WEP */
if (password) {
if (!nm_utils_wep_key_valid (password, NM_WEP_KEY_TYPE_KEY)) {
g_set_error (error, NMCLI_ERROR, 0,
_("'%s' is not valid WEP key (it should be 5 or 13 ASCII chars)"),
password);
return FALSE;
}
key = password;
} else {
generate_wep_key (generated_key, sizeof (generated_key));
key = generated_key;
}
g_object_set (s_wsec,
NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, key_mgmt,
NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, key,
NM_SETTING_WIRELESS_SECURITY_WEP_KEY_TYPE, NM_WEP_KEY_TYPE_KEY,
NULL);
}
if (show_password)
g_print (_("Hotspot password: %s\n"), key);
return TRUE;
}
static NMCResultCode
do_device_wifi_hotspot (NmCli *nmc, int argc, char **argv)
{
AddAndActivateInfo *info;
const char *ifname = NULL;
const char *con_name = NULL;
char *default_name = NULL;
const char *ssid = NULL;
const char *wifi_mode;
const char *band = NULL;
const char *channel = NULL;
unsigned long channel_int;
const char *password = NULL;
gboolean show_password = FALSE;
NMDevice *device = NULL;
int devices_idx;
const GPtrArray *devices;
NMDeviceWifiCapabilities caps;
NMConnection *connection = NULL;
NMSettingConnection *s_con;
NMSettingWireless *s_wifi;
NMSettingWirelessSecurity *s_wsec;
NMSettingIPConfig *s_ip4, *s_ip6;
GBytes *ssid_bytes;
GError *error = NULL;
/* Set default timeout waiting for operation completion. */
if (nmc->timeout == -1)
nmc->timeout = 60;
while (argc > 0) {
if (strcmp (*argv, "ifname") == 0) {
if (next_arg (&argc, &argv) != 0) {
g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
ifname = *argv;
} else if (strcmp (*argv, "con-name") == 0) {
if (next_arg (&argc, &argv) != 0) {
g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
con_name = *argv;
} else if (strcmp (*argv, "ssid") == 0) {
if (next_arg (&argc, &argv) != 0) {
g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
ssid = *argv;
if (strlen (ssid) > 32) {
g_string_printf (nmc->return_text, _("Error: ssid is too long."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
} else if (strcmp (*argv, "band") == 0) {
if (next_arg (&argc, &argv) != 0) {
g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
band = *argv;
if (strcmp (band, "a") && strcmp (band, "bg")) {
g_string_printf (nmc->return_text, _("Error: band argument value '%s' is invalid; use 'a' or 'bg'."),
band);
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
} else if (strcmp (*argv, "channel") == 0) {
if (next_arg (&argc, &argv) != 0) {
g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
channel = *argv;
} else if (strcmp (*argv, "password") == 0) {
if (next_arg (&argc, &argv) != 0) {
g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
password = *argv;
} else if (nmc_arg_is_option (*argv, "show-password")) {
show_password = TRUE;
} else {
g_string_printf (nmc->return_text, _("Error: Unknown parameter %s."), *argv);
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
argc--;
argv++;
}
/* Verify band and channel parameters */
if (!channel) {
if (g_strcmp0 (band, "bg") == 0)
channel = "1";
if (g_strcmp0 (band, "a") == 0)
channel = "7";
}
if (channel) {
if (!band) {
g_string_printf (nmc->return_text, _("Error: channel requires band too."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
if ( !nmc_string_to_uint (channel, TRUE, 1, 5825, &channel_int)
|| !nm_utils_wifi_is_channel_valid (channel_int, band)) {
g_string_printf (nmc->return_text, _("Error: channel '%s' not valid for band '%s'."),
channel, band);
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto error;
}
}
/* Find Wi-Fi device. When no ifname is provided, the first Wi-Fi is used. */
devices = nm_client_get_devices (nmc->client);
devices_idx = 0;
device = find_wifi_device_by_iface (devices, ifname, &devices_idx);
if (!device) {
if (ifname)
g_string_printf (nmc->return_text, _("Error: Device '%s' is not a Wi-Fi device."), ifname);
else
g_string_printf (nmc->return_text, _("Error: No Wi-Fi device found."));
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
goto error;
}
/* Check device supported mode */
caps = nm_device_wifi_get_capabilities (NM_DEVICE_WIFI (device));
if (caps & NM_WIFI_DEVICE_CAP_AP)
wifi_mode = NM_SETTING_WIRELESS_MODE_AP;
else if (caps & NM_WIFI_DEVICE_CAP_ADHOC)
wifi_mode = NM_SETTING_WIRELESS_MODE_ADHOC;
else {
g_string_printf (nmc->return_text, _("Error: Device '%s' supports neither AP nor Ad-Hoc mode."),
nm_device_get_iface (device));
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
goto error;
}
/* Create a connection with appropriate parameters */
connection = nm_simple_connection_new ();
s_con = (NMSettingConnection *) nm_setting_connection_new ();
nm_connection_add_setting (connection, NM_SETTING (s_con));
if (!con_name)
con_name = default_name = nmc_unique_connection_name (nm_client_get_connections (nmc->client), "Hotspot");
g_object_set (s_con,
NM_SETTING_CONNECTION_ID, con_name,
NM_SETTING_CONNECTION_AUTOCONNECT, FALSE,
NULL);
g_free (default_name);
s_wifi = (NMSettingWireless *) nm_setting_wireless_new ();
nm_connection_add_setting (connection, NM_SETTING (s_wifi));
ssid_bytes = generate_ssid_for_hotspot (ssid);
g_object_set (s_wifi, NM_SETTING_WIRELESS_MODE, wifi_mode,
NM_SETTING_WIRELESS_SSID, ssid_bytes,
NULL);
g_bytes_unref (ssid_bytes);
if (channel)
g_object_set (s_wifi,
NM_SETTING_WIRELESS_CHANNEL, (guint32) channel_int,
NM_SETTING_WIRELESS_BAND, band,
NULL);
s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new ();
nm_connection_add_setting (connection, NM_SETTING (s_wsec));
if (!set_wireless_security_for_hotspot (s_wsec, wifi_mode, caps, password, show_password, &error)) {
g_object_unref (connection);
g_string_printf (nmc->return_text, _("Error: Invalid 'password': %s."), error->message);
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
g_clear_error (&error);
goto error;
}
s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new ();
nm_connection_add_setting (connection, NM_SETTING (s_ip4));
g_object_set (s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_SHARED, NULL);
s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new ();
nm_connection_add_setting (connection, NM_SETTING (s_ip6));
g_object_set (s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
/* Activate the connection now */
nmc->nowait_flag = (nmc->timeout == 0);
nmc->should_wait = TRUE;
info = g_malloc0 (sizeof (AddAndActivateInfo));
info->nmc = nmc;
info->device = device;
info->hotspot = TRUE;
nm_client_add_and_activate_connection_async (nmc->client,
connection,
device,
NULL,
NULL,
add_and_activate_cb,
info);
error:
return nmc->return_value;
}
static void
request_rescan_cb (GObject *object, GAsyncResult *result, gpointer user_data)
{
@ -2791,6 +3158,8 @@ do_device_wifi (NmCli *nmc, int argc, char **argv)
nmc->return_value = do_device_wifi_list (nmc, argc-1, argv+1);
} else if (matches (*argv, "connect") == 0) {
nmc->return_value = do_device_wifi_connect_network (nmc, argc-1, argv+1);
} else if (matches (*argv, "hotspot") == 0) {
nmc->return_value = do_device_wifi_hotspot (nmc, argc-1, argv+1);
} else if (matches (*argv, "rescan") == 0) {
nmc->return_value = do_device_wifi_rescan (nmc, argc-1, argv+1);
} else {

View file

@ -512,6 +512,7 @@ _nmcli_compl_ARGS()
save| \
hidden| \
private)
show-password)
if [[ "${#words[@]}" -eq 2 ]]; then
_nmcli_list "yes no"
return 0
@ -589,6 +590,12 @@ _nmcli_compl_ARGS()
return 0
fi
;;
band)
if [[ "${#words[@]}" -eq 2 ]]; then
_nmcli_list "a bg"
return 0
fi
;;
*)
return 1
;;
@ -1317,7 +1324,7 @@ _nmcli()
;;
w|wi|wif|wifi)
if [[ ${#words[@]} -eq 3 ]]; then
_nmcli_compl_COMMAND "${words[2]}" list connect rescan
_nmcli_compl_COMMAND "${words[2]}" list connect hotspot rescan
else
case "${words[2]}" in
l|li|lis|list)
@ -1338,6 +1345,11 @@ _nmcli()
_nmcli_compl_ARGS
fi
;;
h|ho|hot|hots|hotsp|hotspo|hotspot)
_nmcli_array_delete_at words 0 2
OPTIONS=(ifname con-name ssid band channel password show-password)
_nmcli_compl_ARGS
;;
r|re|res|resc|resca|rescan)
_nmcli_array_delete_at words 0 2
OPTIONS_REPEATABLE=(ssid)

View file

@ -853,6 +853,36 @@ Otherwise the connection is system\(hywide, which is the default.
Otherwise the SSID would not be found and the connection attempt would fail.
.RE
.TP
.B wifi hotspot [ifname <ifname>] [con-name <name>] [ssid <SSID>] [band a|bg] [channel <channel>]
.B [password <password>] [--show-password]
.br
Create a Wi-Fi hotspot. The command creates a hotspot connection profile according to
Wi-Fi device capabilities and activates it on the device. The hotspot is secured with WPA
if device/driver supports that, otherwise WEP is used.
Use \fIconnection down\fP or \fIdevice disconnect\fP to stop the hotspot.
.br
.RS
.PP
Parameters of the hotspot can be influenced by the optional parameters:
.IP \fIifname\fP 17
\(en what Wi-Fi device is used
.IP \fIcon-name\fP 17
\(en name of the created hotspot connection profile
.IP \fIssid\fP 17
\(en SSID of the hotspot
.IP \fIband\fP 17
\(en Wi-Fi band to use
.IP \fIchannel\fP 17
\(en Wi-Fi channel to use
.IP \fIpassword\fP 17
\(en password to use for the created hotspot. If not provided,
nmcli will generate a password. The password is either WPA
pre-shared key or WEP key.
.IP \fI--show-password\fP 17
\(en tell nmcli to print the password to stdout. It is useful
when the user did not provide his own password.
.RE
.TP
.B wifi rescan [ifname <ifname>] [[ssid <SSID>] ...]
.br
Request that \fINetworkManager\fP immediately re-scan for available access points.
@ -1040,6 +1070,11 @@ using password "caffeine". This is mainly useful when connecting to "Cafe Hotspo
the first time. Next time, it is better to use 'nmcli con up id "My cafe"' so that the
existing connection profile can be used and no additional is created.
.IP "\fB\f(CWnmcli dev wifi hotspot -s con-name QuickHotspot\fP\fP"
.IP
creates a hotspot profile and connects it. Prints the hotspot password the user should use
to connect to the hotspot from other devices.
.IP "\fB\f(CWnmcli connection add type ethernet autoconnect no ifname eth0\fP\fP"
.IP
non-interactively adds an Ethernet connection tied to eth0 interface with automatic IP configuration (DHCP),