wireless.c revision d71dbb732372504daff1f1783bc0d8864ce9bd50
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This file containes all the routines to handle wireless (more
* accurately, 802.11 "WiFi" family only at this moment) operations.
* This is only phase 0 work so the handling is pretty simple.
*
* When the daemon starts up, for each WiFi interface detected, it'll
* spawn a thread doing an access point (AP) scanning. After the scans
* finish and if one of the WiFi interfaces is chosen to be active, the
* code will pop up a window showing the scan results and wait for the
* user's input on which AP to connect to and then complete the AP
* connection and IP interface set up. WEP is supported to connect to
* those APs which require it. The code also maintains a list of known
* WiFi APs in the file KNOWN_WIFI_NETS. Whenever the code successfully
* This file is used in the following way.
*
* If the AP scan results contain one known AP, the code will automatically
* connect to that AP without asking the user. But if the detected signal
* strength of that AP is weaker than some other AP's, the code will still
* pop up a window asking for user input.
*
* If the AP scan results contain more than one known APs, the code will
* pop up a window listing those known APs only. If the user does not
* make a choice, the full list of available APs will be shown.
*
* If the AP scan results contain no known AP, the full list of available
* APs is shown and the user is asked which AP to connect to.
*
* Note that not all APs broadcast the Beacon. And some events may
* happen during the AP scan such that not all available APs are found.
* So the code also allows a user to manually input an AP's data in the
* pop up window. This allows a user to connect to the aforementioned
* "hidden" APs.
*
* The code also periodically (specified by wlan_scan_interval) checks
* for the health of the AP connection. If the signal strength of the
* connected AP drops below a threshold (specified by wireless_scan_level),
* the code will try to do another scan to find out other APs available.
* If there is currently no connected AP, a scan will also be done
* periodically to look for available APs. In both cases, if there are
* new APs, the above AP connection procedure will be performed.
*
* One limitation of the current code is that a user cannot initiate a
* WiFi APs scan manually. A manual scan can only be done when the code
* shows a pop up window asking for user input. Suppose there is no
* connected AP and periodic scan is going on. If a user wants to
* connect to a hidden AP, this is only possible if there is another
* non-hidden AP available such that after a periodic scan, the pop up
* window is shown. This will be fixed in a later phase.
*/
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <syslog.h>
#include <assert.h>
#include <limits.h>
#include <errno.h>
#include <syslog.h>
#include <pthread.h>
#include <stropts.h>
#include <fcntl.h>
#include <locale.h>
#include <libintl.h>
#include <libdladm.h>
#include <libdllink.h>
#include <libinetutil.h>
#include "defines.h"
#include "structures.h"
#include "functions.h"
#include "variables.h"
static pthread_mutex_t wifi_mutex;
static pthread_mutexattr_t wifi_mutex_attr;
typedef enum {
SUCCESS = 0,
/*
* Is a wireless interface doing a scan currently? We only allow one
* wireless interface to do a scan at any one time. This is to
* avoid unnecessary interference. The following variable is used
* to store the interface doing the scan. It is protected by
* wifi_init_mutex.
*/
/* used entries have non NULL memebers */
static int store_wepkey(char *, char *, char *);
static dladm_wlan_wepkey_t *retrieve_wepkey(const char *, const char *);
static boolean_t check_wlan(const char *, const char *);
const char *);
static void free_wireless_lan(struct wireless_lan *);
static struct wireless_lan *get_specific_lan(void);
static void get_user_wepkey(struct wireless_lan *);
static char *get_zenity_response(const char *);
static int zenity_height(int);
#define WIRELESS_LAN_INIT_COUNT 8
/*
* The variable wlan_scan_interval controls the interval in seconds
* between periodic scans.
*/
/*
* The variable wireless_scan_level specifies the lowest signal level
* when a periodic wireless scan needs to be done.
*/
void
init_mutexes(void)
{
(void) pthread_mutexattr_init(&wifi_mutex_attr);
(void) pthread_mutexattr_settype(&wifi_mutex_attr,
}
/*
* wlan is expected to be non-NULL.
*/
static void
{
char zenity_cmd[1024];
char buf[1024];
/*
* First, test if we have wepkey stored as secobj. If so,
* no need to prompt for it.
*/
dprintf("get_user_wepkey: retrieve_wepkey() returns non NULL");
return;
}
"%s --entry --text=\"%s %s\""
" --title=\"%s\" --hide-text", ZENITY,
gettext("Enter WEP key"));
if (!valid_graphical_user(B_TRUE))
return;
/* Store WEP key persistently */
wlan->raw_wepkey) != 0) {
"get_user_wepkey: failed to store"
" user specified WEP key");
}
} else {
"get_user_wepkey: strdup failed");
}
}
} else {
}
}
static boolean_t
{
int i;
(void) pthread_mutex_lock(&wifi_mutex);
/* Check if the new entry is already there. */
for (i = 0; i < wireless_lan_used; i++) {
/*
* Assume that essid and bssid are already NULL terminated.
* Note that we also check for the interface name here.
* If there is only one wireless interface, it should not
* matter. But if there are more than 1, then it is safer
* to use the interface which finds the AP to connect to
* it.
*/
(void) pthread_mutex_unlock(&wifi_mutex);
return (B_TRUE);
}
}
(void) pthread_mutex_unlock(&wifi_mutex);
return (B_FALSE);
}
static void
{
}
static boolean_t
{
int n;
(void) pthread_mutex_lock(&wifi_mutex);
if (wireless_lan_used == wireless_lan_count) {
int newcnt;
struct wireless_lan *r;
newcnt = (wireless_lan_count == 0) ?
if (r == NULL) {
(void) pthread_mutex_unlock(&wifi_mutex);
return (B_FALSE);
}
(void) memset((void *)(r + wireless_lan_count), 0,
(newcnt - wireless_lan_count) * sizeof (*r));
wlans = r;
}
n = wireless_lan_used;
free_wireless_lan(&(wlans[n]));
(void) pthread_mutex_unlock(&wifi_mutex);
return (B_FALSE);
}
(void) pthread_mutex_unlock(&wifi_mutex);
return (B_TRUE);
}
static void
clear_lan_entries(void)
{
int i;
(void) pthread_mutex_lock(&wifi_mutex);
for (i = 0; i < wireless_lan_used; i++)
free_wireless_lan(&(wlans[i]));
wireless_lan_used = 0;
(void) pthread_mutex_unlock(&wifi_mutex);
}
/*
* Verify if a WiFi NIC is associated with the given ESSID. If the given
* ESSID is NULL, and if the NIC is already connected, return true.
* Otherwise,
*
* 1. If the NIC is associated with the given ESSID, return true.
* 2. If the NIC is not associated with any AP, return false.
* 3. If the NIC is associated with a different AP, tell the driver
* to disassociate with it and then return false.
*/
static boolean_t
{
char cur_essid[DLADM_STRSIZE];
char errmsg[DLADM_STRSIZE];
if (status != DLADM_STATUS_OK) {
dprintf("check_wlan: dladm_wlan_get_linkattr() failed: %s",
return (B_FALSE);
}
return (B_FALSE);
return (B_TRUE);
/* Is the NIC associated with the expected one? */
return (B_TRUE);
/* Tell the driver to disassociate with the current AP. */
dprintf("check_wlan: dladm_wlan_disconnect() fails");
return (B_FALSE);
}
/*
* Given a wireless interface, use it to scan for available networks.
*/
{
int num_ap;
/*
* If there is already a scan in progress, wait until the
* scan is done to avoid interference. But if the interface
* doing the scan is the same as the one requesting the new
* scan, just return.
*
* Whenever a wireless scan is in progress, all the other
* threads checking the wireless AP list should wait.
*/
(void) pthread_mutex_lock(&wifi_init_mutex);
while (wifi_scan_intf != NULL) {
dprintf("scan_wireless_nets in progress: old %s new %s",
(void) pthread_mutex_unlock(&wifi_init_mutex);
return (B_FALSE);
}
}
(void) pthread_mutex_unlock(&wifi_init_mutex);
/*
* Since only one scan is allowed at any one time, no need to grab
* a lock in checking wireless_lan_used.
*/
if (status != DLADM_STATUS_OK)
else
(void) pthread_mutex_lock(&wifi_init_mutex);
(void) pthread_cond_signal(&wifi_init_cond);
(void) pthread_mutex_unlock(&wifi_init_mutex);
return (new_ap);
}
/* ARGSUSED */
static void
{
if (scan_wireless_nets(ifp)) {
}
}
}
static boolean_t
{
char essid_name[DLADM_STRSIZE];
char bssid_name[DLADM_STRSIZE];
char strength[DLADM_STRSIZE];
return (B_TRUE);
}
return (B_FALSE);
}
/* ARGSUSED */
void *
periodic_wireless_scan(void *arg)
{
/*
* No periodic scan if the "-i" option is used to change the
* interval to 0.
*/
if (wlan_scan_interval == 0)
return (NULL);
for (;;) {
int ret;
/*
* We assume that once an llp is created, it will never be
* deleted in the lifetime of the process. So it is OK
* to do this assignment without a lock.
*/
/*
* We do a scan if
*
* 1. There is no active profile. Or
* 2. We are now disconnected from the AP. Or
* 3. The signal strength falls below a certain specified level.
*/
if (ret == 0) {
&attr) != DLADM_STATUS_OK) {
continue;
}
continue;
}
/*
* Clear the IF_DHCPFAILED and IF_DHCPSTARTED
* flags on this interface; this is a "fresh
* start" for the interface, so we should
* retry dhcp.
*/
}
/*
* Deactivate the original llp.
* If we reached this point, we either were
* not connected, or were connected with
* "very weak" signal strength; so we're
* assuming that having this llp active was
* not very useful. So we deactivate.
*/
}
/* We should start from fresh. */
} else if (ret == -1) {
continue;
return (NULL);
}
}
/* NOTREACHED */
return (NULL);
}
/*
* and dladm_get_secobj().
*/
/*
* Convert wepkey hexascii string to raw secobj value. This
* code is very similar to convert_secobj() in dladm.c, it would
* be good to have a libdladm function to convert values.
*/
static int
{
if (buf_len == 0) {
"wepkey_string_to_secobj_value: empty WEP key");
return (-1);
}
switch (buf_len) {
case 5: /* ASCII key sizes */
case 13:
break;
case 10:
case 26: /* Hex key sizes, not preceded by 0x */
!= 0) {
"wepkey_string_to_secobj_value: invalid WEP key");
return (-1);
}
break;
case 12:
case 28: /* Hex key sizes, preceded by 0x */
obj_lenp) != 0) {
"wepkey_string_to_secobj_value: invalid WEP key");
return (-1);
}
break;
default:
"wepkey_string_to_secobj_value: invalid WEP key length");
return (-1);
}
return (0);
}
/*
* Print the key format into the appropriate field, then convert any ":"
* characters to ".", as ":[1-4]" is the slot indicator, which otherwise
* would trip us up. The third parameter is expected to be of size
* DLADM_SECOBJ_NAME_MAX.
*/
static void
{
int i;
else
if (name[i] == ':')
name[i] = '.';
}
static int
{
char obj_name[DLADM_SECOBJ_NAME_MAX];
char errmsg[DLADM_STRSIZE];
/*
* Name wepkey object for this WLAN so it can be later retrieved
*/
/* above function logs internally on failure */
return (-1);
}
if (status != DLADM_STATUS_OK) {
"'%s' for wepkey: %s", obj_name,
return (-1);
}
return (0);
}
/*
* retrieve_wepkey returns NULL if no wepkey was recovered from dladm
*/
static dladm_wlan_wepkey_t *
{
char errmsg[DLADM_STRSIZE];
/*
* Newly-allocated wepkey must be freed by caller, or by
* subsequent call to retrieve_wepkey().
*/
return (NULL);
}
/* Set name appropriately to retrieve wepkey for this WLAN */
dprintf("retrieve_wepkey: len = %d, object = %s\n",
/* Try the kernel first, then fall back to persistent storage. */
if (status != DLADM_STATUS_OK) {
dprintf("retrieve_wepkey: dladm_get_secobj(TEMP) failed: %s",
}
switch (status) {
case DLADM_STATUS_OK:
dprintf("retrieve_wepkey: dladm_get_secobj succeeded: len %d",
break;
case DLADM_STATUS_NOTFOUND:
/*
* We do not want an error in the case that the secobj
* is not found, since we then prompt for it.
*/
return (NULL);
default:
return (NULL);
}
return (cooked_wepkey);
}
/* Create the KNOWN_WIFI_NETS using info from the interface list. */
void
{
/* Create the NWAM directory in case it does not exist. */
return;
}
}
return;
}
}
/*
* Add an entry to known_wifi_nets file given the parameters.
*/
void
{
return;
}
/*
* If there is none, we should create one instead.
* For now, we will use the order of seeing each new net
* for the priority. We should have a priority field
* in the known_wifi_nets file eventually...
*/
return;
}
}
/* now see if this info is already in the file */
/* now add this to the file */
}
}
/*
* Check if the given AP (ESSID, BSSID pair) is on the known AP list.
*/
{
int line_num;
/*
* For now the file format is:
* essid\tbssid
* (essid followed by tab followed by bssid)
*/
return (B_FALSE);
}
return (B_FALSE);
}
cp++;
continue;
continue;
}
continue;
/*
* no BSSID specified => ESSID match
* is good enough.
*/
/* Match on both is always good. */
}
if (found)
break;
}
return (found);
}
/*
* reqlan->essid is required (i.e., cannot be NULL)
* reqlan->bssid is optional (i.e., may be NULL)
*/
{
char errmsg[DLADM_STRSIZE];
/* try to apply essid selected by the user */
return (B_FALSE);
/* If it is already connected to the required AP, just return. */
return (B_TRUE);
"connect_chosen_lan: invalid ESSID '%s' for '%s'",
return (B_FALSE);
}
"connect_chosen_lan: invalid BSSID '%s' for '%s'",
return (B_FALSE);
}
}
/* First check for the wepkey */
if (reqlan->need_wepkey) {
return (B_FALSE);
keycount = 1;
dprintf("connect_chosen_lan: retrieved key");
} else {
keycount = 0;
}
/*
* Connect; only scan if a bssid was not specified.
* If it times out and we were trying with a bssid,
* try a second time with just the ESSID.
*/
flags);
dprintf("connect_chosen_lan: dladm_wlan_connect returned %s",
"trying again with just (%s)",
flags = 0;
}
if (status != DLADM_STATUS_OK) {
"connect_chosen_lan: connect to '%s' failed on '%s': %s",
return (B_FALSE);
}
return (B_TRUE);
}
/*
* First attempt to connect to the network specified by essid.
* If that fails, attempt to connect using autoconf.
*/
static boolean_t
{
"Could not connect to chosen WLAN %s, going to auto-conf",
return (wlan_autoconf(ifname));
}
return (B_TRUE);
}
/*
* The +1 is for the extra "compare" row, the 24 is for the font spacing
* and the 125 if extra for the buttons et al.
*/
static int
zenity_height(int rows)
{
}
static return_vals_t
const char *ifname)
{
int i, rtn, j = 0;
struct wireless_lan *reqlan;
char buf[2048];
char zenity_cmd[2048];
if (num == 0) {
"you know of any which do not broadcast."));
}
dprintf("connect_to_new_wlan: cannot find wireless interface: "
"%s", ifname);
return (FAILURE);
}
/* build list for display */
buf[0] = '\0';
for (i = 0; i < num; i++) {
"essid %s, bssid %s; ignoring", i,
continue;
}
/*
* Only use the interface which finds the AP to connect to it.
*/
dprintf("connect_to_new_wlan: wrong interface (%s) for "
lanlist[i].wl_if_name);
continue;
}
j++;
/*
* Zenity uses a space as its delimiter, so put the ESSID in
* quotes so it won't get confused if there is a space in the
* ESSID name.
*/
"%d '%s' %s %s '%s' ", j,
lanlist[i].signal_strength);
}
}
/*
* All columns except the first are empty for the "Other"
* and "Rescan" rows.
*/
"\"%s\" \"\" \"\" \"\" \"\" \"%s\" \"\" \"\" \"\" \"\"",
}
"%s --list --title=\"%s\""
" --height=%d --width=500 --column=\"#\" --column=\"%s\""
" --column=\"%s\" --column=\"%s\" --column=\"%s\""
/* present list to user and get selection */
lanlist);
switch (rtn) {
case 1:
/* user chose "other"; pop-up for specific essid */
reqlan = get_specific_lan();
break;
case 2:
/* user chose "Rescan" */
(void) scan_wireless_nets(intf);
return (TRY_AGAIN);
case -1:
break;
default:
/* common case: reqlan was set in get_user_preference() */
break;
}
dprintf("did not get user preference; attempting autoconf");
}
dprintf("get_user_preference() returned essid %s, bssid %s, encr %s",
/* set wepkey before first time connection */
/*
* now attempt to connect to selection, backing
* off to autoconf if the connect fails
*/
/* succeeded, so add entry to known_essid_list_file */
} else {
/* failed to connect; try auto-conf */
}
if (!autoconf)
return (SUCCESS);
}
struct wireless_lan *
prompt_for_visited(void)
{
char buf[1024];
char zenity_cmd[1024];
int i = 0;
/* Build zenity command string */
buf[0] = '\0';
if (visited_wlan_list->total > 0) {
struct visited_wlans *vlp;
struct wireless_lan *wlp;
sizeof (struct wireless_lan));
return (NULL);
}
continue;
}
i++;
"%d '%s' %s %s '%s' ",
}
}
}
"\"%s\"", select_str);
}
"%s --list --title=\"%s\""
" --height=%d --width=670 --column=\"#\" --column=\"%s\""
" --column=\"%s\" --column=\"%s\" --column=\"%s\""
/*
* If the user doesn't make a choice or something goes wrong
* (get_user_preference() returned -1), or if the user chooses
* the "select from all available" string (get_user_preference()
* returned 1), we simply return NULL: there was no selection
* made from the visited list. If the user *did* make a choice
* (get_user_preference() returned 0), return the alloc'd struct.
*/
return (req_conf);
}
static boolean_t
wlan_autoconf(const char *ifname)
{
if (!autoconf)
return (B_FALSE);
}
/* If the NIC is already associated with something, just return. */
return (B_TRUE);
/*
* Do autoconf, relying on the heuristics used by dladm_wlan_connect()
* to cycle through WLANs detected in priority order, attempting
* to connect.
*/
DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT, NULL, 0, 0);
if (status != DLADM_STATUS_OK) {
char errmsg[DLADM_STRSIZE];
"wlan_autoconf: dladm_wlan_connect failed for '%s': %s",
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Returns:
* B_TRUE if this info is already in visited_wlan_list
* B_FALSE if not
*/
static boolean_t
{
struct visited_wlans *vwlp;
dprintf("%s already in visited_wlan_list",
return (B_TRUE);
} else {
}
}
return (B_FALSE);
}
static char *
get_zenity_response(const char *cmd)
{
char buf[1024];
char *rtnp;
int ret;
if (!valid_graphical_user(B_TRUE))
return (NULL);
return (NULL);
}
} else {
buf[0] = '\0';
dprintf("get_zenity_resp: zenity returned nothing");
}
/*
* We should probably make sure that those ZENITY_* exit
* environment variables are not set first...
*/
return (NULL);
}
dprintf("get_zenity_resp: user cancelled");
return (NULL);
}
return (rtnp);
}
/*
* user via zenity (passed in in a pre-formatted zenity string in the
* param cmd). If there's a final list item ("Other", "Select from
* full list", etc.) that may be selected and should be differentiated,
* that item should be passed in in compare param.
*
* Four possible return values:
* -1: No response from user, or other error. *req_lan is undefined.
* a malloc'd buffer, which the caller is responsible for freeing.
* 1: a compare string ("Other") was given, and the user response matched
* that string. *req_lan is undefined.
* 2: a compare string ("Rescan") was given, and the user response matched
* that string. *req_lan is undefined.
*/
int
const struct wireless_lan *list)
{
char *response;
struct wireless_lan *wlp;
const struct wireless_lan *sel;
int answer;
return (-1);
}
return (-1);
}
return (1);
}
return (2);
}
if (answer <= 0) {
return (-1);
}
goto dup_error;
goto dup_error;
goto dup_error;
goto dup_error;
}
goto dup_error;
goto dup_error;
return (0);
return (-1);
}
/*
* Returns a pointer to an alloc'd struct wireless lan if a response
* is received from the user; only the essid will be valid in this
* case. If no response received, or other failure, returns NULL.
*/
static struct wireless_lan *
get_specific_lan(void)
{
char specify_str[1024];
char *response;
struct wireless_lan *wlp;
/*
* TRANSLATION_NOTE: the token "ESSID" should not be translated
* in the phrase below.
*/
" --entry --title=\"%s\" --text=\"%s\"",
return (NULL);
return (NULL);
}
" --list --title=\"%s\" --text=\"%s\" --column=\"%s\" none wep",
gettext("Type"));
return (wlp);
}
/*
* Returns:
* B_TRUE if things go well
* B_FALSE if we were unable to connect to anything
*/
handle_wireless_lan(const char *ifname)
{
const struct wireless_lan *cur_wlans;
int i, num_wlans;
if (visited_wlan_list == NULL) {
sizeof (struct visited_wlans_list))) == NULL) {
return (B_FALSE);
}
}
/*
* We wait while a scan is in progress. Since we allow a user
* to initiate a re-scan, we can proceed even when no scan
* has been done to fill in the AP list.
*/
(void) pthread_mutex_lock(&wifi_init_mutex);
while (wifi_scan_intf != NULL)
(void) pthread_mutex_unlock(&wifi_init_mutex);
(void) pthread_mutex_lock(&wifi_mutex);
/*
* Try to see if any of the wifi nets currently available
* has been used previously. If more than one available
* nets has been used before, then prompt user with
* all the applicable previously wifi nets, and ask which
* one to connect to.
*/
for (i = 0; i < num_wlans; i++) {
struct visited_wlans *new_wlan;
/* Find the AP with the highest signal. */
&strength) != DLADM_STATUS_OK) {
continue;
}
continue;
if (already_in_visited_wlan_list(&cur_wlans[i])) {
/* don't have to add it again */
continue;
}
/* add this to the visited_wlan_list */
dprintf("adding essid %s, bssid %s to visited list",
goto all_done;
}
goto all_done;
}
goto all_done;
}
}
/*
* only one previously visited wifi net, connect to it
* (falling back to autoconf if the connect fails) if there
* is no AP with a better signal strength.
*/
&strength) == DLADM_STATUS_OK) {
goto connect_any;
}
/*
* more than one previously visited wifi nets seen.
* prompt user for which one should we connect to
*/
} else {
/*
* The user didn't make a choice; offer the full list.
*/
}
} else {
/* last case, no previously visited wlan found */
ifname);
}
/*
* We locked down the list above; free it now that we're done.
*/
(void) pthread_mutex_unlock(&wifi_mutex);
if (visited_wlan_list != NULL) {
}
}
}
}
if (connect_result == TRY_AGAIN) {
dprintf("end of handle_wireless_lan() TRY_AGAIN");
goto start_over;
}
return (result);
}