diff options
| author | James Carlson <james.d.carlson@sun.com> | 2008-09-19 16:27:53 -0400 |
|---|---|---|
| committer | James Carlson <james.d.carlson@sun.com> | 2008-09-19 16:27:53 -0400 |
| commit | b00044a2eb43864b8718585d21949611a2ee59ef (patch) | |
| tree | a53823fd16a577e94a5ce5b708d46d557ff943df | |
| parent | 0ea48bbf7e086befa73c3f191a062c3a82cc5397 (diff) | |
| download | illumos-joyent-b00044a2eb43864b8718585d21949611a2ee59ef.tar.gz | |
PSARC 2008/482 NWAM Phase 0.5 (picea)
6648862 nwam is annoying when no network is available
6642066 NWAM Phase 0 needs better observability
6604418 weak interfaces turn nwam into a bouncing dog that misses its master
6745880 starting or restarting nwam always costs a link reinit
48 files changed, 5722 insertions, 2288 deletions
diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint index ec458d0c76..bd8cf275e2 100644 --- a/usr/src/Makefile.lint +++ b/usr/src/Makefile.lint @@ -361,6 +361,7 @@ COMMON_SUBDIRS = \ lib/libmtmalloc \ lib/libnsl \ lib/libnvpair \ + lib/libnwam \ lib/libpam \ lib/libpctx \ lib/libpicl \ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/Makefile b/usr/src/cmd/cmd-inet/lib/nwamd/Makefile index 818e63f807..de5fcbe951 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/Makefile +++ b/usr/src/cmd/cmd-inet/lib/nwamd/Makefile @@ -20,9 +20,7 @@ # # -# ident "%Z%%M% %I% %E% SMI" -# -# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # usr/src/cmd/cmd-inet/lib/nwamd/Makefile @@ -32,7 +30,7 @@ include ../../../../lib/Makefile.lib PROG= nwamd -OBJS= events.o interface.o llp.o main.o \ +OBJS= door.o events.o interface.o llp.o main.o \ state_machine.o util.o wireless.o SRCS= $(OBJS:%.o=%.c) HEADERS= defines.h functions.h structures.h variables.h @@ -44,7 +42,8 @@ POFILES= interface.po wireless.po ROOTCMDDIR= $(ROOTFS_LIBDIR)/inet -LDLIBS += -lsocket -lnsl -linetcfg -linetutil -lumem -lscf -ldladm -lgen +LDLIBS += -lsocket -lnsl -linetcfg -linetutil -lumem -lscf -ldladm \ + -lgen -ldoor -lsecdb -lbsm -lsysevent -lnvpair .KEEP_STATE: @@ -61,7 +60,7 @@ install: $(ROOTCMD) check: $(SRCS) $(HEADERS) $(CSTYLE) -cpP $(SRCS) $(HEADERS) -$(ROOTCMD): all +$(ROOTCMD): $(PROG) clean: $(RM) $(OBJS) diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/defines.h b/usr/src/cmd/cmd-inet/lib/nwamd/defines.h index 5610a77fbf..c288af0184 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/defines.h +++ b/usr/src/cmd/cmd-inet/lib/nwamd/defines.h @@ -20,37 +20,35 @@ */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DEFINES_H #define _DEFINES_H -#pragma ident "%Z%%M% %I% %E% SMI" - #include <sys/time.h> #define PKILL "/usr/bin/pkill" -#define ZENITY "/usr/bin/zenity" -#define SVCADM "/usr/sbin/svcadm" #define IFCONFIG "/sbin/ifconfig" #define NET_SVC_METHOD "/lib/svc/method/net-svc" -#define NET_SVC_FMRI "svc:/network/service:default" #define DEV_LOCAL_SVC_FMRI "svc:/system/device/local:default" #define PFEXEC "/usr/bin/pfexec" #define ULP_DIR "/etc/nwam/ulp" -#define LLPDIR "/etc/nwam" -#define LLPFILE LLPDIR"/llp" -#define KNOWN_WIFI_NETS LLPDIR"/known_wifi_nets" +#define LLPDIRNAME "/etc/nwam" +#define LLPDIRMODE S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH +#define LLPFILE LLPDIRNAME "/llp" +#define LLPFILETMP LLPDIRNAME "/llp.tmp" +#define KNOWN_WIFI_NETS LLPDIRNAME "/known_wifi_nets" +#define KNOWN_WIFI_TMP LLPDIRNAME "/known_wifi_nets.tmp" + +#define DOOR_FILENAME "/etc/svc/volatile/nwam_door" +#define DOOR_FILEMODE S_IRUSR | S_IRGRP | S_IROTH #define BOOLEAN_TO_STRING(x) ((x) ? "TRUE" : "FALSE") #define STRING(s) (((s) == NULL) ? "NULL" : (s)) -/* IPC listening port */ -#define NP_LISTEN_PORT 12340 - #define NWAM_DEFAULT_DHCP_WAIT_TIME 60 /* 1 minute */ #define NWAM_IF_WAIT_DELTA_MAX 300 /* 5 minutes poll rate max */ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/door.c b/usr/src/cmd/cmd-inet/lib/nwamd/door.c new file mode 100644 index 0000000000..c2481a93f9 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/door.c @@ -0,0 +1,918 @@ +/* + * 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 + * or http://www.opensolaris.org/os/licensing. + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * This file contains the routines that service the libdoor(3LIB) interface. + * This interface is intended for use by an external GUI utility to provide + * status information to users and allow control over nwam behavior in certain + * situations. + * + * The daemon has one active thread for each door call. Typically, a client + * will make a blocking call to await new events, and if an active client is + * busy, we will enqueue a small number of events here. If too many are + * enqueued, then we begin dropping events, and a single special "lost" event + * is placed in the queue. Clients are expected to start over at that point. + * + * For client events that require a response from the client, the server must + * assume a response if "lost" occurs or if there are no clients. + * + * When no clients are present, we just drop events. No history is maintained. + * + * Thread cancellation notes: In general, we disable cancellation for all + * calls, as allowing cancellation would require special handlers throughout + * the nwamd code to deal with the release of locks taken in various contexts. + * Instead, we allow it to run to completion on the assumption that all calls + * are expected to run without significant blocking. + * + * The one exception to this is the event-wait function, which intentionally + * blocks indefinitely. This request must enable cancellation so that an idle + * client can be terminated cleanly. + */ + +#include <stdlib.h> +#include <assert.h> +#include <unistd.h> +#include <pthread.h> +#include <door.h> +#include <alloca.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <syslog.h> +#include <string.h> +#include <pwd.h> +#include <auth_attr.h> +#include <auth_list.h> +#include <secdb.h> +#include <bsm/adt.h> +#include <bsm/adt_event.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +/* Idle time before declaring a client to be dead. */ +uint_t door_idle_time = 10; + +static int door_fd = -1; + +static uid_t cur_user = (uid_t)-1; +static adt_session_data_t *cur_ah; + +/* + * event_queue is a simple circular queue of fixed size. 'evput' is the next + * location to write, and 'evget' is the next waiting event. The queue size is + * chosen so that it's extremely unlikely that a functioning GUI could get this + * far behind on events and still be at all usable. (Too large, and we'd wait + * too long backing off to automatic mode on a broken GUI. Too small, and an + * interface up/down transient would cause us to switch to automatic mode too + * easily.) + */ +#define MAX_DESCR_EVENTS 64 +static nwam_descr_event_t event_queue[MAX_DESCR_EVENTS]; +static nwam_descr_event_t *evput = event_queue, *evget = event_queue; +static struct wireless_lan *current_wlans; +static size_t current_wlansize; + +/* + * This lock protects the event queue and the current_wlans list. + */ +static pthread_mutex_t event_lock = PTHREAD_MUTEX_INITIALIZER; + +/* + * sleep_cv is used to block waiting for new events to appear in an empty + * queue. client_cv is used to wait for event client threads to wake up and + * return before shutting down the daemon. + */ +static pthread_cond_t sleep_cv, client_cv; +static uint_t sleeping_clients; +static boolean_t active_clients; +static uint32_t client_expire; + +/* + * Register a "user logout" event with the auditing system. + * A "logout" occurs when the GUI stops calling the event wait system (detected + * either by idle timer or queue overflow), or when a different authorized user + * calls the daemon (the previous one is logged out), or when the daemon itself + * is shut down. + */ +static void +audit_detach(void) +{ + adt_event_data_t *event; + + event = adt_alloc_event(cur_ah, ADT_nwam_detach); + if (event == NULL) + syslog(LOG_ERR, "audit failure: detach allocation: %m"); + else if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) + syslog(LOG_ERR, "audit failure: detach put: %m"); + adt_free_event(event); + (void) adt_end_session(cur_ah); + cur_ah = NULL; + cur_user = (uid_t)-1; +} + +/* + * Register either a normal "user login" event (if 'attached' is set) or a + * failed login (if 'attached' is not set) with auditing. + */ +static void +audit_attach(ucred_t *ucr, boolean_t attached) +{ + adt_session_data_t *ah; + adt_event_data_t *event; + int retv, status, retval; + + if (adt_start_session(&ah, NULL, 0) != 0) { + syslog(LOG_ERR, "audit failure: session start: %m"); + return; + } + + if (adt_set_from_ucred(ah, ucr, ADT_NEW) != 0) { + syslog(LOG_ERR, "audit failure: session credentials: %m"); + goto failure; + } + if ((event = adt_alloc_event(ah, ADT_nwam_attach)) == NULL) { + syslog(LOG_ERR, "audit failure: audit allocation: %m"); + goto failure; + } + event->adt_nwam_attach.auth_used = NET_AUTOCONF_AUTH; + if (attached) { + status = ADT_SUCCESS; + retval = ADT_SUCCESS; + } else { + status = ADT_FAILURE; + retval = ADT_FAIL_VALUE_AUTH; + } + retv = adt_put_event(event, status, retval); + adt_free_event(event); + if (retv != 0) { + syslog(LOG_ERR, "audit failure: attach put: %m"); + goto failure; + } + + /* + * Only successful attach records result in a detach. All else have + * (at most) a single failure record, and nothing else. Thus, we do + * not set cur_ah until we know we've written an attach record. + */ + if (attached) { + cur_ah = ah; + return; + } + +failure: + (void) adt_end_session(ah); +} + +/* Convert descriptive event to a text name for debug log */ +static const char * +descr_event_name(libnwam_descr_evtype_t evt) +{ + /* + * Cast to int so that compiler and lint don't complain about extra + * 'default' case, and so that we can handle stray values. + */ + switch ((int)evt) { + case deInitial: + return ("Initial"); + case deInterfaceUp: + return ("InterfaceUp"); + case deInterfaceDown: + return ("InterfaceDown"); + case deInterfaceAdded: + return ("InterfaceAdded"); + case deInterfaceRemoved: + return ("InterfaceRemoved"); + case deWlanConnectFail: + return ("WlanConnectFail"); + case deWlanDisconnect: + return ("WlanDisconnect"); + case deWlanConnected: + return ("WlanConnected"); + case deLLPSelected: + return ("LLPSelected"); + case deLLPUnselected: + return ("LLPUnselected"); + case deULPActivated: + return ("ULPActivated"); + case deULPDeactivated: + return ("ULPDeactivated"); + case deScanChange: + return ("ScanChange"); + case deScanSame: + return ("ScanSame"); + case deWlanKeyNeeded: + return ("WlanKeyNeeded"); + case deWlanSelectionNeeded: + return ("WlanSelectionNeeded"); + default: + return ("unknown"); + } +} + +/* + * This is called only by ndcWaitEvent, which holds event_lock until it has + * copied out the data from the entry. + */ +static const nwam_descr_event_t * +get_descr_event(void) +{ + nwam_descr_event_t *nde; + static const nwam_descr_event_t init = { deInitial }; + + if (!active_clients) { + syslog(LOG_INFO, "new active door client detected"); + active_clients = B_TRUE; + return (&init); + } + if ((nde = evget) == evput) + return (NULL); + if ((evget = nde + 1) >= event_queue + MAX_DESCR_EVENTS) + evget = event_queue; + /* If this event has a new WLAN snapshot, then update */ + if (nde->nde_wlans != NULL) { + free(current_wlans); + current_wlans = nde->nde_wlans; + current_wlansize = nde->nde_wlansize; + nde->nde_wlans = NULL; + } + return (nde); +} + +/* + * {start,put}_descr_event are called by the reporting functions. This + * function starts a new descriptive event and returns with the lock held (if + * the return value is non-NULL). + */ +static nwam_descr_event_t * +start_descr_event(libnwam_descr_evtype_t evt) +{ + nwam_descr_event_t *nde, *ndenext; + + if (!active_clients || pthread_mutex_lock(&event_lock) != 0) { + dprintf("dropping event %s; no active client", + descr_event_name(evt)); + return (NULL); + } + nde = evput; + if ((ndenext = nde + 1) >= event_queue + MAX_DESCR_EVENTS) + ndenext = event_queue; + if (ndenext == evget) { + syslog(LOG_INFO, "descr event queue overflow"); + active_clients = B_FALSE; + (void) np_queue_add_event(EV_RESELECT, NULL); + evput = evget = event_queue; + audit_detach(); + (void) pthread_mutex_unlock(&event_lock); + return (NULL); + } else { + nde->nde_type = evt; + return (nde); + } +} + +/* Finish reporting the event; must not be called if nde is NULL */ +static void +put_descr_event(nwam_descr_event_t *nde, const char *ifname) +{ + if (ifname != NULL) { + dprintf("putting descr event %s %s", + descr_event_name(nde->nde_type), ifname); + (void) strlcpy(nde->nde_interface, ifname, + sizeof (nde->nde_interface)); + if (++nde >= event_queue + MAX_DESCR_EVENTS) + nde = event_queue; + evput = nde; + (void) pthread_cond_signal(&sleep_cv); + } + /* Cannot drop the lock unless we've acquired it. */ + assert(nde != NULL); + (void) pthread_mutex_unlock(&event_lock); +} + +/* + * Finish reporting an event that sets the WLAN snapshot. If there's no + * client, then update the saved snapshot right now, as we won't be queuing the + * event. + */ +static boolean_t +commit_wlans(nwam_descr_event_t *nde, const struct wireless_lan *wlans, + int wlan_cnt, const char *ifname) +{ + size_t wlansize; + struct wireless_lan *saved_wlans; + + wlansize = sizeof (*saved_wlans) * wlan_cnt; + if ((saved_wlans = malloc(wlansize)) == NULL) { + if (nde != NULL) + put_descr_event(nde, NULL); + return (B_FALSE); + } + (void) memcpy(saved_wlans, wlans, wlansize); + + if (nde != NULL) { + nde->nde_wlansize = wlansize; + nde->nde_wlans = saved_wlans; + put_descr_event(nde, ifname); + return (B_TRUE); + } else { + /* If the UI isn't running, then save the cached results */ + if (pthread_mutex_lock(&event_lock) == 0) { + free(current_wlans); + current_wlans = saved_wlans; + current_wlansize = wlansize; + (void) pthread_mutex_unlock(&event_lock); + } else { + free(saved_wlans); + } + return (B_FALSE); + } +} + +void +report_interface_up(const char *ifname, struct in_addr addr, int prefixlen) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deInterfaceUp)) != NULL) { + nde->nde_v4address = addr; + nde->nde_prefixlen = prefixlen; + put_descr_event(nde, ifname); + } +} + +void +report_interface_down(const char *ifname, libnwam_diag_cause_t cause) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deInterfaceDown)) != NULL) { + nde->nde_cause = cause; + put_descr_event(nde, ifname); + } +} + +void +report_interface_added(const char *ifname) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deInterfaceAdded)) != NULL) + put_descr_event(nde, ifname); +} + +void +report_interface_removed(const char *ifname) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deInterfaceRemoved)) != NULL) + put_descr_event(nde, ifname); +} + +void +report_wlan_connect_fail(const char *ifname) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deWlanConnectFail)) != NULL) + put_descr_event(nde, ifname); +} + +void +report_wlan_disconnect(const struct wireless_lan *wlan) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deWlanDisconnect)) != NULL) { + nde->nde_attrs = wlan->attrs; + put_descr_event(nde, wlan->wl_if_name); + } +} + +void +report_wlan_connected(const struct wireless_lan *wlan) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deWlanConnected)) != NULL) { + nde->nde_attrs = wlan->attrs; + put_descr_event(nde, wlan->wl_if_name); + } +} + +void +report_llp_selected(const char *ifname) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deLLPSelected)) != NULL) + put_descr_event(nde, ifname); +} + +void +report_llp_unselected(const char *ifname, libnwam_diag_cause_t cause) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deLLPUnselected)) != NULL) { + nde->nde_cause = cause; + put_descr_event(nde, ifname); + } +} + +void +report_ulp_activated(const char *ulpname) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deULPActivated)) != NULL) + put_descr_event(nde, ulpname); +} + +void +report_ulp_deactivated(const char *ulpname) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deULPDeactivated)) != NULL) + put_descr_event(nde, ulpname); +} + +void +report_scan_complete(const char *ifname, boolean_t changed, + const struct wireless_lan *wlans, int wlan_cnt) +{ + nwam_descr_event_t *nde; + + nde = start_descr_event(changed ? deScanChange : deScanSame); + (void) commit_wlans(nde, wlans, wlan_cnt, ifname); +} + +boolean_t +request_wlan_key(struct wireless_lan *wlan) +{ + nwam_descr_event_t *nde; + + if ((nde = start_descr_event(deWlanKeyNeeded)) != NULL) { + nde->nde_attrs = wlan->attrs; + put_descr_event(nde, wlan->wl_if_name); + return (B_TRUE); + } else { + return (B_FALSE); + } +} + +boolean_t +request_wlan_selection(const char *ifname, const struct wireless_lan *wlans, + int wlan_cnt) +{ + nwam_descr_event_t *nde; + + nde = start_descr_event(deWlanSelectionNeeded); + return (commit_wlans(nde, wlans, wlan_cnt, ifname)); +} + +/* ARGSUSED */ +static void +thread_cancel_handler(void *arg) +{ + if (--sleeping_clients == 0) { + client_expire = NSEC_TO_SEC(gethrtime()) + door_idle_time; + (void) pthread_cond_signal(&client_cv); + /* + * On the wrong thread; must call start_timer from the main + * thread. + */ + if (client_expire < timer_expire) + (void) np_queue_add_event(EV_DOOR_TIME, NULL); + } + (void) pthread_mutex_unlock(&event_lock); +} + +/* + * A timer is set when there are waiting event collectors. If there haven't + * been any collectors for "a long time," then we assume that the user + * interface has been terminated or is jammed. + */ +void +check_door_life(uint32_t now) +{ + if (active_clients && sleeping_clients == 0) { + if (client_expire > now) { + start_timer(now, client_expire - now); + } else { + syslog(LOG_INFO, + "no active door clients left; flushing queue"); + if (pthread_mutex_lock(&event_lock) == 0) { + active_clients = B_FALSE; + if (evput != evget) { + (void) np_queue_add_event(EV_RESELECT, + NULL); + } + evput = evget = event_queue; + audit_detach(); + (void) pthread_mutex_unlock(&event_lock); + } + } + } +} + +/* + * This is called for an unrecognized UID. We check to see if the user is + * authorized to issue commands to the NWAM daemon. + */ +static boolean_t +update_cur_user(ucred_t *ucr) +{ + struct passwd *pwd; + uid_t uid = ucred_getruid(ucr); + boolean_t attached = B_FALSE; + + if ((pwd = getpwuid(uid)) == NULL) { + syslog(LOG_DEBUG, "unable to translate uid %d to a name", uid); + } else if (chkauthattr(NET_AUTOCONF_AUTH, pwd->pw_name) == 0) { + syslog(LOG_DEBUG, "user %s (%d) does not have %s", pwd->pw_name, + uid, NET_AUTOCONF_AUTH); + } else { + attached = B_TRUE; + } + if (pthread_mutex_lock(&event_lock) == 0) { + if (attached) { + audit_detach(); + cur_user = uid; + } + audit_attach(ucr, attached); + (void) pthread_mutex_unlock(&event_lock); + } else { + attached = B_FALSE; + } + return (attached); +} + +/* ARGSUSED */ +static void +nwam_door_server(void *cookie, char *argp, size_t arg_size, door_desc_t *dp, + uint_t ndesc) +{ + /* LINTED: alignment */ + nwam_door_cmd_t *ndc = (nwam_door_cmd_t *)argp; + int retv = -1; + ucred_t *ucr = NULL; + libnwam_interface_type_t ift; + + if (arg_size < sizeof (*ndc) || door_ucred(&ucr) != 0) { + retv = EINVAL; + (void) door_return((char *)&retv, sizeof (retv), NULL, 0); + return; + } + + if (ucred_getruid(ucr) != cur_user && !update_cur_user(ucr)) { + ucred_free(ucr); + retv = EPERM; + (void) door_return((char *)&retv, sizeof (retv), NULL, 0); + return; + } + ucred_free(ucr); + + /* + * Only the blocking event wait can be canceled, and then only when + * headed for a block. + */ + (void) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + + switch (ndc->ndc_type) { + case ndcNull: + dprintf("door: null event from client"); + retv = 0; + break; + + case ndcWaitEvent: { + const nwam_descr_event_t *nde; + nwam_descr_event_t ndcopy; + + if ((retv = pthread_mutex_lock(&event_lock)) != 0) + break; + if ((nde = get_descr_event()) != NULL) { + ndcopy = *nde; + (void) pthread_mutex_unlock(&event_lock); + dprintf("door: returning waiting event %s", + descr_event_name(ndcopy.nde_type)); + (void) door_return((char *)&ndcopy, sizeof (ndcopy), + NULL, 0); + return; + } + + (void) pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_cleanup_push(thread_cancel_handler, NULL); + sleeping_clients++; + while ((nde = get_descr_event()) == NULL && + door_fd != -1) { + if (pthread_cond_wait(&sleep_cv, &event_lock) != 0) + break; + } + if (nde != NULL) + ndcopy = *nde; + pthread_cleanup_pop(1); + if (nde == NULL) { + retv = EBADF; + break; + } + dprintf("door: returning waited-for event %s", + descr_event_name(ndcopy.nde_type)); + (void) door_return((char *)&ndcopy, sizeof (ndcopy), NULL, 0); + return; + } + + case ndcGetLLPList: { + nwam_llp_data_t *nld; + llp_t *llplist, *llpstack, *llp; + size_t llpsize; + uint_t count; + char selected[LIFNAMSIZ], locked[LIFNAMSIZ]; + + /* + * Note that door_return never returns here, so we can't just + * use malloc'd memory. Copy over to a stack-allocated buffer + * and do the string pointer fix-ups. + */ + if ((retv = pthread_mutex_lock(&machine_lock)) != 0) + break; + errno = 0; + llplist = get_llp_list(&llpsize, &count, selected, locked); + (void) pthread_mutex_unlock(&machine_lock); + if (llplist != NULL) { + nld = alloca(sizeof (*nld) + llpsize); + nld->nld_count = count; + (void) strlcpy(nld->nld_selected, selected, + sizeof (nld->nld_selected)); + (void) strlcpy(nld->nld_locked, locked, + sizeof (nld->nld_locked)); + llpstack = (llp_t *)(nld + 1); + (void) memcpy(llpstack, llplist, llpsize); + llp = llpstack; + while (count-- > 0) { + if (llp->llp_ipv4addrstr != NULL) + llp->llp_ipv4addrstr -= + (uintptr_t)llplist; + if (llp->llp_ipv6addrstr != NULL) + llp->llp_ipv6addrstr -= + (uintptr_t)llplist; + llp++; + } + free(llplist); + llpsize += sizeof (*nld); + } else { + retv = errno; + dprintf("door: no LLP list to get"); + break; + } + dprintf("door: get llp list returning %d entries", + nld->nld_count); + (void) door_return((char *)nld, llpsize, NULL, 0); + return; + } + + case ndcSetLLPPriority: + if ((retv = pthread_mutex_lock(&machine_lock)) != 0) + break; + dprintf("door: set priority on %s to %d", + ndc->ndc_interface, ndc->ndc_priority); + retv = set_llp_priority(ndc->ndc_interface, ndc->ndc_priority); + (void) pthread_mutex_unlock(&machine_lock); + break; + + case ndcLockLLP: + if ((retv = pthread_mutex_lock(&machine_lock)) != 0) + break; + if (ndc->ndc_interface[0] == '\0') + dprintf("door: unlocking llp selection"); + else + dprintf("door: locking to %s", ndc->ndc_interface); + retv = set_locked_llp(ndc->ndc_interface); + (void) pthread_mutex_unlock(&machine_lock); + break; + + case ndcGetWlanList: { + char *wlans; + size_t wlansize; + + /* + * We protect ourselves here against a malicious or confused + * user. The list is stable only while we're holding the lock, + * and the lock can't be held during the door return. + */ + if ((retv = pthread_mutex_lock(&event_lock)) != 0) + break; + if (current_wlans == NULL) { + (void) pthread_mutex_unlock(&event_lock); + dprintf("door: no WLAN list to get"); + retv = ENXIO; + break; + } + wlans = alloca(wlansize = current_wlansize); + (void) memcpy(wlans, current_wlans, wlansize); + (void) pthread_mutex_unlock(&event_lock); + dprintf("door: get wlan list returning %lu bytes", + (ulong_t)wlansize); + (void) door_return(wlans, wlansize, NULL, 0); + return; + } + + case ndcGetKnownAPList: { + nwam_known_ap_t *nka; + libnwam_known_ap_t *kalist, *kastack, *kap; + size_t kasize; + uint_t count; + + /* + * Note that door_return never returns here, so we can't just + * use malloc'd memory. Copy over to a stack-allocated buffer + * and do the string pointer fix-ups. + */ + errno = 0; + kalist = get_known_ap_list(&kasize, &count); + if (kalist != NULL) { + nka = alloca(sizeof (*nka) + kasize); + nka->nka_count = count; + kastack = (libnwam_known_ap_t *)(nka + 1); + (void) memcpy(kastack, kalist, kasize); + kap = kastack; + while (count-- > 0) { + kap->ka_bssid -= (uintptr_t)kalist; + kap->ka_essid -= (uintptr_t)kalist; + kap++; + } + free(kalist); + kasize += sizeof (*nka); + } else { + retv = errno; + dprintf("door: no known AP list to get"); + break; + } + dprintf("door: get known AP list returning %u entries", + nka->nka_count); + (void) door_return((char *)nka, kasize, NULL, 0); + return; + } + + case ndcAddKnownAP: + dprintf("door: adding known AP %s %s", + ndc->ndc_essid, ndc->ndc_bssid); + retv = add_known_ap(ndc->ndc_essid, ndc->ndc_bssid); + break; + + case ndcDeleteKnownAP: + dprintf("door: removing known AP %s %s", + ndc->ndc_essid, ndc->ndc_bssid); + retv = delete_known_ap(ndc->ndc_essid, ndc->ndc_bssid); + break; + + case ndcSelectWlan: + if ((retv = pthread_mutex_lock(&machine_lock)) != 0) + break; + dprintf("door: selecting WLAN on %s as %s %s", + ndc->ndc_interface, ndc->ndc_essid, ndc->ndc_bssid); + /* + * Check if we're already connected to the requested + * ESSID/BSSID. If so, then this request succeeds without + * changing anything. Otherwise, tear down the interface + * (disconnecting from the WLAN) and set up again. + */ + if (check_wlan_connected(ndc->ndc_interface, ndc->ndc_essid, + ndc->ndc_bssid)) { + retv = 0; + } else { + takedowninterface(ndc->ndc_interface, dcSelect); + if (link_layer_profile != NULL) + link_layer_profile->llp_waiting = B_TRUE; + retv = set_specific_lan(ndc->ndc_interface, + ndc->ndc_essid, ndc->ndc_bssid); + } + (void) pthread_mutex_unlock(&machine_lock); + break; + + case ndcWlanKey: + if ((retv = pthread_mutex_lock(&machine_lock)) != 0) + break; + dprintf("door: selecting WLAN key on %s for %s %s", + ndc->ndc_interface, ndc->ndc_essid, ndc->ndc_bssid); + retv = set_wlan_key(ndc->ndc_interface, ndc->ndc_essid, + ndc->ndc_bssid, ndc->ndc_key); + (void) pthread_mutex_unlock(&machine_lock); + break; + + case ndcStartRescan: + dprintf("door: rescan requested on %s", + ndc->ndc_interface); + ift = get_if_type(ndc->ndc_interface); + if (ift != IF_UNKNOWN && ift != IF_WIRELESS) { + retv = EINVAL; + break; + } + if ((retv = pthread_mutex_lock(&machine_lock)) != 0) + break; + retv = launch_wireless_scan(ndc->ndc_interface); + (void) pthread_mutex_unlock(&machine_lock); + break; + + default: + dprintf("door: unknown request type %d", (int)ndc->ndc_type); + break; + } + if (retv != 0) + dprintf("door: returning to caller with error %d (%s)", + retv, strerror(retv)); + (void) door_return((char *)&retv, sizeof (retv), NULL, 0); +} + +static void +door_cleanup(void) +{ + if (door_fd != -1) { + syslog(LOG_DEBUG, "closing door"); + (void) door_revoke(door_fd); + door_fd = -1; + } + (void) unlink(DOOR_FILENAME); +} + +void +terminate_door(void) +{ + door_cleanup(); + if (pthread_mutex_lock(&event_lock) != 0) + return; + if (sleeping_clients != 0) + syslog(LOG_DEBUG, "waiting on %d sleeping clients", + sleeping_clients); + while (sleeping_clients != 0) { + (void) pthread_cond_broadcast(&sleep_cv); + if (pthread_cond_wait(&client_cv, &event_lock) != 0) + break; + } + free(current_wlans); + current_wlans = NULL; + audit_detach(); + (void) pthread_mutex_unlock(&event_lock); +} + +void +initialize_door(void) +{ + int did; + + /* Do a low-overhead "touch" on the file that will be the door node. */ + syslog(LOG_DEBUG, "opening door"); + did = open(DOOR_FILENAME, + O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_NONBLOCK, + DOOR_FILEMODE); + if (did != -1) { + (void) close(did); + } else if (errno != EEXIST) { + syslog(LOG_ERR, "unable to create control door node: %m"); + exit(EXIT_FAILURE); + } + + (void) atexit(door_cleanup); + + /* Create the door. */ + door_fd = door_create(nwam_door_server, NULL, DOOR_REFUSE_DESC); + if (door_fd == -1) { + syslog(LOG_ERR, "unable to create control door: %m"); + exit(EXIT_FAILURE); + } + + /* Attach the door to the file. */ + (void) fdetach(DOOR_FILENAME); + if (fattach(door_fd, DOOR_FILENAME) == -1) { + syslog(LOG_ERR, "unable to attach control door: %m"); + exit(EXIT_FAILURE); + } +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/events.c b/usr/src/cmd/cmd-inet/lib/nwamd/events.c index 1ae42e38e2..845e98fa97 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/events.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/events.c @@ -20,12 +20,10 @@ */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * This file contains routines to retrieve events from the system and package * them for high level processing. @@ -40,6 +38,11 @@ * Functions of the form addevent_*() provide the mechanism to cook down a * higher level event into an np_event and put it on the queue. * + * hotplug_handler() is called for EC_DEV_ADD and EC_DEV_REMOVE hotplug events + * of class ESC_NETWORK - i.e. hotplug insertion/removal of network card - + * and plumbs/unplumbs the interface, adding/removing it from running + * configuration (the interface and llp lists). + * * routing_events() reads routing messages off of an IPv4 routing socket and * by calling addevent_*() functions places appropriate events on the queue. * @@ -49,32 +52,35 @@ */ #include <arpa/inet.h> -#include <assert.h> #include <errno.h> #include <libsysevent.h> +#include <sys/sysevent/eventdefs.h> +#include <sys/sysevent/dev.h> +#include <libnvpair.h> #include <net/if.h> #include <net/route.h> #include <pthread.h> #include <stdlib.h> #include <string.h> #include <sys/fcntl.h> -#include <sys/sysevent/eventdefs.h> #include <syslog.h> #include <unistd.h> -#include <fcntl.h> #include "defines.h" #include "structures.h" #include "functions.h" #include "variables.h" -struct np_event *equeue = NULL; -static struct np_event *equeue_end = NULL; +struct np_event *equeue; +static struct np_event *equeue_end; pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; pthread_t routing, scan; +static sysevent_handle_t *sysevent_handle; + +static void hotplug_handler(sysevent_t *ev); static void printaddrs(int mask, void *address); static char *printaddr(void **address); static void *getaddr(int addrid, int mask, void *address); @@ -104,25 +110,40 @@ union rtm_buf }; void -free_event(struct np_event *e) +free_event(struct np_event *npe) { - free(e->npe_name); - free(e); + free(npe); } -void -np_queue_add_event(struct np_event *e) +boolean_t +np_queue_add_event(enum np_event_type evt, const char *ifname) { + struct np_event *npe; + size_t slen; + + slen = ifname == NULL ? 0 : (strlen(ifname) + 1); + if ((npe = calloc(1, sizeof (*npe) + slen)) == NULL) { + syslog(LOG_ERR, "event %s alloc for %s failed", + npe_type_str(evt), STRING(ifname)); + return (B_FALSE); + } + if (ifname != NULL) + npe->npe_name = strcpy((char *)(npe + 1), ifname); + npe->npe_type = evt; + (void) pthread_mutex_lock(&queue_mutex); + dprintf("adding event type %s name %s to queue", + npe_type_str(evt), STRING(ifname)); if (equeue_end != NULL) { - equeue_end->npe_next = e; - equeue_end = e; + equeue_end->npe_next = npe; + equeue_end = npe; } else { - equeue = equeue_end = e; + equeue = equeue_end = npe; } equeue_end->npe_next = NULL; (void) pthread_cond_signal(&queue_cond); (void) pthread_mutex_unlock(&queue_mutex); + return (B_TRUE); } /* @@ -154,99 +175,36 @@ const char * npe_type_str(enum np_event_type type) { switch (type) { - case EV_ROUTING: - return ("ROUTING"); - case EV_SYS: - return ("SYS"); - case EV_TIMER: - return ("TIMER"); - case EV_SHUTDOWN: - return ("SHUTDOWN"); - case EV_NEWADDR: - return ("NEWADDR"); - default: - return ("unknown"); - } -} - -static void -addevent_routing_ifa(struct ifa_msghdr *ifa, const char *name) -{ - struct np_event *e; - - dprintf("addevent_routing_ifa"); - if (ifa->ifam_index == 0) { - /* what is this? */ - dprintf("tossing index 0 routing event"); - return; - } - - e = calloc(1, sizeof (*e)); - if (e == NULL) { - syslog(LOG_ERR, "calloc failed"); - return; - } - - switch (ifa->ifam_type) { - case RTM_NEWADDR: - assert(name != NULL); - e->npe_type = EV_NEWADDR; - if ((e->npe_name = strdup(name)) == NULL) { - syslog(LOG_ERR, "strdup failed"); - free(e); - return; - } - dprintf("adding event type %s name %s to queue", - npe_type_str(e->npe_type), STRING(e->npe_name)); - np_queue_add_event(e); - break; - - default: - free(e); - dprintf("unhandled type in addevent_routing_ifa %d", - ifa->ifam_type); - break; - } -} - -static void -addevent_routing_msghdr(struct if_msghdr *ifm, const char *name) -{ - struct np_event *e; - - dprintf("addevent_routing_msghdr"); - if (ifm->ifm_index == 0) { - /* what is this? */ - dprintf("tossing index 0 routing event"); - return; - } - - switch (ifm->ifm_type) { - case RTM_IFINFO: - assert(name != NULL); - e = calloc(1, sizeof (*e)); - if (e == NULL) { - syslog(LOG_ERR, "calloc failed"); - return; - } - - e->npe_type = EV_ROUTING; - if ((e->npe_name = strdup(name)) == NULL) { - syslog(LOG_ERR, "strdup failed"); - free(e); - return; - } - dprintf("flags = %x, IFF_RUNNING = %x", ifm->ifm_flags, - IFF_RUNNING); - dprintf("adding event type %s name %s to queue", - npe_type_str(e->npe_type), STRING(e->npe_name)); - np_queue_add_event(e); - break; - + case EV_LINKDROP: + return ("LINKDROP"); + case EV_LINKUP: + return ("LINKUP"); + case EV_LINKFADE: + return ("LINKFADE"); + case EV_LINKDISC: + return ("LINKDISC"); + case EV_NEWAP: + return ("NEWAP"); + case EV_USER: + return ("USER"); + case EV_TIMER: + return ("TIMER"); + case EV_SHUTDOWN: + return ("SHUTDOWN"); + case EV_NEWADDR: + return ("NEWADDR"); + case EV_RESELECT: + return ("RESELECT"); + case EV_DOOR_TIME: + return ("DOOR_TIME"); + case EV_ADDIF: + return ("ADDIF"); + case EV_REMIF: + return ("REMIF"); + case EV_TAKEDOWN: + return ("TAKEDOWN"); default: - dprintf("unhandled type in addevent_routing_msghdr %d", - ifm->ifm_type); - break; + return ("unknown"); } } @@ -273,6 +231,98 @@ rtmtype_str(int type) } } +/* + * At present, we only handle EC_DEV_ADD/EC_DEV_REMOVE sysevents of + * subclass ESC_NETWORK. These signify hotplug addition/removal. + * + * The sysevents are converted into NWAM events so that we can process them in + * the main loop. If we didn't do this, we'd either have bad pointer + * references or need to have reference counts on everything. Serializing + * through the event mechanism is much simpler. + */ +static void +hotplug_handler(sysevent_t *ev) +{ + int32_t instance; + char *driver; + char ifname[LIFNAMSIZ]; + nvlist_t *attr_list; + char *event_class = sysevent_get_class_name(ev); + char *event_subclass = sysevent_get_subclass_name(ev); + int retv; + + dprintf("hotplug_handler: event %s/%s", event_class, + event_subclass); + + /* Make sure sysevent is of expected class/subclass */ + if ((strcmp(event_class, EC_DEV_ADD) != 0 && + strcmp(event_class, EC_DEV_REMOVE) != 0) || + strcmp(event_subclass, ESC_NETWORK) != 0) { + syslog(LOG_ERR, "hotplug_handler: unexpected sysevent " + "class/subclass %s/%s", event_class, event_subclass); + return; + } + + /* + * Retrieve driver name and instance attributes, and combine to + * get interface name. + */ + if (sysevent_get_attr_list(ev, &attr_list) != 0) { + syslog(LOG_ERR, "hotplug_handler: sysevent_get_attr_list: %m"); + return; + } + retv = nvlist_lookup_string(attr_list, DEV_DRIVER_NAME, &driver); + if (retv == 0) + retv = nvlist_lookup_int32(attr_list, DEV_INSTANCE, &instance); + if (retv != 0) { + syslog(LOG_ERR, "handle_hotplug_interface: nvlist_lookup " + "of attributes failed: %s", strerror(retv)); + } else { + (void) snprintf(ifname, LIFNAMSIZ, "%s%d", driver, instance); + (void) np_queue_add_event(strcmp(event_class, EC_DEV_ADD) == 0 ? + EV_ADDIF : EV_REMIF, ifname); + } + nvlist_free(attr_list); +} + +static void +hotplug_events_unregister(void) +{ + /* Unsubscribe to sysevents */ + sysevent_unbind_handle(sysevent_handle); + sysevent_handle = NULL; +} + +static void +hotplug_events_register(void) +{ + const char *subclass = ESC_NETWORK; + + sysevent_handle = sysevent_bind_handle(hotplug_handler); + if (sysevent_handle == NULL) { + syslog(LOG_ERR, "sysevent_bind_handle: %s", strerror(errno)); + return; + } + /* + * Subscribe to ESC_NETWORK subclass of EC_DEV_ADD and EC_DEV_REMOVE + * events. As a result, we get sysevent notification of hotplug + * add/remove events, which we handle above in hotplug_event_handler(). + */ + if (sysevent_subscribe_event(sysevent_handle, EC_DEV_ADD, &subclass, 1) + != 0 || sysevent_subscribe_event(sysevent_handle, EC_DEV_REMOVE, + &subclass, 1) != 0) { + syslog(LOG_ERR, "sysevent_subscribe_event: %s", + strerror(errno)); + hotplug_events_unregister(); + } +} + +/* + * This thread reads routing socket events and sends them to the main state + * machine. We must be careful with access to interface data structures here, + * as we're not the main thread, which may delete things. Holding a pointer is + * not allowed. + */ /* ARGSUSED */ static void * routing_events(void *arg) @@ -299,10 +349,9 @@ routing_events(void *arg) dprintf("routing socket %d", rtsock); for (;;) { - struct interface *ifp; - char *addrs, *if_name; + char *addrs; struct sockaddr_dl *addr_dl; - struct sockaddr *addr; + struct sockaddr_in *addr_in; rtm = &buffer.r.rtm; n = read(rtsock, &buffer, sizeof (buffer)); @@ -336,6 +385,56 @@ routing_events(void *arg) } switch (rtm->rtm_type) { + case RTM_DELADDR: { + uint64_t ifflags; + + /* + * Check for failure due to CR 6745448: if we get a + * report that an address has been deleted, then check + * for interface up, datalink down, and actual address + * non-zero. If that combination is seen, then this is + * a DHCP cached lease, and we need to remove it from + * the system, or it'll louse up the kernel routes + * (which aren't smart enough to avoid dead + * interfaces). + */ + ifa = (void *)rtm; + addrs = (char *)ifa + sizeof (*ifa); + + dprintf("routing message DELADDR: index %d flags %x", + ifa->ifam_index, ifa->ifam_flags); + printaddrs(ifa->ifam_addrs, addrs); + + if (ifa->ifam_index == 0) { + /* what is this? */ + dprintf("tossing index 0 routing event"); + break; + } + + addr_in = getaddr(RTA_IFA, ifa->ifam_addrs, addrs); + if (addr_in == NULL) { + dprintf("no RTA_IFA in RTM_DELADDR message"); + break; + } + + addr_dl = getaddr(RTA_IFP, ifa->ifam_addrs, addrs); + if (addr_dl == NULL) { + dprintf("no RTA_IFP in RTM_DELADDR message"); + break; + } + + addr_dl->sdl_data[addr_dl->sdl_nlen] = 0; + + if (addr_in->sin_addr.s_addr == INADDR_ANY) { + ifflags = get_ifflags(addr_dl->sdl_data, + AF_INET); + if ((ifflags & IFF_UP) && + !(ifflags & IFF_RUNNING)) + zero_out_v4addr(addr_dl->sdl_data); + } + break; + } + case RTM_NEWADDR: ifa = (void *)rtm; addrs = (char *)ifa + sizeof (*ifa); @@ -344,106 +443,62 @@ routing_events(void *arg) ifa->ifam_index, ifa->ifam_flags); printaddrs(ifa->ifam_addrs, addrs); - if ((addr = (struct sockaddr *)getaddr(RTA_IFA, - ifa->ifam_addrs, addrs)) == NULL) + if (ifa->ifam_index == 0) { + /* what is this? */ + dprintf("tossing index 0 routing event"); break; + } + + addr_in = getaddr(RTA_IFA, ifa->ifam_addrs, addrs); + if (addr_in == NULL) { + dprintf("no RTA_IFA in RTM_NEWADDR message"); + break; + } - if ((addr_dl = (struct sockaddr_dl *)getaddr - (RTA_IFP, ifa->ifam_addrs, addrs)) == NULL) + addr_dl = getaddr(RTA_IFP, ifa->ifam_addrs, addrs); + if (addr_dl == NULL) { + dprintf("no RTA_IFP in RTM_NEWADDR message"); break; + } + /* * We don't use the lladdr in this structure so we can * run over it. */ addr_dl->sdl_data[addr_dl->sdl_nlen] = 0; - if_name = addr_dl->sdl_data; - ifp = get_interface(if_name); - if (ifp == NULL) { - dprintf("no interface struct for %s; ignoring " - "message", STRING(if_name)); - break; - } - /* if no cached address, cache it */ - if (ifp->if_ipaddr == NULL) { - ifp->if_ipaddr = dupsockaddr(addr); - dprintf("cached address %s for link %s", - printaddr((void **)&addr), if_name); - addevent_routing_ifa(ifa, if_name); - } else if (!cmpsockaddr(addr, ifp->if_ipaddr)) { - free(ifp->if_ipaddr); - ifp->if_ipaddr = dupsockaddr(addr); - addevent_routing_ifa(ifa, if_name); - } + update_interface_v4_address(addr_dl->sdl_data, + addr_in->sin_addr.s_addr); break; - case RTM_IFINFO: - { - boolean_t plugged_in; + case RTM_IFINFO: ifm = (void *)rtm; addrs = (char *)ifm + sizeof (*ifm); dprintf("routing message IFINFO: index %d flags %x", ifm->ifm_index, ifm->ifm_flags); printaddrs(ifm->ifm_addrs, addrs); - if ((addr_dl = (struct sockaddr_dl *)getaddr(RTA_IFP, - ifm->ifm_addrs, addrs)) == NULL) + if (ifm->ifm_index == 0) { + dprintf("tossing index 0 routing event"); + break; + } + + addr_dl = getaddr(RTA_IFP, ifm->ifm_addrs, addrs); + if (addr_dl == NULL) { + dprintf("no RTA_IFP in RTM_IFINFO message"); break; + } + /* * We don't use the lladdr in this structure so we can * run over it. */ addr_dl->sdl_data[addr_dl->sdl_nlen] = 0; - if_name = addr_dl->sdl_data; - ifp = get_interface(if_name); - if (ifp == NULL) { - dprintf("no interface struct for %s; ignoring " - "message", STRING(if_name)); - break; - } - /* - * Check for toggling of the IFF_RUNNING flag. - * - * On any change in the flag value, we turn off the - * DHCP flags; the change in the RUNNING state - * indicates a "fresh start" for the interface, so we - * should try dhcp again. - * - * Ignore specific IFF_RUNNING changes for - * wireless interfaces; their semantics are - * a bit different (either the flag is always - * on, or, with newer drivers, it indicates - * whether or not they are connected to an AP). - * - * For wired interfaces, if the interface was - * not plugged in and now it is, start info - * collection. - * - * If it was plugged in and now it is - * unplugged, generate an event. - * - * XXX We probably need a lock to protect - * if_flags setting and getting. - */ - if ((ifp->if_flags & IFF_RUNNING) != - (ifm->ifm_flags & IFF_RUNNING)) { - ifp->if_lflags &= ~IF_DHCPFLAGS; - } - if (ifp->if_type == IF_WIRELESS) - break; - plugged_in = ((ifp->if_flags & IFF_RUNNING) != 0); - ifp->if_flags = ifm->ifm_flags; - if (!plugged_in && - (ifm->ifm_flags & IFF_RUNNING)) { - start_if_info_collect(ifp, NULL); - } else if (plugged_in && - !(ifm->ifm_flags & IFF_RUNNING)) { - check_drop_dhcp(ifp); - addevent_routing_msghdr(ifm, if_name); - } + update_interface_flags(addr_dl->sdl_data, + ifm->ifm_flags); break; - } + default: dprintf("routing message %s socket %d discarded", rtmtype_str(rtm->rtm_type), rtsock); @@ -564,7 +619,6 @@ boolean_t start_event_collection(void) { int err; - boolean_t check_cache = B_TRUE; /* * if these are ever created/destroyed repetitively then we will @@ -586,7 +640,14 @@ start_event_collection(void) dprintf("scan thread: %d", scan); } - walk_interface(start_if_info_collect, &check_cache); + /* + * This function registers a callback which will get a dedicated thread + * for handling of hotplug sysevents when they occur. + */ + hotplug_events_register(); + + dprintf("initial interface scan"); + walk_interface(start_if_info_collect, "check"); return (B_TRUE); } diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/functions.h b/usr/src/cmd/cmd-inet/lib/nwamd/functions.h index bb88f4d2d9..2c429fcefb 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/functions.h +++ b/usr/src/cmd/cmd-inet/lib/nwamd/functions.h @@ -27,58 +27,99 @@ #ifndef _FUNCTIONS_H #define _FUNCTIONS_H -#pragma ident "%Z%%M% %I% %E% SMI" +/* door.c: door-based control/status interface */ +extern void initialize_door(void); +extern void terminate_door(void); +extern void report_interface_up(const char *, struct in_addr, int); +extern void report_interface_down(const char *, libnwam_diag_cause_t); +extern void report_interface_added(const char *); +extern void report_interface_removed(const char *); +extern void report_wlan_connect_fail(const char *); +extern void report_wlan_disconnect(const struct wireless_lan *); +extern void report_wlan_connected(const struct wireless_lan *); +extern void report_llp_selected(const char *); +extern void report_llp_unselected(const char *, libnwam_diag_cause_t); +extern void report_ulp_activated(const char *); +extern void report_ulp_deactivated(const char *); +extern void report_scan_complete(const char *, boolean_t, + const struct wireless_lan *, int); +extern boolean_t request_wlan_key(struct wireless_lan *); +extern boolean_t request_wlan_selection(const char *, + const struct wireless_lan *, int); +extern void check_door_life(uint32_t); /* events.c: event queue handling */ extern void free_event(struct np_event *); -extern void np_queue_add_event(struct np_event *); +extern boolean_t np_queue_add_event(enum np_event_type, const char *); extern struct np_event *np_queue_get_event(void); extern const char *npe_type_str(enum np_event_type); extern boolean_t start_event_collection(void); /* interface.c: interface and upper layer profile handling */ -extern void check_drop_dhcp(struct interface *); -extern void gen_newif_event(struct interface *); extern void initialize_interfaces(void); extern struct interface *add_interface(sa_family_t, const char *, uint64_t); +extern void remove_interface(const char *); extern struct interface *get_interface(const char *); extern void walk_interface(void (*)(struct interface *, void *), void *); -extern enum interface_type find_if_type(const char *); -extern const char *if_type_str(enum interface_type); +extern libnwam_interface_type_t find_if_type(const char *); +extern const char *if_type_str(libnwam_interface_type_t); +extern void update_interface_v4_address(const char *, in_addr_t); +extern void update_interface_flags(const char *, int); extern boolean_t interface_is_active(const struct interface *); extern void show_if_status(const char *); -extern boolean_t bringupinterface(const char *, const char *, const char *, +extern return_vals_t bringupinterface(const char *, const char *, const char *, boolean_t); -extern void takedowninterface(const char *, boolean_t, boolean_t); -extern void take_down_all_ifs(const char *); -extern void check_interface_timer(struct interface *, void *); +extern void takedowninterface(const char *, libnwam_diag_cause_t); +extern void clear_cached_address(const char *); +extern void check_interface_timers(uint32_t); extern void start_if_info_collect(struct interface *, void *); extern boolean_t ulp_is_active(void); extern void activate_upper_layer_profile(boolean_t, const char *); extern void deactivate_upper_layer_profile(void); -extern void display(const char *); extern int lookup_boolean_property(const char *, const char *, boolean_t *); extern int lookup_count_property(const char *, const char *, uint64_t *); +extern boolean_t is_interface_ok(const char *); +extern libnwam_interface_type_t get_if_type(const char *); +extern void get_interface_state(const char *, boolean_t *, boolean_t *); +extern void print_interface_status(void); /* wireless.c: wifi link handling */ -extern void init_mutexes(void); -extern boolean_t connect_chosen_lan(struct wireless_lan *, struct interface *); +extern void initialize_wireless(void); +extern void add_wireless_if(const char *); +extern void remove_wireless_if(const char *); extern struct wireless_lan *prompt_for_visited(void); -extern boolean_t handle_wireless_lan(struct interface *); -extern boolean_t scan_wireless_nets(struct interface *); -extern void create_known_wifi_nets_file(void); -extern void update_known_wifi_nets_file(const char *, const char *); +extern return_vals_t handle_wireless_lan(const char *); +extern libnwam_known_ap_t *get_known_ap_list(size_t *, uint_t *); +extern int add_known_ap(const char *, const char *); +extern int delete_known_ap(const char *, const char *); extern void *periodic_wireless_scan(void *); +extern boolean_t check_wlan_connected(const char *, const char *, const char *); +extern int set_specific_lan(const char *, const char *, const char *); +extern int set_wlan_key(const char *, const char *, const char *, const char *); +extern int launch_wireless_scan(const char *); +extern void disconnect_wlan(const char *); +extern void get_wireless_state(const char *, boolean_t *, boolean_t *); +extern void print_wireless_status(void); /* llp.c: link layer profile handling */ +extern void initialize_llp(void); extern void llp_parse_config(void); +extern void llp_add_file(const llp_t *); +extern llp_t *llp_add(const char *); +extern void llp_delete(llp_t *); extern llp_t *llp_lookup(const char *); extern llp_t *llp_high_pri(llp_t *, llp_t *); extern llp_t *llp_best_avail(void); -extern boolean_t llp_activate(llp_t *); -extern void llp_deactivate(void); -extern void llp_swap(llp_t *); +extern void llp_swap(llp_t *, libnwam_diag_cause_t); extern char *llp_prnm(llp_t *); +extern void llp_write_changed_priority(llp_t *); +extern int set_llp_priority(const char *, int); +extern int set_locked_llp(const char *); +extern llp_t *get_llp_list(size_t *, uint_t *, char *, char *); +extern void llp_reselect(void); +extern void llp_get_name_and_type(char *, size_t, libnwam_interface_type_t *); +extern libnwam_ipv4src_t llp_get_ipv4src(const char *); +extern void print_llp_status(void); /* state_machine.c: state machine handling */ extern void state_machine(struct np_event *); @@ -87,13 +128,10 @@ extern void cleanup(void); /* util.c: utility & ipc functions */ extern void dprintf(const char *, ...); extern uint64_t get_ifflags(const char *, sa_family_t); -extern boolean_t is_plugged_in(struct interface *); +extern void zero_out_v4addr(const char *); extern int start_childv(const char *, char const * const *); extern int start_child(const char *, ...); extern void start_timer(uint32_t, uint32_t); -extern boolean_t valid_graphical_user(boolean_t); extern void lookup_zonename(char *, size_t); -extern struct sockaddr *dupsockaddr(const struct sockaddr *); -extern boolean_t cmpsockaddr(const struct sockaddr *, const struct sockaddr *); #endif /* _FUNCTIONS_H */ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/interface.c b/usr/src/cmd/cmd-inet/lib/nwamd/interface.c index a455d91435..397990d935 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/interface.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/interface.c @@ -24,16 +24,12 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * This file contains the routines that manipulate interfaces, the * list of interfaces present on the system, and upper layer profiles; - * and various support functions. It also contains the functions used - * to display various bits of informations and queries for the user - * using /usr/bin/zenity, and a set of functions to read property - * values stored in the SMF repository. Finally, it contains the - * functions required for the "gather info" threads. + * and various support functions. It also contains a set of functions + * to read property values stored in the SMF repository. Finally, it + * contains the functions required for the "gather info" threads. * * The daemon maintains a list of structures that represent each IPv4 * interface found on the system (after doing 'ifconfig -a plumb'). @@ -72,6 +68,20 @@ * found at startup. This thread will do a scan on a wireless interface, * and initiate DHCP on a wired interface. It will then generate an event * for the state machine that indicates the availability of a new interface. + * + * The ifs_head and associated list pointers are protected by ifs_lock. Only + * the main thread may modify the list (single writer), and it does so with the + * lock held. As a consequence, the main thread alone may read the list (and + * examine pointers) without holding any locks. All other threads must hold + * ifs_lock for the duration of any examination of the data structures, and + * must not deal directly in interface pointers. (A thread may also hold + * machine_lock to block the main thread entirely in order to manipulate the + * data; such use is isolated to the door interface.) + * + * Functions in this file have comments noting where the main thread alone is + * the caller. These functions do not need to acquire the lock. + * + * If you hold both ifs_lock and llp_lock, you must take ifs_lock first. */ #include <errno.h> @@ -80,22 +90,17 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> -#include <assert.h> #include <pthread.h> -#include <sys/sockio.h> #include <syslog.h> #include <unistd.h> #include <libscf.h> -#include <utmpx.h> -#include <pwd.h> #include <sys/types.h> #include <arpa/inet.h> #include <inetcfg.h> -#include <locale.h> -#include <libintl.h> #include <signal.h> #include <stdarg.h> #include <sys/sysmacros.h> +#include <sys/wait.h> #include <libdllink.h> #include "defines.h" @@ -103,35 +108,19 @@ #include "functions.h" #include "variables.h" -static struct interface *ifs_head = NULL; -static struct interface *ifs_wired = NULL; -static struct interface *ifs_wireless = NULL; +static pthread_mutex_t ifs_lock = PTHREAD_MUTEX_INITIALIZER; -static char upper_layer_profile[MAXHOSTNAMELEN]; +static struct interface *ifs_head; +static struct interface *ifs_wired, *ifs_wired_last; +static struct interface *ifs_wireless, *ifs_wireless_last; -static void print_interface_list(); -static struct interface *get_next_interface(struct interface *); +static char upper_layer_profile[MAXHOSTNAMELEN]; #define LOOPBACK_IF "lo0" void -display(const char *msg) -{ - char cmd[1024]; - - dprintf("display('%s')", STRING(msg)); - if (valid_graphical_user(B_FALSE)) { - (void) snprintf(cmd, sizeof (cmd), "--text=%s", msg); - (void) start_child(ZENITY, "--info", cmd, NULL); - } else { - syslog(LOG_INFO, "%s", msg); - } -} - -void show_if_status(const char *ifname) { - char msg[128]; icfg_if_t intf; icfg_handle_t h; struct sockaddr_in sin; @@ -152,15 +141,13 @@ show_if_status(const char *ifname) return; } icfg_close(h); - (void) snprintf(msg, sizeof (msg), - gettext("Brought interface %s up, got address %s."), ifname, - inet_ntoa(sin.sin_addr)); - display(msg); + report_interface_up(ifname, sin.sin_addr, prefixlen); } /* * If this interface matches the currently active llp, return B_TRUE. * Otherwise, return B_FALSE. + * Called only from main thread. */ boolean_t interface_is_active(const struct interface *ifp) @@ -181,13 +168,25 @@ start_dhcp(struct interface *ifp) uint32_t now_s; uint64_t timer_s; - if ((ifp->if_lflags & IF_DHCPSTARTED) != 0) { + if (ifp->if_lflags & IF_DHCPSTARTED) { dprintf("start_dhcp: already started; returning"); return; } ifp->if_lflags |= IF_DHCPSTARTED; - (void) start_child(IFCONFIG, ifp->if_name, "dhcp", "wait", "0", NULL); + /* + * If we need to use DHCP and DHCP is already controlling the + * interface, we don't need to do anything. Otherwise, start it now. + */ + if (!(ifp->if_flags & IFF_DHCPRUNNING)) { + dprintf("launching DHCP on %s", ifp->if_name); + (void) start_child(IFCONFIG, ifp->if_name, "dhcp", "wait", "0", + NULL); + } else { + dprintf("DHCP already running on %s; resetting timer", + ifp->if_name); + } + ifp->if_lflags &= ~IF_DHCPFAILED; /* start dhcp timer */ res = lookup_count_property(OUR_PG, "dhcp_wait_time", &timer_s); @@ -429,7 +428,7 @@ void activate_upper_layer_profile(boolean_t do_dhcp, const char *ifname) { FILE *f; - char buffer[1024]; + char buffer[1024], *cp; size_t buflen; size_t offset; const char bringup[] = "/bringup"; @@ -452,33 +451,65 @@ activate_upper_layer_profile(boolean_t do_dhcp, const char *ifname) } } f = popen(ULP_DIR "/check-conditions", "r"); - if (f == NULL) + if (f == NULL) { + /* note that this doesn't happen if the file is missing */ + syslog(LOG_ERR, "popen: check-conditions: %m"); return; + } /* * We want to build a path to the user's upper layer profile script * that looks like ULP_DIR "/<string we read here>/bringup". If we * leave some space at the beginning of this buffer for ULP_DIR "/" * that saves us some shuffling later. */ - offset = sizeof (ULP_DIR); - if (fgets(buffer + offset, + offset = strlcpy(buffer, ULP_DIR "/", sizeof (buffer)); + cp = fgets(buffer + offset, MIN(sizeof (upper_layer_profile), sizeof (buffer) - offset), - f) == NULL) { - (void) pclose(f); - return; /* EOF before anything read */ - } - (void) pclose(f); - (void) memcpy(buffer, ULP_DIR "/", sizeof (ULP_DIR)); + f); buflen = strlen(buffer); if (buffer[buflen - 1] == '\n') buffer[--buflen] = '\0'; - (void) memcpy(upper_layer_profile, buffer + offset, - buflen + 1 - offset); - (void) strlcpy(buffer + buflen, bringup, sizeof (buffer) - buflen); - (void) start_child(PFEXEC, "-P", "basic", buffer, NULL); - syslog(LOG_NOTICE, "upper layer profile %s activated", - upper_layer_profile); + /* Need to check for script error before interpreting result */ + res = pclose(f); + if (res == -1) { + syslog(LOG_ERR, "check-conditions: pclose: %m"); + return; + } + if (WIFEXITED(res)) { + if (WEXITSTATUS(res) == 0) { + if (cp == NULL || *cp == '\0') { + syslog(LOG_DEBUG, + "check-conditions returned no information"); + } else { + (void) strlcpy(upper_layer_profile, + buffer + offset, + sizeof (upper_layer_profile)); + (void) strlcpy(buffer + buflen, bringup, + sizeof (buffer) - buflen); + (void) start_child(PFEXEC, "-P", "basic", + buffer, NULL); + syslog(LOG_NOTICE, + "upper layer profile %s activated", + upper_layer_profile); + report_ulp_activated(upper_layer_profile); + } + } else if (access(ULP_DIR "/check-conditions", X_OK) == 0) { + syslog(LOG_ERR, + "check-conditions exited with status %d", + WEXITSTATUS(res)); + } else if (errno == ENOENT) { + syslog(LOG_DEBUG, "check-conditions not present"); + } else { + syslog(LOG_ERR, "check-conditions: %m"); + } + } else if (WIFSIGNALED(res)) { + syslog(LOG_ERR, "check-conditions exit on SIG%s", + strsignal(WTERMSIG(res))); + } else { + syslog(LOG_ERR, + "check-conditions terminated in unknown manner"); + } } void @@ -499,33 +530,54 @@ deactivate_upper_layer_profile(void) syslog(LOG_NOTICE, "upper layer profile %s deactivated", upper_layer_profile); + report_ulp_deactivated(upper_layer_profile); + upper_layer_profile[0] = '\0'; } /* - * Returns B_TRUE if the interface is successfully brought up; - * B_FALSE if bringup fails. + * Returns SUCCESS if the interface is successfully brought up, + * FAILURE if bringup fails, or WAITING if we'll need to wait on the GUI to run. + * Called only in the main thread or a thread holding machine_lock. */ -boolean_t +return_vals_t bringupinterface(const char *ifname, const char *host, const char *ipv6addr, boolean_t ipv6onlink) { - boolean_t do_dhcp; struct interface *intf; - uint64_t ifflags; intf = get_interface(ifname); if (intf == NULL) { syslog(LOG_ERR, "could not bring up interface %s: not in list", ifname); - return (B_FALSE); + return (FAILURE); } /* check current state; no point going on if flags are 0 */ - if ((ifflags = get_ifflags(ifname, intf->if_family)) == 0) { + if ((intf->if_flags = get_ifflags(ifname, intf->if_family)) == 0) { dprintf("bringupinterface(%s): get_ifflags() returned 0", ifname); - return (B_FALSE); + return (FAILURE); + } + + if (intf->if_type == IF_WIRELESS) { + switch (handle_wireless_lan(ifname)) { + case WAITING: + intf->if_up_attempted = B_TRUE; + return (WAITING); + case FAILURE: + syslog(LOG_INFO, "Could not connect to any WLAN, not " + "bringing %s up", ifname); + return (FAILURE); + } + } + intf->if_up_attempted = B_TRUE; + + /* physical level must now be up; bail out if not */ + intf->if_flags = get_ifflags(ifname, intf->if_family); + if (!(intf->if_flags & IFF_RUNNING)) { + dprintf("bringupinterface(%s): physical layer down", ifname); + return (FAILURE); } /* @@ -541,63 +593,36 @@ bringupinterface(const char *ifname, const char *host, const char *ipv6addr, ipv6addr, "up", NULL); } } + intf->if_v6onlink = ipv6onlink; - do_dhcp = (strcmp(host, "dhcp") == 0); - - /* - * If we need to use DHCP and DHCP is already controlling - * the interface, we don't need to do anything. - */ - if (do_dhcp && (ifflags & IFF_DHCPRUNNING) != 0) { - dprintf("bringupinterface: nothing to do"); - return (B_TRUE); - } - - if (intf->if_type == IF_WIRELESS) { - if (!handle_wireless_lan(intf)) { - syslog(LOG_INFO, "Could not connect to any WLAN, not " - "bringing %s up", ifname); - return (B_FALSE); - } - } - - if (do_dhcp) { + if (strcmp(host, "dhcp") == 0) { start_dhcp(intf); } else { (void) start_child(IFCONFIG, ifname, host, NULL); (void) start_child(IFCONFIG, ifname, "up", NULL); } - return (B_TRUE); + syslog(LOG_DEBUG, "brought up %s", ifname); + + return (SUCCESS); } +/* Called only in the main thread */ void -takedowninterface(const char *ifname, boolean_t popup, boolean_t v6onlink) +takedowninterface(const char *ifname, libnwam_diag_cause_t cause) { uint64_t flags; struct interface *ifp; - dprintf("takedowninterface(%s, %s, %s)", ifname, - BOOLEAN_TO_STRING(popup), BOOLEAN_TO_STRING(v6onlink)); + dprintf("takedowninterface(%s, %d)", ifname, (int)cause); if ((ifp = get_interface(ifname)) == NULL) { dprintf("takedowninterface: can't find interface struct for %s", ifname); - } else { - if (ifp->if_lflags & IF_DHCPFAILED) { - /* - * We're here because of a dhcp failure, and - * we actually want dhcp to keep trying. So - * don't take the interface down. - */ - dprintf("takedowninterface: still trying for dhcp on " - "%s, so will not take down interface", ifname); - return; - } } flags = get_ifflags(ifname, AF_INET); - if ((flags & IFF_DHCPRUNNING) != 0) { + if (flags & IFF_DHCPRUNNING) { /* * We generally prefer doing a release, as that tells the * server that it can relinquish the lease, whereas drop is @@ -612,14 +637,14 @@ takedowninterface(const char *ifname, boolean_t popup, boolean_t v6onlink) NULL); } } else { - if ((flags & IFF_UP) != 0) + if (flags & IFF_UP) (void) start_child(IFCONFIG, ifname, "down", NULL); /* need to unset a statically configured addr */ (void) start_child(IFCONFIG, ifname, "0.0.0.0", "netmask", "0", "broadcast", "0.0.0.0", NULL); } - if (v6onlink) { + if (ifp == NULL || ifp->if_v6onlink) { /* * Unplumbing the link local interface causes dhcp and ndpd to * remove other addresses they have added. @@ -627,53 +652,37 @@ takedowninterface(const char *ifname, boolean_t popup, boolean_t v6onlink) (void) start_child(IFCONFIG, ifname, "inet6", "unplumb", NULL); } - if (ifp->if_type == IF_WIRELESS) - (void) dladm_wlan_disconnect(ifp->if_linkid); + if (ifp == NULL || ifp->if_up_attempted) + report_interface_down(ifname, cause); - dprintf("takedown interface, free cached ip address"); if (ifp != NULL) { - free(ifp->if_ipaddr); - ifp->if_ipaddr = NULL; - } - if (popup) { - char msg[64]; /* enough to hold this string */ - - (void) snprintf(msg, sizeof (msg), - gettext("Took interface %s down."), ifname); - display(msg); + if (ifp->if_type == IF_WIRELESS) + disconnect_wlan(ifp->if_name); + dprintf("takedown interface, zero cached ip address"); + ifp->if_flags = flags; + ifp->if_lflags &= ~IF_DHCPSTARTED & ~IF_DHCPACQUIRED; + ifp->if_ipv4addr = INADDR_ANY; + ifp->if_up_attempted = B_FALSE; } } -/* - * Take down all known interfaces. If ignore_if is non-null, an - * active (IFF_UP) interface whose name matches ignore_if will *not* - * be taken down. - */ +/* Called only in the main thread */ void -take_down_all_ifs(const char *ignore_if) +clear_cached_address(const char *ifname) { struct interface *ifp; - uint64_t flags; - boolean_t ignore_set = (ignore_if != NULL); - - deactivate_upper_layer_profile(); - - for (ifp = get_next_interface(NULL); ifp != NULL; - ifp = get_next_interface(ifp)) { - if (ignore_set && strcmp(ifp->if_name, ignore_if) == 0) - continue; - flags = get_ifflags(ifp->if_name, ifp->if_family); - if ((flags & IFF_UP) != 0) { - takedowninterface(ifp->if_name, B_FALSE, - ifp->if_family == AF_INET6); - } - } -} + uint64_t ifflags; -static struct interface * -get_next_interface(struct interface *ifp) -{ - return (ifp == NULL ? ifs_head : ifp->if_next); + if ((ifp = get_interface(ifname)) == NULL) { + dprintf("clear_cached_address: can't find interface struct " + "for %s", ifname); + return; + } + ifflags = get_ifflags(ifname, AF_INET); + if ((ifflags & IFF_UP) && !(ifflags & IFF_RUNNING)) + zero_out_v4addr(ifname); + ifp->if_ipv4addr = INADDR_ANY; + ifp->if_lflags &= ~IF_DHCPFLAGS; } /* @@ -685,70 +694,73 @@ get_next_interface(struct interface *ifp) static void interface_list_insert(struct interface *ifp) { - struct interface **wpp; - struct interface *endp; - boolean_t first_wireless = B_FALSE; - boolean_t first_wired = B_FALSE; + struct interface **headpp, **lastpp; + struct interface *pchain, *nextp; + + if (pthread_mutex_lock(&ifs_lock) != 0) + return; switch (ifp->if_type) { case IF_WIRELESS: - first_wireless = (ifs_wireless == NULL); - wpp = &ifs_wireless; - endp = NULL; + /* + * Wireless entries are in the wireless list, and are chained + * after the wired entries. If there are no wired entries, then + * chain on main list. + */ + headpp = &ifs_wireless; + lastpp = &ifs_wireless_last; + pchain = ifs_wired_last; + nextp = NULL; break; case IF_WIRED: - first_wired = (ifs_wired == NULL); - wpp = &ifs_wired; - endp = ifs_wireless; + /* + * Wired entries are on the wired list, and are chained before + * the wireless entries. + */ + headpp = &ifs_wired; + lastpp = &ifs_wired_last; + pchain = NULL; + nextp = ifs_wireless; break; default: /* don't add to the list */ + (void) pthread_mutex_unlock(&ifs_lock); return; } - /* set list head if this is the first entry */ - if (ifs_head == NULL) { - ifs_head = *wpp = ifp; - ifp->if_next = NULL; - return; + /* Connect into the correct list */ + if (*lastpp == NULL) { + /* + * If there's a previous list, then wire to the end of + * that, as we're the new head here. + */ + if (pchain != NULL) + pchain->if_next = ifp; + *headpp = ifp; + } else { + (*lastpp)->if_next = ifp; } + *lastpp = ifp; - if (*wpp != NULL) { - while (*wpp != endp) - wpp = &(*wpp)->if_next; - } - *wpp = ifp; - ifp->if_next = endp; - - /* update list head if we just inserted the first wired interface */ - if (first_wired) - ifs_head = ifs_wired; - - /* link sections if we just inserted the first wireless interface */ - if (first_wireless) { - wpp = &ifs_wired; - while (*wpp != NULL) - wpp = &(*wpp)->if_next; - *wpp = ifs_wireless; - } + ifp->if_next = nextp; + + /* Fix up the main list; it's always wired-first */ + ifs_head = ifs_wired == NULL ? ifs_wireless : ifs_wired; + + (void) pthread_mutex_unlock(&ifs_lock); } /* * Returns the interface structure upon success. Returns NULL and sets - * errno upon error. If lr is null then it will look up the information - * needed. - * - * Note that given the MT nature of this program we are almost certainly - * racing for this structure. That needs to be fixed. + * errno upon error. */ struct interface * add_interface(sa_family_t family, const char *name, uint64_t flags) { struct interface *i; - datalink_id_t linkid = DATALINK_INVALID_LINKID; - enum interface_type iftype; + libnwam_interface_type_t iftype; if (name == NULL) return (NULL); @@ -780,77 +792,109 @@ add_interface(sa_family_t family, const char *name, uint64_t flags) return (NULL); } - if ((i = malloc(sizeof (*i))) == NULL) { + if ((i = calloc(1, sizeof (*i))) == NULL) { dprintf("add_interface: malloc failed"); return (NULL); } - i->if_name = strdup(name); - if (i->if_name == NULL) { - free(i); - dprintf("add_interface: malloc failed"); - return (NULL); - } - i->if_ipaddr = NULL; + (void) strlcpy(i->if_name, name, sizeof (i->if_name)); i->if_family = family; i->if_type = iftype; i->if_flags = flags == 0 ? get_ifflags(name, family) : flags; - i->if_lflags = 0; - i->if_timer_expire = 0; - - /* - * If linkid is DATALINK_INVALID_LINKID, it is an IP-layer only - * interface. - */ - (void) dladm_name2info(name, &linkid, NULL, NULL, NULL); - i->if_linkid = linkid; dprintf("added interface %s of type %s af %d; is %savailable", i->if_name, if_type_str(i->if_type), i->if_family, - ((i->if_type == IF_WIRELESS) || - ((i->if_flags & IFF_RUNNING) != 0)) ? "" : "not "); + (i->if_flags & IFF_RUNNING) ? "" : "not "); interface_list_insert(i); + if (iftype == IF_WIRELESS) + add_wireless_if(name); + return (i); } /* + * This is called only by the main thread. + */ +void +remove_interface(const char *ifname) +{ + struct interface *ifp, *prevp = NULL; + + if (pthread_mutex_lock(&ifs_lock) != 0) + return; + for (ifp = ifs_head; ifp != NULL; ifp = ifp->if_next) { + if (strcmp(ifname, ifp->if_name) == 0) { + if (prevp == NULL) + ifs_head = ifp->if_next; + else + prevp->if_next = ifp->if_next; + if (ifp == ifs_wired_last) { + if ((ifs_wired_last = prevp) == NULL) + ifs_wired = NULL; + } else if (ifp == ifs_wired) { + ifs_wired = ifp->if_next; + } + if (ifp == ifs_wireless_last) { + if (prevp != NULL && + prevp->if_type != IF_WIRELESS) + prevp = NULL; + if ((ifs_wireless_last = prevp) == NULL) + ifs_wireless = NULL; + } else if (ifp == ifs_wireless) { + ifs_wireless = ifp->if_next; + } + break; + } + prevp = ifp; + } + (void) pthread_mutex_unlock(&ifs_lock); + + remove_wireless_if(ifname); + + if (ifp != NULL && ifp->if_thr != 0) { + (void) pthread_cancel(ifp->if_thr); + (void) pthread_join(ifp->if_thr, NULL); + } + free(ifp); +} + +/* * Searches for an interface and returns the interface structure if found. - * Returns NULL otherwise. errno is set upon error exit. + * Returns NULL otherwise. The caller must either be holding ifs_lock, or be + * in the main thread. */ struct interface * get_interface(const char *name) { - struct interface *i; + struct interface *ifp; if (name == NULL) return (NULL); - for (i = ifs_head; i != NULL; i = i->if_next) { - if (strcmp(name, i->if_name) == 0) { - return (i); - } + for (ifp = ifs_head; ifp != NULL; ifp = ifp->if_next) { + if (strcmp(name, ifp->if_name) == 0) + break; } - - return (NULL); + return (ifp); } /* - * Checks interface flags and, if IFF_DHCPRUNNING and !IFF_UP, does - * an 'ifconfig ifname dhcp drop'. + * Check to see whether the interface could be started. If the IFF_RUNNING + * flag is set, then we're in good shape. Otherwise, wireless interfaces are + * special: we'll attempt to connect to an Access Point as part of the start-up + * procedure, and IFF_RUNNING won't be present until that's done, so assume + * that all wireless interfaces are good to go. This is just an optimization; + * we could start everything. */ -void -check_drop_dhcp(struct interface *ifp) +static boolean_t +is_startable(struct interface *ifp) { - uint64_t flags = get_ifflags(ifp->if_name, ifp->if_family); - - if (!(flags & IFF_DHCPRUNNING) || (flags & IFF_UP)) { - dprintf("check_drop_dhcp: nothing to do (flags=0x%llx)", flags); - return; - } - - (void) start_child(IFCONFIG, ifp->if_name, "dhcp", "drop", NULL); + ifp->if_flags = get_ifflags(ifp->if_name, ifp->if_family); + if (ifp->if_flags & IFF_RUNNING) + return (B_TRUE); + return (ifp->if_type == IF_WIRELESS); } /* @@ -860,94 +904,63 @@ check_drop_dhcp(struct interface *ifp) * * For the real code, we should pass back the network information * gathered. Note that the state engine will then use the llp to - * determine which interface should be set up... + * determine which interface should be set up. + * + * ifs_lock is not held on entry. The caller will cancel this thread and wait + * for it to exit if the interface is to be deleted. */ static void * gather_interface_info(void *arg) { struct interface *i = arg; - llp_t *llp; - - assert(i != NULL); + int retv; dprintf("Start gathering info for %s", i->if_name); switch (i->if_type) { case IF_WIRELESS: - (void) scan_wireless_nets(i); + /* This generates EV_NEWAP when successful */ + retv = launch_wireless_scan(i->if_name); + if (retv != 0) + dprintf("didn't launch wireless scan: %s", + strerror(retv)); break; case IF_WIRED: - /* - * It should not happen as the llp list should be done when - * this function is called. But let the state engine decide - * what to do. - */ - if ((llp = llp_lookup(i->if_name)) == NULL) - break; - /* - * The following is to avoid locking up the state machine - * as it is currently the choke point. We start dhcp with - * a wait time of 0; later, if we see the link go down - * (IFF_RUNNING is cleared), we will drop the attempt. - */ - if (llp->llp_ipv4src == IPV4SRC_DHCP && is_plugged_in(i)) - start_dhcp(i); + if (llp_get_ipv4src(i->if_name) == IPV4SRC_DHCP) { + /* + * The following is to avoid locking up the state + * machine as it is currently the choke point. We + * start dhcp with a wait time of 0; later, if we see + * the link go down (IFF_RUNNING is cleared), we will + * drop the attempt. + */ + if (is_startable(i)) + start_dhcp(i); + } + (void) np_queue_add_event(EV_LINKUP, i->if_name); break; - default: - /* For other types, do not do anything. */ - return (NULL); } - gen_newif_event(i); - dprintf("Done gathering info for %s", i->if_name); + i->if_thr = 0; return (NULL); } -void -gen_newif_event(struct interface *i) -{ - struct np_event *e; - - e = calloc(1, sizeof (struct np_event)); - if (e == NULL) { - dprintf("gen_newif_event: calloc failed"); - return; - } - e->npe_name = strdup(i->if_name); - if (e->npe_name == NULL) { - dprintf("gen_newif_event: strdup failed"); - free(e); - return; - } - e->npe_type = EV_ROUTING; - - /* - * This event notifies the state machine that a new interface is - * (at least nominally) available to be brought up. When the state - * machine processes the event, it will look at the entire list of - * interfaces and corresponding LLPs, and make a determination about - * the best available LLP under current conditions. - */ - np_queue_add_event(e); - dprintf("gen_newif_event: generated event for if %s", i->if_name); -} - /* * Caller uses this function to walk through the whole interface list. * For each interface, the caller provided walker is called with - * the interface and arg as parameters. - * - * XXX There is no lock held right now for accessing the interface - * list. We probably need that in future. + * the interface and arg as parameters, and with the ifs_lock held. */ void walk_interface(void (*walker)(struct interface *, void *), void *arg) { - struct interface *i; + struct interface *ifp; - for (i = ifs_head; i != NULL; i = i->if_next) - walker(i, arg); + if (pthread_mutex_lock(&ifs_lock) != 0) + return; + for (ifp = ifs_head; ifp != NULL; ifp = ifp->if_next) + walker(ifp, arg); + (void) pthread_mutex_unlock(&ifs_lock); } static void @@ -1014,11 +1027,6 @@ initialize_interfaces(void) unsigned int wait_time = 1; boolean_t found_nonlo_if; - dprintf("initialize_interfaces: setting link_layer_profile(%p) to NULL", - (void *)link_layer_profile); - link_layer_profile = NULL; - upper_layer_profile[0] = '\0'; - /* * Bring down all interfaces bar lo0. */ @@ -1090,12 +1098,13 @@ initialize_interfaces(void) } /* - * Walker function used to start info gathering of each interface. + * Walker function used to start info gathering of each interface. Caller + * holds ifs_lock. */ void start_if_info_collect(struct interface *ifp, void *arg) { - pthread_t if_thr; + int retv; pthread_attr_t attr; /* @@ -1104,15 +1113,17 @@ start_if_info_collect(struct interface *ifp, void *arg) * event after we initialize interfaces before the routing thread * is launched. */ - if (arg != NULL && *(boolean_t *)arg) + if (arg != NULL) ifp->if_flags = get_ifflags(ifp->if_name, ifp->if_family); /* * Only if the cable of the wired interface is * plugged in, start gathering info from it. */ - if (!is_plugged_in(ifp)) + if (!is_startable(ifp)) { + dprintf("not gathering info on %s; not running", ifp->if_name); return; + } /* * This is a "fresh start" for the interface, so clear old DHCP flags. @@ -1121,12 +1132,14 @@ start_if_info_collect(struct interface *ifp, void *arg) (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - if (pthread_create(&if_thr, &attr, gather_interface_info, - (void *)ifp) != 0) { - syslog(LOG_ERR, "create interface gathering thread: %m"); + if ((retv = pthread_create(&ifp->if_thr, &attr, gather_interface_info, + ifp)) != 0) { + syslog(LOG_ERR, "create interface gathering thread: %s", + strerror(retv)); exit(EXIT_FAILURE); } else { - dprintf("interface info thread: %d", if_thr); + dprintf("interface info thread for %s: %d", ifp->if_name, + ifp->if_thr); } } @@ -1135,12 +1148,10 @@ start_if_info_collect(struct interface *ifp, void *arg) * If timer has expired, generate a timer event for the * interface. */ -/* ARGSUSED */ -void -check_interface_timer(struct interface *ifp, void *arg) +static void +iftimer(struct interface *ifp, void *arg) { - uint32_t now = *(uint32_t *)arg; - struct np_event *ev; + uint32_t now = (uint32_t)(uintptr_t)arg; if (ifp->if_timer_expire == 0) return; @@ -1152,27 +1163,20 @@ check_interface_timer(struct interface *ifp, void *arg) ifp->if_timer_expire = 0; - if ((ev = calloc(1, sizeof (*ev))) == NULL) { - dprintf("could not allocate timer event for %s; ignoring timer", - ifp->if_name); - return; - } - ev->npe_type = EV_TIMER; - ev->npe_name = strdup(ifp->if_name); - if (ev->npe_name == NULL) { - dprintf("could not strdup name for timer event on %s; ignoring", - ifp->if_name); - free(ev); - return; - } - np_queue_add_event(ev); + (void) np_queue_add_event(EV_TIMER, ifp->if_name); } -enum interface_type +void +check_interface_timers(uint32_t now) +{ + walk_interface(iftimer, (void *)(uint32_t)now); +} + +libnwam_interface_type_t find_if_type(const char *name) { uint32_t media; - enum interface_type type; + libnwam_interface_type_t type; if (name == NULL) { dprintf("find_if_type: no ifname; returning IF_UNKNOWN"); @@ -1199,7 +1203,7 @@ find_if_type(const char *name) } const char * -if_type_str(enum interface_type type) +if_type_str(libnwam_interface_type_t type) { switch (type) { case IF_WIRED: @@ -1213,3 +1217,170 @@ if_type_str(enum interface_type type) return ("unknown type"); } } + +/* + * This is called by the routing socket thread to update the IPv4 address on an + * interface. The routing socket thread cannot touch the interface structures + * without holding the global lock, because interface structures can be + * deleted. + */ +void +update_interface_v4_address(const char *ifname, in_addr_t addr) +{ + struct in_addr in; + struct interface *ifp; + + if (pthread_mutex_lock(&ifs_lock) == 0) { + if ((ifp = get_interface(ifname)) == NULL) { + dprintf("no interface struct for %s; ignoring message", + ifname); + } else if (ifp->if_ipv4addr != addr) { + ifp->if_ipv4addr = addr; + in.s_addr = addr; + dprintf("cached new address %s for link %s", + inet_ntoa(in), ifname); + (void) np_queue_add_event(EV_NEWADDR, ifname); + } else { + dprintf("same address on %s; no event", ifname); + } + (void) pthread_mutex_unlock(&ifs_lock); + } +} + +/* + * This is called by the routing socket thread to update the flags on a given + * IPv4 interface. If the interface has changed state, then we launch an event + * or a thread as appropriate. + */ +void +update_interface_flags(const char *ifname, int newflags) +{ + struct interface *ifp; + int oldflags; + + if (pthread_mutex_lock(&ifs_lock) == 0) { + if ((ifp = get_interface(ifname)) == NULL) { + dprintf("no interface data for %s; ignoring message", + ifname); + } else { + /* + * Check for toggling of the IFF_RUNNING flag. + * + * On any change in the flag value, we turn off the + * DHCP flags; the change in the RUNNING state + * indicates a "fresh start" for the interface, so we + * should try dhcp again. + * + * If the interface was not plugged in and now it is, + * start info collection. + * + * If it was plugged in and now it is unplugged, + * generate an event. + */ + oldflags = ifp->if_flags; + if ((oldflags & IFF_RUNNING) != + (newflags & IFF_RUNNING)) { + ifp->if_lflags &= ~IF_DHCPFLAGS; + } + if (!(newflags & IFF_DHCPRUNNING)) + ifp->if_lflags &= ~IF_DHCPFLAGS; + ifp->if_flags = newflags; + if (!(oldflags & IFF_RUNNING) && + (newflags & IFF_RUNNING)) { + start_if_info_collect(ifp, NULL); + } else if ((oldflags & IFF_RUNNING) && + !(newflags & IFF_RUNNING)) { + (void) np_queue_add_event(EV_LINKDROP, ifname); + } else { + dprintf("no-event flag change on %s: %x -> %x", + ifp->if_name, oldflags, newflags); + } + } + (void) pthread_mutex_unlock(&ifs_lock); + } +} + +/* + * Called only in main thread. Note that wireless interfaces are considered + * "ok" even if the IFF_RUNNING bit isn't set. This is because AP attach + * occurs as part of the LLP selection process. + */ +boolean_t +is_interface_ok(const char *ifname) +{ + boolean_t is_ok = B_FALSE; + struct interface *ifp; + + if ((ifp = get_interface(ifname)) != NULL && + !(ifp->if_lflags & IF_DHCPFAILED) && is_startable(ifp)) + is_ok = B_TRUE; + return (is_ok); +} + +/* + * Return the interface type for a given interface name. + */ +libnwam_interface_type_t +get_if_type(const char *ifname) +{ + libnwam_interface_type_t ift = IF_UNKNOWN; + struct interface *ifp; + + if (pthread_mutex_lock(&ifs_lock) == 0) { + if ((ifp = get_interface(ifname)) != NULL) + ift = ifp->if_type; + (void) pthread_mutex_unlock(&ifs_lock); + } + return (ift); +} + +/* + * Get the interface state for storing in llp_t. This is used only with the + * doors interface to return status flags. + */ +void +get_interface_state(const char *ifname, boolean_t *dhcp_failed, + boolean_t *link_up) +{ + struct interface *ifp; + + *dhcp_failed = *link_up = B_FALSE; + if (pthread_mutex_lock(&ifs_lock) == 0) { + if ((ifp = get_interface(ifname)) != NULL) { + if (ifp->if_lflags & IF_DHCPFAILED) + *dhcp_failed = B_TRUE; + if (ifp->if_flags & IFF_UP) + *link_up = B_TRUE; + } + (void) pthread_mutex_unlock(&ifs_lock); + } +} + +/* + * Dump out the interface state via debug messages. + */ +void +print_interface_status(void) +{ + struct interface *ifp; + struct in_addr ina; + + if (pthread_mutex_lock(&ifs_lock) == 0) { + if (upper_layer_profile[0] != '\0') + dprintf("upper layer profile %s active", + upper_layer_profile); + else + dprintf("no upper layer profile active"); + for (ifp = ifs_head; ifp != NULL; ifp = ifp->if_next) { + ina.s_addr = ifp->if_ipv4addr; + dprintf("I/F %s af %d flags %llX lflags %X type %d " + "expire %u v6 %son-link up %sattempted addr %s", + ifp->if_name, ifp->if_family, ifp->if_flags, + ifp->if_lflags, ifp->if_type, ifp->if_timer_expire, + ifp->if_v6onlink ? "" : "not ", + ifp->if_up_attempted ? "" : "not ", + inet_ntoa(ina)); + } + (void) pthread_mutex_unlock(&ifs_lock); + } +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/llp.c b/usr/src/cmd/cmd-inet/lib/nwamd/llp.c index fee7bfcb24..737aea412b 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/llp.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/llp.c @@ -20,12 +20,10 @@ */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * This file contains the routines that manipulate Link Layer Profiles * (aka LLPs) and various support functions. This includes parsing and @@ -52,13 +50,25 @@ * administrator creates the file with wireless entries before wired, * that priority order will be respected. * - * The llp list (pointed to by the global llp_head) is protected by - * the global llp_lock, which should be pthread_mutex_lock()'d before - * reading or writing the list. + * The llp list is protected by the global llp_lock, which must be + * pthread_mutex_lock()'d before reading or writing the list. Only the main + * thread can write to the list; this allows the main thread to deal with read + * access to structure pointers without holding locks and without the + * complexity of reference counts. All other threads must hold llp_lock for + * the duration of any read access to the data, and must not deal directly in + * structure pointers. (A thread may also hold machine_lock to block the main + * thread entirely in order to manipulate the data; such use is isolated to the + * door interface.) + * + * Functions in this file have comments noting where the main thread alone is + * the caller. These functions do not need to acquire the lock. + * + * If you hold both ifs_lock and llp_lock, you must take ifs_lock first. */ #include <stdio.h> #include <stdlib.h> +#include <unistd.h> #include <limits.h> #include <strings.h> #include <string.h> @@ -82,17 +92,26 @@ /* Lock to protect the llp list. */ static pthread_mutex_t llp_lock = PTHREAD_MUTEX_INITIALIZER; -llp_t *llp_head = NULL; -llp_t *link_layer_profile = NULL; +/* Accessed only from main thread or with llp_lock held */ +llp_t *link_layer_profile; + +static struct qelem llp_list; +static llp_t *locked_llp; /* * Global variable to hold the highest priority. Need to use the atomic * integer arithmetic functions to update it. */ -static uint32_t llp_highest_pri = 0; +static uint32_t llp_highest_pri; static void print_llp_list(void); +void +initialize_llp(void) +{ + llp_list.q_forw = llp_list.q_back = &llp_list; +} + char * llp_prnm(llp_t *llp) { @@ -104,27 +123,51 @@ llp_prnm(llp_t *llp) return (llp->llp_lname); } +/* + * This function removes a given LLP from the global list and discards it. + * Called only from the main thread. + */ +void +llp_delete(llp_t *llp) +{ + if (pthread_mutex_lock(&llp_lock) == 0) { + if (llp == locked_llp) + locked_llp = NULL; + assert(llp != link_layer_profile); + remque(&llp->llp_links); + (void) pthread_mutex_unlock(&llp_lock); + free(llp->llp_ipv6addrstr); + free(llp->llp_ipv4addrstr); + free(llp); + } +} + static void -llp_list_free(llp_t *head) +llp_list_free(void) { - llp_t **llpp; - llp_t *llpfree; + int retv; + llp_t *llp; - if (pthread_mutex_lock(&llp_lock) != 0) { + locked_llp = NULL; + if ((retv = pthread_mutex_lock(&llp_lock)) != 0) { /* Something very serious is wrong... */ - syslog(LOG_ERR, "llp_list_free: cannot lock mutex: %m"); + syslog(LOG_ERR, "llp_list_free: cannot lock mutex: %s", + strerror(retv)); return; } - llpp = &head; - while (*llpp != NULL) { - llpfree = *llpp; - *llpp = llpfree->llp_next; - free(llpfree->llp_ipv4addrstr); - free(llpfree); + while (llp_list.q_forw != &llp_list) { + llp = (llp_t *)llp_list.q_forw; + remque(&llp->llp_links); + free(llp->llp_ipv6addrstr); + free(llp->llp_ipv4addrstr); + free(llp); } (void) pthread_mutex_unlock(&llp_lock); } +/* + * Called either from main thread or with llp_lock held. + */ llp_t * llp_lookup(const char *link) { @@ -133,17 +176,13 @@ llp_lookup(const char *link) if (link == NULL) return (NULL); - /* The name may change. Better hold the lock. */ - if (pthread_mutex_lock(&llp_lock) != 0) { - /* Something very serious is wrong... */ - syslog(LOG_ERR, "llp_lookup: cannot lock mutex: %m"); - return (NULL); - } - for (llp = llp_head; llp != NULL; llp = llp->llp_next) { + for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list; + llp = (llp_t *)llp->llp_links.q_forw) { if (strcmp(link, llp->llp_lname) == 0) break; } - (void) pthread_mutex_unlock(&llp_lock); + if (llp == (llp_t *)&llp_list) + llp = NULL; return (llp); } @@ -158,56 +197,57 @@ llp_lookup(const char *link) llp_t * llp_high_pri(llp_t *a, llp_t *b) { - if (a == NULL) + if (a == NULL || a->llp_links.q_forw == NULL) + return (b); + else if (b == NULL || b->llp_links.q_forw == NULL) + return (a); + + /* Check for locked LLP selection for user interface */ + if (a == locked_llp) + return (a); + else if (b == locked_llp) + return (b); + + if (a->llp_failed && !b->llp_failed) return (b); - else if (b == NULL) + if (!a->llp_failed && b->llp_failed) return (a); /* * Higher priority is represented by a lower number. This seems a * bit backwards, but for now it makes assigning priorities very easy. - * - * We shouldn't have ties right now, but just in case, tie goes to a. */ return ((a->llp_pri <= b->llp_pri) ? a : b); } /* - * Chooses the highest priority link that corresponds to an - * available interface. + * Chooses the highest priority link that corresponds to an available + * interface. Called only in the main thread. */ llp_t * llp_best_avail(void) { - llp_t *p, *rtnllp = NULL; - struct interface *ifp; + llp_t *llp, *rtnllp; - /* The priority may change. Better hold the lock. */ - if (pthread_mutex_lock(&llp_lock) != 0) { - /* Something very serious is wrong... */ - syslog(LOG_ERR, "llp_best_avail: cannot lock mutex: %m"); - return (NULL); - } - for (p = llp_head; p != NULL; p = p->llp_next) { - ifp = get_interface(p->llp_lname); - if (ifp == NULL || !is_plugged_in(ifp) || - (ifp->if_lflags & IF_DHCPFAILED) != 0) - continue; - rtnllp = llp_high_pri(p, rtnllp); + if ((rtnllp = locked_llp) == NULL) { + for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list; + llp = (llp_t *)llp->llp_links.q_forw) { + if (is_interface_ok(llp->llp_lname)) + rtnllp = llp_high_pri(llp, rtnllp); + } } - (void) pthread_mutex_unlock(&llp_lock); return (rtnllp); } /* - * Returns B_TRUE if llp is successfully activated; - * B_FALSE if activation fails. + * Called only by the main thread. Note that this leaves link_layer_profile + * set to NULL only in the case of abject failure, and then leaves llp_failed + * set. */ -boolean_t +static void llp_activate(llp_t *llp) { - boolean_t rtn; char *host; /* * Choosing "dhcp" as a hostname is unsupported right now. @@ -216,40 +256,33 @@ llp_activate(llp_t *llp) */ char *dhcpstr = "dhcp"; - llp_deactivate(); + assert(link_layer_profile == NULL); host = (llp->llp_ipv4src == IPV4SRC_DHCP) ? dhcpstr : llp->llp_ipv4addrstr; - if (bringupinterface(llp->llp_lname, host, llp->llp_ipv6addrstr, + report_llp_selected(llp->llp_lname); + switch (bringupinterface(llp->llp_lname, host, llp->llp_ipv6addrstr, llp->llp_ipv6onlink)) { + case SUCCESS: + llp->llp_failed = B_FALSE; + llp->llp_waiting = B_FALSE; link_layer_profile = llp; dprintf("llp_activate: activated llp for %s", llp_prnm(llp)); - rtn = B_TRUE; - } else { - dprintf("llp_activate: failed to bringup %s", llp_prnm(llp)); + break; + case FAILURE: + llp->llp_failed = B_TRUE; + llp->llp_waiting = B_FALSE; + dprintf("llp_activate: failed to bring up %s", llp_prnm(llp)); + report_llp_unselected(llp->llp_lname, dcFailed); link_layer_profile = NULL; - rtn = B_FALSE; + break; + case WAITING: + llp->llp_failed = B_FALSE; + llp->llp_waiting = B_TRUE; + link_layer_profile = llp; + dprintf("llp_activate: waiting for %s", llp_prnm(llp)); } - - return (rtn); -} - -/* - * Deactivate the current active llp (link_layer_profile) - */ -void -llp_deactivate(void) -{ - if (link_layer_profile == NULL) - return; - - takedowninterface(link_layer_profile->llp_lname, B_TRUE, - link_layer_profile->llp_ipv6onlink); - - dprintf("llp_deactivate: setting link_layer_profile(%p) to NULL", - (void *)link_layer_profile); - link_layer_profile = NULL; } /* @@ -263,222 +296,120 @@ llp_deactivate(void) * If the new llp is the same as the currently active one, don't * do anything. * - * If the new llp is NULL, just take down the currently active one. + * Called only by the main thread. */ void -llp_swap(llp_t *newllp) +llp_swap(llp_t *newllp, libnwam_diag_cause_t cause) { - char *upifname; + int minpri; if (newllp == link_layer_profile) return; deactivate_upper_layer_profile(); - if (link_layer_profile == NULL) { - /* - * there shouldn't be anything else running; - * make sure that's the case! - */ - upifname = (newllp == NULL) ? NULL : newllp->llp_lname; - take_down_all_ifs(upifname); - } else { + if (link_layer_profile != NULL) { dprintf("taking down current link layer profile (%s)", llp_prnm(link_layer_profile)); - llp_deactivate(); + report_llp_unselected(link_layer_profile->llp_lname, cause); + link_layer_profile->llp_waiting = B_FALSE; + link_layer_profile = NULL; } - if (newllp != NULL) { + + /* + * Establish the new link layer profile. If we have trouble setting + * it, then try to get another. Note that llp_activate sets llp_failed + * on failure, so this loop is guaranteed to terminate. + */ + while (newllp != NULL) { dprintf("bringing up new link layer profile (%s)", llp_prnm(newllp)); - (void) llp_activate(newllp); + llp_activate(newllp); + newllp = NULL; + if (link_layer_profile == NULL && + (newllp = llp_best_avail()) != NULL && + newllp->llp_failed) + newllp = NULL; } -} - -/* - * - * ifp->if_family == AF_INET, addr_src == DHCP ==> addr == NULL - * ifp->if_family == AF_INET, addr_src == STATIC ==> addr non null sockaddr_in - * ifp->if_family == AF_INET6, ipv6onlink == FALSE ==> addr == NULL - * ifp->if_family == AF_INET6, ipv6onlink == TRUE, - * if addr non NULL then it is the textual representation of the address - * and prefix. - * - * The above set of conditions describe what the inputs to this fuction are - * expected to be. Given input which meets those conditions this functions - * then outputs a line of configuration describing the inputs. - * - * Note that it is assumed only one thread can call this function at - * any time. So there is no lock to protect the file writing. This - * is true as the only caller of this function should originate from - * llp_parse_config(), which is done at program initialization time. - */ -static void -add_if_file(FILE *fp, struct interface *ifp, ipv4src_t addr_src, - boolean_t ipv6onlink, void *addr) -{ - char addr_buf[INET6_ADDRSTRLEN]; - - switch (ifp->if_family) { - case AF_INET: - switch (addr_src) { - case IPV4SRC_STATIC: - /* This is not supposed to happen... */ - if (addr == NULL) { - (void) fprintf(fp, "%s\tdhcp\n", ifp->if_name); - break; - } - (void) inet_ntop(AF_INET, addr, addr_buf, - INET6_ADDRSTRLEN); - (void) fprintf(fp, "%s\tstatic\t%s\n", ifp->if_name, - addr_buf); - break; - case IPV4SRC_DHCP: - /* Default is DHCP for now. */ - default: - (void) fprintf(fp, "%s\tdhcp\n", ifp->if_name); - break; - } - break; - - case AF_INET6: - if (ipv6onlink) - (void) fprintf(fp, "%s\tipv6\n", ifp->if_name); - break; - default: - syslog(LOG_ERR, "interface %s of type %d?!", ifp->if_name, - ifp->if_family); - break; + /* + * Knock down all interfaces that are at a lower (higher-numbered) + * priority than the new one. If there isn't a new one, then leave + * everything as it is. + */ + if (link_layer_profile == NULL) { + minpri = -1; + if (locked_llp != NULL) + dprintf("taking down all but %s", llp_prnm(locked_llp)); + } else { + minpri = link_layer_profile->llp_pri; + dprintf("taking down remaining interfaces below priority %d", + minpri); + } + for (newllp = (llp_t *)llp_list.q_forw; newllp != (llp_t *)&llp_list; + newllp = (llp_t *)newllp->llp_links.q_forw) { + if (newllp == link_layer_profile) + continue; + if ((link_layer_profile != NULL && newllp->llp_pri > minpri) || + (locked_llp != NULL && newllp != locked_llp)) + takedowninterface(newllp->llp_lname, cause); + else + clear_cached_address(newllp->llp_lname); } } /* - * Walker function to pass to walk_interface() to add a default - * interface description to the LLPFILE. - * - * Regarding IF_TUN interfaces: see comments before find_and_add_llp() - * for an explanation of why we skip them. + * Create the named LLP with default settings. Called only in main thread. */ -static void -add_if_default(struct interface *ifp, void *arg) -{ - FILE *fp = (FILE *)arg; - - if (ifp->if_type != IF_TUN) - add_if_file(fp, ifp, IPV4SRC_DHCP, B_TRUE, NULL); -} - -/* Create the LLPFILE using info from the interface list. */ -static void -create_llp_file(void) +llp_t * +llp_add(const char *name) { - FILE *fp; - int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + int retv; + llp_t *llp; - /* Create the NWAM directory in case it does not exist. */ - if (mkdir(LLPDIR, dirmode) != 0) { - if (errno != EEXIST) { - syslog(LOG_ERR, "create NWAM directory: %m"); - return; - } + if ((llp = calloc(1, sizeof (llp_t))) == NULL) { + syslog(LOG_ERR, "cannot allocate LLP: %m"); + return (NULL); } - if ((fp = fopen(LLPFILE, "w")) == NULL) { - syslog(LOG_ERR, "create LLP config file: %m"); - return; + + if (strlcpy(llp->llp_lname, name, sizeof (llp->llp_lname)) >= + sizeof (llp->llp_lname)) { + syslog(LOG_ERR, "llp: link name '%s' too long; ignoring entry", + name); + free(llp); + return (NULL); } - syslog(LOG_INFO, "Creating %s", LLPFILE); - walk_interface(add_if_default, fp); - (void) fclose(fp); -} -/* - * Append an llp struct to the end of the llp list. - */ -static void -llp_list_append(llp_t *llp) -{ - llp_t **wpp = &llp_head; + llp->llp_fileorder = llp->llp_pri = + atomic_add_32_nv(&llp_highest_pri, 1); + llp->llp_ipv4src = IPV4SRC_DHCP; + llp->llp_type = find_if_type(llp->llp_lname); + llp->llp_ipv6onlink = B_TRUE; /* * should be a no-op, but for now, make sure we only * create llps for wired and wireless interfaces. */ - if (llp->llp_type != IF_WIRED && llp->llp_type != IF_WIRELESS) - return; + if (llp->llp_type != IF_WIRED && llp->llp_type != IF_WIRELESS) { + syslog(LOG_ERR, "llp: wrong type of interface for %s", name); + free(llp); + return (NULL); + } - if (pthread_mutex_lock(&llp_lock) != 0) { + if ((retv = pthread_mutex_lock(&llp_lock)) != 0) { /* Something very serious is wrong... */ - syslog(LOG_ERR, "llp_list_append: cannot lock mutex: %m"); - return; + syslog(LOG_ERR, "llp: cannot lock mutex: %s", strerror(retv)); + free(llp); + return (NULL); } - while (*wpp != NULL) - wpp = &(*wpp)->llp_next; - *wpp = llp; - llp->llp_next = NULL; + insque(&llp->llp_links, llp_list.q_back); (void) pthread_mutex_unlock(&llp_lock); -} - -/* - * Create a llp given the parameters and add it to the global list. - */ -static void -create_and_add_llp(const char *name, ipv4src_t ipv4src, const char *addrstr, - boolean_t ipv6onlink, const char *ipv6addrstr) -{ - llp_t *newllp; - int lnamelen; - - if ((newllp = llp_lookup(name)) != NULL) { - if (ipv6addrstr != NULL) { - newllp->llp_ipv6addrstr = strdup(ipv6addrstr); - if (newllp->llp_ipv6addrstr == NULL) { - syslog(LOG_ERR, "could not save ipv6 static " - "address for %s", name); - } - } - newllp->llp_ipv6onlink = ipv6onlink; - return; - } else if ((newllp = calloc(1, sizeof (llp_t))) == NULL) { - syslog(LOG_ERR, "calloc llp: %m"); - return; - } - - lnamelen = sizeof (newllp->llp_lname); - if (strlcpy(newllp->llp_lname, name, lnamelen) >= lnamelen) { - syslog(LOG_ERR, "llp: link name too long; ignoring entry"); - free(newllp); - return; - } - if (ipv4src == IPV4SRC_STATIC) { - if ((newllp->llp_ipv4addrstr = strdup(addrstr)) == NULL) { - syslog(LOG_ERR, "malloc ipaddrstr: %m"); - free(newllp); - return; - } - } else { - newllp->llp_ipv4addrstr = NULL; - } - newllp->llp_next = NULL; - newllp->llp_pri = atomic_add_32_nv(&llp_highest_pri, 1); - newllp->llp_ipv4src = ipv4src; - newllp->llp_type = find_if_type(newllp->llp_lname); - newllp->llp_ipv6onlink = ipv6onlink; - - if (ipv6onlink && ipv6addrstr != NULL) { - newllp->llp_ipv6addrstr = strdup(ipv6addrstr); - if (newllp->llp_ipv6addrstr == NULL) - syslog(LOG_WARNING, "could not store static address %s" - "on interface %s", ipv6addrstr, newllp->llp_lname); - } else { - newllp->llp_ipv6addrstr = NULL; - } - llp_list_append(newllp); - - dprintf("created llp for link %s, pri %d", newllp->llp_lname, - newllp->llp_pri); + dprintf("created llp for link %s, priority %d", llp->llp_lname, + llp->llp_pri); + return (llp); } /* @@ -491,39 +422,43 @@ create_and_add_llp(const char *name, ipv4src_t ipv4src, const char *addrstr, * as part of a higher-layer profile, for example). Thus, they * shouldn't be considered when looking at the llp list, so don't * add them here. + * + * ifs_lock is held when this function is called. Called only in main thread. */ static void find_and_add_llp(struct interface *ifp, void *arg) { - FILE *fp = (FILE *)arg; + FILE *fp = arg; + + if (ifp->if_type != IF_TUN && llp_lookup(ifp->if_name) == NULL) { + switch (ifp->if_family) { + case AF_INET: + (void) fprintf(fp, "%s\tdhcp\n", ifp->if_name); + break; - if (ifp->if_type != IF_TUN && (llp_lookup(ifp->if_name) == NULL)) { - dprintf("Adding %s to %s", ifp->if_name, LLPFILE); - add_if_file(fp, ifp, IPV4SRC_DHCP, B_TRUE, NULL); + case AF_INET6: + (void) fprintf(fp, "%s\tipv6\n", ifp->if_name); + break; + + default: + syslog(LOG_ERR, "interface %s family %d?!", + ifp->if_name, ifp->if_family); + return; + } + dprintf("Added %s to %s", ifp->if_name, LLPFILE); /* If we run out of memory, ignore this interface for now. */ - create_and_add_llp(ifp->if_name, IPV4SRC_DHCP, NULL, - B_TRUE, NULL); + (void) llp_add(ifp->if_name); } } -/* - * This is a very "slow" function. It uses walk_interface() to find - * out if any of the interface is missing from the LLPFILE. For the - * missing ones, add them to the LLPFILE. - */ -static void -add_missing_if_llp(FILE *fp) -{ - walk_interface(find_and_add_llp, fp); -} - static void print_llp_list(void) { llp_t *wp; dprintf("Walking llp list"); - for (wp = llp_head; wp != NULL; wp = wp->llp_next) + for (wp = (llp_t *)llp_list.q_forw; wp != (llp_t *)&llp_list; + wp = (llp_t *)wp->llp_links.q_forw) dprintf("==> %s", wp->llp_lname); } @@ -533,7 +468,7 @@ print_llp_list(void) * delimited fields. Each address family (IPv4, IPv6) is described on a * separate line. * The first field is a link name. - * The second field can be either static, dhcp, ipv6, or noipv6. + * The second field can be either static, dhcp, ipv6, noipv6, or priority. * If the second field is static then the next field is an ipv4 address which * can contain a prefix. Previous versions of this file could contain a * hostname in this field which is no longer supported. @@ -545,6 +480,10 @@ print_llp_list(void) * and possible prefix which are applied to the interface. * If the second field is noipv6 then no ipv6 interfaces will be put on that * link. + * If the second field is priority, then the next field is an integer + * specifying the link priority. + * + * Called only in main thread. */ void llp_parse_config(void) @@ -553,50 +492,35 @@ llp_parse_config(void) static const char DHCP[] = "dhcp"; static const char IPV6[] = "ipv6"; static const char NOIPV6[] = "noipv6"; + static const char PRIORITY[] = "priority"; FILE *fp; char line[LINE_MAX]; - char *cp, *lasts, *lstr, *srcstr, *addrstr, *v6addrstr; + char *cp, *lasts, *lstr, *srcstr, *addrstr; int lnum; - ipv4src_t ipv4src; - boolean_t ipv6onlink; + llp_t *llp; + + /* Create the NWAM directory in case it does not exist. */ + if (mkdir(LLPDIRNAME, LLPDIRMODE) != 0 && + errno != EEXIST) { + syslog(LOG_ERR, "could not create %s: %m", LLPDIRNAME); + return; + } fp = fopen(LLPFILE, "r+"); if (fp == NULL) { if (errno != ENOENT) { - /* - * XXX See comment before create_llp_file() re - * better error handling. - */ syslog(LOG_ERR, "open LLP config file: %m"); return; } - - /* - * If there is none, we should create one instead. - * For now, we will use the order of the interface list - * for the priority. We should have a priority field - * in the llp file eventually... - */ - create_llp_file(); - - /* Now we can try to reopen the file for processing. */ - fp = fopen(LLPFILE, "r+"); - if (fp == NULL) { - syslog(LOG_ERR, "2nd open LLP config file: %m"); + if ((fp = fopen(LLPFILE, "w+")) == NULL) { + syslog(LOG_ERR, "create LLP config file: %m"); return; } } - if (llp_head != NULL) - llp_list_free(llp_head); - llp_head = NULL; + llp_list_free(); for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) { - ipv4src = IPV4SRC_DHCP; - ipv6onlink = B_FALSE; - addrstr = NULL; - v6addrstr = NULL; - if (line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = '\0'; @@ -615,51 +539,474 @@ llp_parse_config(void) "ignoring entry", lnum); continue; } + + if ((llp = llp_lookup(lstr)) == NULL && + (llp = llp_add(lstr)) == NULL) { + syslog(LOG_ERR, "llp:%d: cannot add entry", lnum); + continue; + } + if (strcasecmp(srcstr, STATICSTR) == 0) { if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL || atoi(addrstr) == 0) { /* crude check for number */ syslog(LOG_ERR, "llp:%d: missing ipaddr " - "for static config; ignoring entry", + "for static config", lnum); + } else if ((addrstr = strdup(addrstr)) == NULL) { + syslog(LOG_ERR, "llp:%d: cannot save address", lnum); - continue; + } else { + free(llp->llp_ipv4addrstr); + llp->llp_ipv4src = IPV4SRC_STATIC; + llp->llp_ipv4addrstr = addrstr; } - ipv4src = IPV4SRC_STATIC; + } else if (strcasecmp(srcstr, DHCP) == 0) { - ipv4src = IPV4SRC_DHCP; + llp->llp_ipv4src = IPV4SRC_DHCP; + } else if (strcasecmp(srcstr, IPV6) == 0) { - ipv6onlink = B_TRUE; - if ((addrstr = strtok_r(NULL, " \t", &lasts)) != NULL) { - v6addrstr = strdup(addrstr); - if (v6addrstr == NULL) { - syslog(LOG_ERR, "could not store v6 " - "static address %s for %s", - v6addrstr, lstr); - } + llp->llp_ipv6onlink = B_TRUE; + if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) { + (void) 0; + } else if ((addrstr = strdup(addrstr)) == NULL) { + syslog(LOG_ERR, "llp:%d: cannot save address", + lnum); } else { - v6addrstr = NULL; + free(llp->llp_ipv6addrstr); + llp->llp_ipv6addrstr = addrstr; } + } else if (strcasecmp(srcstr, NOIPV6) == 0) { - ipv6onlink = B_FALSE; + llp->llp_ipv6onlink = B_FALSE; + + } else if (strcasecmp(srcstr, PRIORITY) == 0) { + if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) { + syslog(LOG_ERR, + "llp:%d: missing priority value", lnum); + } else { + llp->llp_pri = atoi(addrstr); + } + } else { - syslog(LOG_ERR, "llp:%d: unrecognized " - "field; ignoring entry", lnum); - continue; + syslog(LOG_ERR, "llp:%d: unrecognized field '%s'", lnum, + srcstr); } - - create_and_add_llp(lstr, ipv4src, addrstr, ipv6onlink, - v6addrstr); } /* * So we have read in the llp file, is there an interface which * it does not describe? If yes, we'd better add it to the - * file for future reference. Again, since we don't have a - * priority field yet, we will add the interface in the order - * in the interface list. + * file for future reference. */ - add_missing_if_llp(fp); + walk_interface(find_and_add_llp, fp); (void) fclose(fp); print_llp_list(); } + +/* + * Called only from the main thread. + */ +void +llp_add_file(const llp_t *llp) +{ + FILE *fp; + + if ((fp = fopen(LLPFILE, "a")) == NULL) + return; + (void) fprintf(fp, "%s\tdhcp\n", llp->llp_lname); + (void) fclose(fp); +} + +/* + * This function rewrites the LLP configuration file entry for a given + * interface and keyword. If the keyword is present, then it is updated if + * removeonly is B_FALSE, otherwise it's removed. If the keyword is not + * present, then it is added immediately after the last entry for that + * interface if removeonly is B_FALSE, otherwise no action is taken. User + * comments are preserved. + * + * To preserve file integrity, this is called only from the main thread. + */ +static void +llp_update_config(const char *ifname, const char *keyword, const char *optval, + boolean_t removeonly) +{ + FILE *fpin, *fpout; + char line[LINE_MAX]; + char *cp, *lstr, *keystr, *valstr, *lasts; + boolean_t matched_if, copying; + long match_pos; + + if ((fpin = fopen(LLPFILE, "r")) == NULL) + return; + if ((fpout = fopen(LLPFILETMP, "w")) == NULL) { + syslog(LOG_ERR, "create LLP temporary config file: %m"); + (void) fclose(fpin); + return; + } + matched_if = copying = B_FALSE; +restart: + while (fgets(line, sizeof (line), fpin) != NULL) { + cp = line + strlen(line) - 1; + if (cp >= line && *cp == '\n') + *cp = '\0'; + + cp = line; + while (isspace(*cp)) + cp++; + + lstr = NULL; + if (copying || *cp == '#' || + (lstr = strtok_r(cp, " \t", &lasts)) == NULL || + strcmp(lstr, ifname) != 0) { + if (!matched_if || copying) { + /* + * It's ugly to write through the pointer + * returned as the third argument of strtok_r, + * but doing so saves a data copy. + */ + if (lstr != NULL && lasts != NULL) + lasts[-1] = '\t'; + (void) fprintf(fpout, "%s\n", line); + } + continue; + } + + if (lasts != NULL) + lasts[-1] = '\t'; + + /* + * If we've found the keyword, then process removal or update + * of the value. + */ + if ((keystr = strtok_r(NULL, " \t", &lasts)) != NULL && + strcmp(keystr, keyword) == 0) { + matched_if = copying = B_TRUE; + if (removeonly) + continue; + valstr = strtok_r(NULL, " \t", &lasts); + if ((valstr == NULL && optval == NULL) || + (valstr != NULL && optval != NULL && + strcmp(valstr, optval) == 0)) { + /* Value identical; abort update */ + goto no_change; + } + if (optval == NULL) { + (void) fprintf(fpout, "%s\t%s\n", ifname, + keyword); + } else { + (void) fprintf(fpout, "%s\t%s %s\n", ifname, + keyword, optval); + } + continue; + } + + /* Otherwise, record the last possible insertion point */ + matched_if = B_TRUE; + match_pos = ftell(fpin); + if (lasts != NULL) + lasts[-1] = '\t'; + (void) fprintf(fpout, "%s\n", line); + } + if (!copying) { + /* keyword not encountered; we're done if deleting */ + if (removeonly) + goto no_change; + /* need to add keyword and value */ + if (optval == NULL) { + (void) fprintf(fpout, "%s\t%s\n", ifname, keyword); + } else { + (void) fprintf(fpout, "%s\t%s %s\n", ifname, keyword, + optval); + } + /* copy the rest of the file */ + (void) fseek(fpin, match_pos, SEEK_SET); + copying = B_TRUE; + goto restart; + } + (void) fclose(fpin); + (void) fclose(fpout); + if (rename(LLPFILETMP, LLPFILE) != 0) { + syslog(LOG_ERR, "rename LLP temporary config file: %m"); + (void) unlink(LLPFILETMP); + } + return; + +no_change: + (void) fclose(fpin); + (void) fclose(fpout); + (void) unlink(LLPFILETMP); +} + +/* + * This is called back from the main thread by the state machine. + */ +void +llp_write_changed_priority(llp_t *llp) +{ + if (llp->llp_pri == llp->llp_fileorder) { + llp_update_config(llp->llp_lname, "priority", NULL, B_TRUE); + } else { + char prival[32]; + + (void) snprintf(prival, sizeof (prival), "%d", llp->llp_pri); + llp_update_config(llp->llp_lname, "priority", prival, B_FALSE); + } +} + +/* + * Called by the door interface: set LLP priority and schedule an LLP update if + * this interface has changed. + */ +int +set_llp_priority(const char *ifname, int prio) +{ + llp_t *llp; + int retv; + + if (prio < 0) + return (EINVAL); + + if ((retv = pthread_mutex_lock(&llp_lock)) != 0) + return (retv); + if ((llp = llp_lookup(ifname)) != NULL) { + llp->llp_failed = B_FALSE; + if (llp->llp_pri != prio) { + llp->llp_pri = prio; + (void) np_queue_add_event(EV_USER, ifname); + } + retv = 0; + } else { + retv = ENXIO; + } + (void) pthread_mutex_unlock(&llp_lock); + return (retv); +} + +/* + * Called by the door interface: set a locked LLP and schedule an LLP update if + * the locked LLP has changed. + */ +int +set_locked_llp(const char *ifname) +{ + llp_t *llp; + int retv; + + if ((retv = pthread_mutex_lock(&llp_lock)) != 0) + return (retv); + if (ifname[0] == '\0') { + if (locked_llp != NULL) { + ifname = locked_llp->llp_lname; + locked_llp = NULL; + (void) np_queue_add_event(EV_USER, ifname); + } + } else if ((llp = llp_lookup(ifname)) != NULL) { + locked_llp = llp; + if (llp != link_layer_profile) + (void) np_queue_add_event(EV_USER, ifname); + } else { + retv = ENXIO; + } + (void) pthread_mutex_unlock(&llp_lock); + return (retv); +} + +/* Copy string to pre-allocated buffer. */ +static void +strinsert(char **dest, const char *src, char **buf) +{ + if (*dest != NULL) { + *dest = strcpy(*buf, src); + *buf += strlen(src) + 1; + } +} + +/* + * Sample the list of LLPs and copy to a single buffer for return through the + * door interface. + */ +llp_t * +get_llp_list(size_t *lsize, uint_t *countp, char *selected, char *locked) +{ + llp_t *llplist, *llpl, *llp; + char *strptr; + uint_t nllp; + size_t strspace; + int retv; + + *lsize = 0; + if ((retv = pthread_mutex_lock(&llp_lock)) != 0) { + errno = retv; + return (NULL); + } + (void) strlcpy(selected, link_layer_profile == NULL ? "" : + link_layer_profile->llp_lname, LIFNAMSIZ); + (void) strlcpy(locked, locked_llp == NULL ? "" : + locked_llp->llp_lname, LIFNAMSIZ); + nllp = 0; + strspace = 0; + for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list; + llp = (llp_t *)llp->llp_links.q_forw) { + nllp++; + if (llp->llp_ipv4addrstr != NULL) + strspace += strlen(llp->llp_ipv4addrstr) + 1; + if (llp->llp_ipv6addrstr != NULL) + strspace += strlen(llp->llp_ipv6addrstr) + 1; + } + *countp = nllp; + /* Note that malloc doesn't guarantee a NULL return for zero count */ + llplist = nllp == 0 ? NULL : + malloc(sizeof (*llplist) * nllp + strspace); + if (llplist != NULL) { + *lsize = sizeof (*llplist) * nllp + strspace; + llpl = llplist; + strptr = (char *)(llplist + nllp); + for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list; + llp = (llp_t *)llp->llp_links.q_forw) { + *llpl = *llp; + strinsert(&llpl->llp_ipv4addrstr, llp->llp_ipv4addrstr, + &strptr); + strinsert(&llpl->llp_ipv6addrstr, llp->llp_ipv6addrstr, + &strptr); + llpl++; + } + } + (void) pthread_mutex_unlock(&llp_lock); + + /* Add in the special door-only state flags */ + llpl = llplist; + while (nllp-- > 0) { + get_interface_state(llpl->llp_lname, &llpl->llp_dhcp_failed, + &llpl->llp_link_up); + if (llpl->llp_type == IF_WIRELESS) { + get_wireless_state(llpl->llp_lname, + &llpl->llp_need_wlan, &llpl->llp_need_key); + } + llpl++; + } + return (llplist); +} + +/* + * This is called for the special case when there are outstanding requests sent + * to the user interface, and the user interface disappears. We handle this + * case by re-running bringupinterface() without deselecting. That function + * will call the wireless and DHCP-related parts again, and they should proceed + * in automatic mode, because the UI is now gone. + * + * Called only by the main thread or by a thread holding machine_lock. + */ +void +llp_reselect(void) +{ + llp_t *llp; + const char *host; + + /* + * If there's no active profile, or if the active profile isn't waiting + * on the UI, then just return; nothing to do. + */ + if ((llp = link_layer_profile) == NULL || !llp->llp_waiting) + return; + + host = (llp->llp_ipv4src == IPV4SRC_DHCP) ? "dhcp" : + llp->llp_ipv4addrstr; + + dprintf("llp_reselect: bringing up %s", llp_prnm(llp)); + switch (bringupinterface(llp->llp_lname, host, llp->llp_ipv6addrstr, + llp->llp_ipv6onlink)) { + case SUCCESS: + llp->llp_failed = B_FALSE; + llp->llp_waiting = B_FALSE; + dprintf("llp_reselect: activated llp for %s", llp_prnm(llp)); + break; + case FAILURE: + llp->llp_failed = B_TRUE; + llp->llp_waiting = B_FALSE; + dprintf("llp_reselect: failed to bring up %s", llp_prnm(llp)); + report_llp_unselected(llp->llp_lname, dcFailed); + link_layer_profile = NULL; + break; + case WAITING: + llp->llp_failed = B_FALSE; + dprintf("llp_reselect: waiting for %s", llp_prnm(llp)); + } +} + +/* + * This is used by the wireless module to check on the selected LLP. We don't + * do periodic rescans if a wireless interface is current and if its connection + * state is good. + */ +void +llp_get_name_and_type(char *ifname, size_t ifnlen, + libnwam_interface_type_t *iftype) +{ + *ifname = '\0'; + *iftype = IF_UNKNOWN; + + if (pthread_mutex_lock(&llp_lock) == 0) { + if (link_layer_profile != NULL) { + (void) strlcpy(ifname, link_layer_profile->llp_lname, + ifnlen); + *iftype = link_layer_profile->llp_type; + } + (void) pthread_mutex_unlock(&llp_lock); + } +} + +/* + * This is called by the interface.c module to check if an interface needs to + * run DHCP. It's intentionally called without ifs_lock held. + */ +libnwam_ipv4src_t +llp_get_ipv4src(const char *ifname) +{ + libnwam_ipv4src_t src = IPV4SRC_DHCP; + llp_t *llp; + + if (pthread_mutex_lock(&llp_lock) == 0) { + if ((llp = llp_lookup(ifname)) != NULL) + src = llp->llp_ipv4src; + (void) pthread_mutex_unlock(&llp_lock); + } + return (src); +} + +/* + * Dump out the LLP state via debug messages. + */ +void +print_llp_status(void) +{ + llp_t *llp; + + if (pthread_mutex_lock(&llp_lock) == 0) { + if (link_layer_profile == NULL) + dprintf("no LLP selected"); + else + dprintf("LLP %s selected", + link_layer_profile->llp_lname); + if (locked_llp == NULL) + dprintf("no LLP locked"); + else + dprintf("LLP %s locked", locked_llp->llp_lname); + for (llp = (llp_t *)llp_list.q_forw; + llp != (llp_t *)&llp_list; + llp = (llp_t *)llp->llp_links.q_forw) { + dprintf("LLP %s pri %d file order %d type %d " + "%sfailed %swaiting src %d v4addr %s v6addr %s " + "v6 %son-link", + llp->llp_lname, llp->llp_pri, llp->llp_fileorder, + (int)llp->llp_type, llp->llp_failed ? "" : "not ", + llp->llp_waiting ? "" : "not ", + (int)llp->llp_ipv4src, + STRING(llp->llp_ipv4addrstr), + STRING(llp->llp_ipv6addrstr), + llp->llp_ipv6onlink ? "not " : ""); + } + (void) pthread_mutex_unlock(&llp_lock); + } +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/main.c b/usr/src/cmd/cmd-inet/lib/nwamd/main.c index f065de988c..0b78b0aa2e 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/main.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/main.c @@ -20,12 +20,10 @@ */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * nwamd - NetWork Auto-Magic Daemon */ @@ -62,6 +60,7 @@ boolean_t fg = B_FALSE; boolean_t shutting_down; sigset_t original_sigmask; char zonename[ZONENAME_MAX]; +pthread_mutex_t machine_lock = PTHREAD_MUTEX_INITIALIZER; /* * nwamd @@ -161,11 +160,14 @@ lookup_daemon_properties(void) { boolean_t debug_set; uint64_t scan_interval; + uint64_t idle_time; if (lookup_boolean_property(OUR_PG, "debug", &debug_set) == 0) debug = debug_set; if (lookup_count_property(OUR_PG, "scan_interval", &scan_interval) == 0) wlan_scan_interval = scan_interval; + if (lookup_count_property(OUR_PG, "idle_time", &idle_time) == 0) + door_idle_time = idle_time; dprintf("Read daemon configuration properties."); } @@ -173,14 +175,13 @@ lookup_daemon_properties(void) static void * sighandler(void *arg) { - struct np_event *ev; sigset_t sigset; int sig; uint32_t now; (void) sigfillset(&sigset); - for (;;) { + while (!shutting_down) { sig = sigwait(&sigset); dprintf("signal %d caught", sig); switch (sig) { @@ -192,7 +193,8 @@ sighandler(void *arg) */ timer_expire = TIMER_INFINITY; now = NSEC_TO_SEC(gethrtime()); - walk_interface(check_interface_timer, &now); + check_interface_timers(now); + check_door_life(now); break; case SIGHUP: /* @@ -200,25 +202,27 @@ sighandler(void *arg) */ lookup_daemon_properties(); break; + case SIGINT: + /* + * Undocumented "print debug status" signal. + */ + print_llp_status(); + print_interface_status(); + print_wireless_status(); + break; default: syslog(LOG_NOTICE, "%s received, shutting down", strsignal(sig)); shutting_down = B_TRUE; - if ((ev = malloc(sizeof (*ev))) == NULL) { + if (!np_queue_add_event(EV_SHUTDOWN, NULL)) { dprintf("could not allocate shutdown event"); cleanup(); exit(EXIT_FAILURE); } - ev->npe_type = EV_SHUTDOWN; - ev->npe_name = NULL; - np_queue_add_event(ev); break; } - - /* if we're shutting down, exit this thread */ - if (shutting_down) - return (NULL); } + return (NULL); } static void @@ -270,8 +274,10 @@ change_user_set_privs(void) (void) priv_addset(priv_set, PRIV_FILE_DAC_WRITE); (void) priv_addset(priv_set, PRIV_NET_PRIVADDR); (void) priv_addset(priv_set, PRIV_NET_RAWACCESS); + (void) priv_addset(priv_set, PRIV_PROC_AUDIT); (void) priv_addset(priv_set, PRIV_PROC_OWNER); (void) priv_addset(priv_set, PRIV_PROC_SETID); + (void) priv_addset(priv_set, PRIV_SYS_CONFIG); (void) priv_addset(priv_set, PRIV_SYS_IP_CONFIG); (void) priv_addset(priv_set, PRIV_SYS_IPC_CONFIG); (void) priv_addset(priv_set, PRIV_SYS_NET_CONFIG); @@ -299,12 +305,27 @@ change_user_set_privs(void) priv_freeset(priv_set); } +static void +init_machine_mutex(void) +{ + pthread_mutexattr_t attrs; + + (void) pthread_mutexattr_init(&attrs); + (void) pthread_mutexattr_settype(&attrs, PTHREAD_MUTEX_ERRORCHECK); + if (pthread_mutex_init(&machine_lock, &attrs) != 0) { + syslog(LOG_ERR, "unable to set up machine lock"); + exit(EXIT_FAILURE); + } + (void) pthread_mutexattr_destroy(&attrs); +} + int main(int argc, char *argv[]) { int c; int scan_lev; struct np_event *e; + enum np_event_type etype; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); @@ -342,46 +363,45 @@ main(int argc, char *argv[]) if (!fg) daemonize(); + initialize_llp(); + init_signalhandling(); - init_mutexes(); + initialize_wireless(); lookup_zonename(zonename, sizeof (zonename)); + init_machine_mutex(); + initialize_interfaces(); llp_parse_config(); + initialize_door(); + (void) start_event_collection(); - while ((e = np_queue_get_event()) != NULL) { /* forever */ + while ((e = np_queue_get_event()) != NULL) { - syslog(LOG_INFO, "got event type %s", - npe_type_str(e->npe_type)); - switch (e->npe_type) { - case EV_ROUTING: - case EV_NEWADDR: - case EV_TIMER: - state_machine(e); - free_event(e); - break; - case EV_SYS: - free_event(e); - break; - case EV_SHUTDOWN: - state_machine(e); - (void) pthread_cancel(routing); - (void) pthread_cancel(scan); - (void) pthread_join(routing, NULL); - (void) pthread_join(scan, NULL); - syslog(LOG_INFO, "nwamd shutting down"); - exit(EXIT_SUCCESS); - /* NOTREACHED */ - default: - free_event(e); - syslog(LOG_NOTICE, "unknown event"); - break; + etype = e->npe_type; + syslog(LOG_INFO, "got event type %s", npe_type_str(etype)); + if (etype == EV_SHUTDOWN) + terminate_door(); + if (pthread_mutex_lock(&machine_lock) != 0) { + syslog(LOG_ERR, "mutex lock"); + exit(EXIT_FAILURE); } + state_machine(e); + (void) pthread_mutex_unlock(&machine_lock); + free_event(e); + if (etype == EV_SHUTDOWN) + break; } - return (0); + syslog(LOG_DEBUG, "terminating routing and scanning threads"); + (void) pthread_cancel(routing); + (void) pthread_cancel(scan); + (void) pthread_join(routing, NULL); + (void) pthread_join(scan, NULL); + syslog(LOG_INFO, "nwamd shutting down"); + return (EXIT_SUCCESS); } diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c b/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c index 95cc0585bc..81bf6210c7 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c @@ -20,12 +20,10 @@ */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * This file contains the core logic of nwamd. * @@ -39,36 +37,31 @@ * * state_machine() calls high level routines in llp.c and interface.c to act on * the state of the machine in response to events. + * + * This function is called by the main thread in the program with machine_lock + * held. This is the only thread that can add or remove interface and LLP + * structures, and thus it's safe for this function to use those structures + * without locks. See also the locking comments in the interface.c and llp.c + * block comments. */ -#include <assert.h> +#include <stdarg.h> +#include <stdio.h> #include <arpa/inet.h> -#include <errno.h> #include <libsysevent.h> #include <net/if.h> -#include <net/if_dl.h> #include <net/route.h> #include <netinet/in.h> -#include <pthread.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/ioctl.h> #include <sys/nvpair.h> #include <sys/socket.h> -#include <sys/sockio.h> #include <sys/types.h> #include <syslog.h> -#include <unistd.h> #include "defines.h" #include "structures.h" #include "functions.h" #include "variables.h" -static struct sockaddr sinzero = { AF_INET, 0 }; - void state_machine(struct np_event *e) { @@ -88,8 +81,9 @@ state_machine(struct np_event *e) break; } flags = get_ifflags(evif->if_name, evif->if_family); - if ((flags & IFF_DHCPRUNNING) == 0 || ((flags & IFF_UP) && - !cmpsockaddr(evif->if_ipaddr, &sinzero))) { + if ((!(evif->if_lflags & IF_DHCPSTARTED) && + !(flags & IFF_DHCPRUNNING)) || ((flags & IFF_UP) && + evif->if_ipv4addr != INADDR_ANY)) { /* * Either DHCP came up successfully, or we're no * longer trying to do DHCP on this interface; @@ -99,6 +93,10 @@ state_machine(struct np_event *e) "(ifflags 0x%llx)", evif->if_name, flags); break; } + if (evif->if_lflags & IF_DHCPFAILED) { + dprintf("ignoring timer; interface already failed"); + break; + } /* * dhcp has not yet completed; give up on it for * now and, if it is the currently active llp, @@ -107,28 +105,90 @@ state_machine(struct np_event *e) dprintf("giving up on dhcp on %s (ifflags 0x%llx)", evif->if_name, flags); evif->if_lflags |= IF_DHCPFAILED; - if (interface_is_active(evif)) - llp_swap(llp_best_avail()); + if (interface_is_active(evif)) { + if ((prefllp = llp_best_avail()) != NULL) { + llp_swap(prefllp, dcTimer); + } else { + dprintf("DHCP timed out, but no better link is " + "available"); + report_interface_down(evif->if_name, dcTimer); + } + } else { + dprintf("DHCP failed on inactive link"); + report_interface_down(evif->if_name, dcTimer); + } break; - case EV_ROUTING: + case EV_NEWAP: + if ((evllp = llp_lookup(e->npe_name)) == NULL) { + dprintf("state_machine: no llp for %s; ignoring " + "EV_NEWAP event", STRING(e->npe_name)); + break; + } + + if (evllp == link_layer_profile) { + llp_reselect(); + } else { + evllp->llp_waiting = B_FALSE; + evllp->llp_failed = B_FALSE; + prefllp = llp_best_avail(); + if (prefllp == NULL) { + dprintf("new APs on %s, but no best link is " + "available", llp_prnm(evllp)); + } else if (prefllp != link_layer_profile) { + dprintf("state_machine: new APs on link %s " + "caused new preferred llp: %s (was %s)", + llp_prnm(evllp), llp_prnm(prefllp), + llp_prnm(link_layer_profile)); + llp_swap(prefllp, dcNewAP); + } + } + break; + + case EV_LINKDROP: + case EV_LINKUP: + case EV_LINKFADE: + case EV_LINKDISC: + case EV_USER: if ((evif = get_interface(e->npe_name)) == NULL || (evllp = llp_lookup(e->npe_name)) == NULL) { dprintf("state_machine: either no intf (%p) or no llp " - "(%p) for %s; ignoring EV_ROUTING event", - (void *)evif, (void *)evllp, STRING(e->npe_name)); + "(%p) for %s; ignoring EV_%s event", + (void *)evif, (void *)evllp, STRING(e->npe_name), + npe_type_str(e->npe_type)); break; } + + if (e->npe_type == EV_LINKUP || e->npe_type == EV_USER) + evllp->llp_failed = B_FALSE; + + /* + * If we're here because wireless has disconnected, then clear + * the DHCP failure flag on this interface; this is a "fresh + * start" for the interface, so we should retry DHCP. + */ + if (e->npe_type == EV_LINKFADE || e->npe_type == EV_LINKDISC) + evif->if_lflags &= ~IF_DHCPFAILED; + prefllp = llp_best_avail(); - if (prefllp != link_layer_profile) { + if (prefllp == NULL) { + dprintf("state changed on %s, but no best link is " + "available", llp_prnm(evllp)); + } else if (prefllp != link_layer_profile) { dprintf("state_machine: change in state of link %s " "resulted in new preferred llp: %s (was %s)", llp_prnm(evllp), llp_prnm(prefllp), llp_prnm(link_layer_profile)); - llp_swap(prefllp); + llp_swap(prefllp, + e->npe_type == EV_LINKDROP ? dcUnplugged : + e->npe_type == EV_LINKFADE ? dcFaded : + e->npe_type == EV_LINKDISC ? dcGone : + e->npe_type == EV_USER ? dcUser : + dcBetter); } - + if (e->npe_type == EV_USER) + llp_write_changed_priority(evllp); break; case EV_NEWADDR: @@ -139,19 +199,45 @@ state_machine(struct np_event *e) (void *)evif, (void *)evllp, STRING(e->npe_name)); break; } + evllp->llp_failed = B_FALSE; if (evllp->llp_ipv4src == IPV4SRC_DHCP) { flags = get_ifflags(evif->if_name, evif->if_family); if (!(flags & IFF_DHCPRUNNING) || !(flags & IFF_UP) || - cmpsockaddr(evif->if_ipaddr, &sinzero)) { + evif->if_ipv4addr == INADDR_ANY) { /* * We don't have a DHCP lease. If we used to * have one, then switch to another profile. + * Note that if *we* took the interface down + * (which happens if this isn't the one + * preferred interface), then this doesn't + * signal a DHCP failure. */ - if ((evif->if_lflags & IF_DHCPACQUIRED) != 0) { + if (!(flags & IFF_DHCPRUNNING)) + evif->if_lflags &= ~IF_DHCPSTARTED; + if (evif->if_lflags & IF_DHCPACQUIRED) { evif->if_lflags &= ~IF_DHCPACQUIRED; - evif->if_lflags |= IF_DHCPFAILED; - if (interface_is_active(evif)) - llp_swap(llp_best_avail()); + if (interface_is_active(evif)) { + evif->if_lflags |= + IF_DHCPFAILED; + prefllp = llp_best_avail(); + if (prefllp != NULL) { + dprintf("DHCP has " + "failed, switch " + "interfaces"); + llp_swap(prefllp, + dcDHCP); + } else { + dprintf("DHCP failed, " + "but no better link" + " is available"); + report_interface_down( + evif->if_name, + dcDHCP); + } + } else { + dprintf("DHCP not acquired and " + "not active"); + } } break; } @@ -162,7 +248,7 @@ state_machine(struct np_event *e) */ evif->if_timer_expire = 0; evif->if_lflags |= IF_DHCPACQUIRED; - if ((evif->if_lflags & IF_DHCPFAILED) != 0) { + if (evif->if_lflags & IF_DHCPFAILED) { evif->if_lflags &= ~IF_DHCPFAILED; dhcp_restored = B_TRUE; } @@ -173,15 +259,14 @@ state_machine(struct np_event *e) dprintf("state_machine: dhcp completed on " "higher priority llp (%s); swapping", llp_prnm(evllp)); - llp_swap(evllp); + llp_swap(evllp, dcBetter); } else { dprintf("state_machine: newaddr event was for " "%s, not for current active link (%s); " "taking down %s", evllp->llp_lname, llp_prnm(link_layer_profile), evllp->llp_lname); - takedowninterface(evllp->llp_lname, B_FALSE, - evllp->llp_ipv6onlink); + takedowninterface(evllp->llp_lname, dcUnwanted); break; } } @@ -202,6 +287,80 @@ state_machine(struct np_event *e) } break; + case EV_ADDIF: + /* Plumb the interface */ + if (start_child(IFCONFIG, e->npe_name, "plumb", NULL) != 0) { + syslog(LOG_ERR, "could not plumb interface %s", + e->npe_name); + return; + } + report_interface_added(e->npe_name); + evllp = llp_lookup(e->npe_name); + /* Add interface to interface list. */ + evif = add_interface(AF_INET, e->npe_name, 0); + if (evllp == NULL) { + /* + * Create a new llp entry, and add it to llp list + * and /etc/nwam/llp. By default, the llp + * has a DHCP IPv4 address source, and IPv6 is + * used on the link. We don't plumb the + * IPv6 link yet - this is done for us by + * bringupinterface(). + */ + if ((evllp = llp_add(e->npe_name)) != NULL) + llp_add_file(evllp); + } + + /* + * start_if_info_collect will launch the gather_interface_info + * thread, which will start a scan for wireless interfaces, or + * start DHCP on wired interfaces. + */ + start_if_info_collect(evif, "check"); + break; + + case EV_REMIF: + /* Unplumb the interface */ + if (start_child(IFCONFIG, e->npe_name, "unplumb", NULL) != 0) { + syslog(LOG_ERR, "could not unplumb interface %s", + e->npe_name); + return; + } + evllp = llp_lookup(e->npe_name); + remove_interface(e->npe_name); + if (evllp != NULL) { + /* Unplumb IPv6 interface if IPv6 is used on the link */ + if (evllp->llp_ipv6onlink) { + (void) start_child(IFCONFIG, e->npe_name, + "inet6", "unplumb", NULL); + } + /* If this llp is active, deactivate it. */ + if (evllp == link_layer_profile) + llp_swap(NULL, dcRemoved); + llp_delete(evllp); + } + report_interface_removed(e->npe_name); + break; + + case EV_TAKEDOWN: + takedowninterface(e->npe_name, dcSelect); + break; + + case EV_RESELECT: + if (link_layer_profile == NULL && + (prefllp = llp_best_avail()) != NULL) { + dprintf("reselect: activating LLP %s", + llp_prnm(prefllp)); + llp_swap(prefllp, dcNone); + } else { + llp_reselect(); + } + break; + + case EV_DOOR_TIME: + check_door_life(NSEC_TO_SEC(gethrtime())); + break; + case EV_SHUTDOWN: /* Cleanup not expecting to see any more events after this */ cleanup(); @@ -217,10 +376,9 @@ state_machine(struct np_event *e) void cleanup(void) { + deactivate_upper_layer_profile(); if (link_layer_profile != NULL) { - deactivate_upper_layer_profile(); - takedowninterface(link_layer_profile->llp_lname, B_FALSE, - link_layer_profile->llp_ipv6onlink); + takedowninterface(link_layer_profile->llp_lname, dcShutdown); } /* * Since actions taken in nwamd result in dhcpagent being diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/structures.h b/usr/src/cmd/cmd-inet/lib/nwamd/structures.h index caf0815d32..94d750a3fe 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/structures.h +++ b/usr/src/cmd/cmd-inet/lib/nwamd/structures.h @@ -27,34 +27,45 @@ #ifndef _STRUCTURES_H #define _STRUCTURES_H -#pragma ident "%Z%%M% %I% %E% SMI" - +#include <search.h> #include <net/if.h> #include <libscf.h> #include <libdlwlan.h> +#include <libnwam.h> /* * XXX More work on the state machine is needed. In the future, * events will be more like EV_NEWADDR, identifying the actual * event that we care about, rather than the source of the event - * that we originally implemented. For example, EV_ROUTING should - * be split into EV_LINK_UP and EV_LINK_DOWN. It's a bit of a mix + * that we originally implemented. It's a bit of a mix * right now. */ enum np_event_type { - EV_ROUTING, - EV_SYS, - EV_TIMER, - EV_SHUTDOWN, - EV_NEWADDR + EV_LINKDROP, /* IFF_RUNNING flag dropped */ + EV_LINKUP, /* Wired link is up */ + EV_LINKFADE, /* Wireless link has poor signal */ + EV_LINKDISC, /* Wireless link has disconnected */ + EV_NEWAP, /* New AP in list / wireless link up */ + EV_USER, /* User altered interface priority */ + EV_TIMER, /* Timer(s) have expired */ + EV_SHUTDOWN, /* Nwamd is shutting down */ + EV_NEWADDR, /* Address established on interface */ + EV_RESELECT, /* Client disconnect; retry operations */ + EV_DOOR_TIME, /* Door server needs new timer */ + EV_ADDIF, /* New interface detected */ + EV_REMIF, /* Old interface removed */ + EV_TAKEDOWN /* Take interface down; AP reselected */ }; -enum interface_type { - IF_UNKNOWN, - IF_WIRED, - IF_WIRELESS, - IF_TUN -}; +/* + * Three-valued return types; used for cases where the processing terminates in + * a wait for the user. + */ +typedef enum { + SUCCESS = 0, + FAILURE, + WAITING +} return_vals_t; struct np_event { enum np_event_type npe_type; @@ -88,14 +99,17 @@ struct np_event { * only 'down' the v4 interface. */ struct interface { - char *if_name; - datalink_id_t if_linkid; + char if_name[LIFNAMSIZ]; sa_family_t if_family; uint64_t if_flags; uint32_t if_lflags; - enum interface_type if_type; + libnwam_interface_type_t if_type; uint32_t if_timer_expire; - struct sockaddr *if_ipaddr; + boolean_t if_v6onlink; + boolean_t if_up_attempted; + dladm_wlan_strength_t if_strength; + in_addr_t if_ipv4addr; + pthread_t if_thr; struct interface *if_next; }; @@ -122,25 +136,6 @@ struct interface { #define IF_DHCPFLAGS (IF_DHCPFAILED | IF_DHCPSTARTED | IF_DHCPACQUIRED) /* - * visited_wlans are stored on visited_wlan_list of type visisted_wlans_list. - * Access is protected by wifi_mutex. - */ -struct visited_wlans { - struct wireless_lan *wifi_net; - struct visited_wlans *next; -}; - -struct visited_wlans_list { - struct visited_wlans *head; - int total; -}; - -typedef enum { - IPV4SRC_STATIC, - IPV4SRC_DHCP -} ipv4src_t; - -/* * This structure contains the intended configuration of the system as * differentiated from the actual IPv4 configuration of the system represented * by the interface structures. @@ -149,28 +144,57 @@ typedef enum { * protected by llp_lock. */ typedef struct llp { - struct llp *llp_next; + struct qelem llp_links; char llp_lname[LIFNAMSIZ]; int llp_pri; /* lower number => higher priority */ - enum interface_type llp_type; - ipv4src_t llp_ipv4src; + int llp_fileorder; + libnwam_interface_type_t llp_type; + boolean_t llp_failed; /* interface bringup failed */ + boolean_t llp_waiting; /* waiting for user interface */ + libnwam_ipv4src_t llp_ipv4src; char *llp_ipv4addrstr; /* if ipsrc is STATIC */ char *llp_ipv6addrstr; /* if the user provided a static addr */ boolean_t llp_ipv6onlink; /* true if we plumb up a v6 interface */ + + /* These are used only with door communication */ + boolean_t llp_dhcp_failed; + boolean_t llp_link_up; + boolean_t llp_need_wlan; + boolean_t llp_need_key; } llp_t; /* - * These entries are user allocated and should be managed by whomever + * The wireless module uses a separate thread to check AP status and scan for + * AP changes. Some wireless operations (such as scanning) may take a long + * time. For that reason, we maintain our own wireless interface structure to + * keep interface-specific wireless state. + */ +typedef struct wireless_if_s { + struct qelem wi_links; + char wi_name[LIFNAMSIZ]; + datalink_id_t wi_linkid; + boolean_t wi_scan_running; + boolean_t wi_wireless_done; + boolean_t wi_need_key; + dladm_wlan_strength_t wi_strength; +} wireless_if_t; + +/* + * These entries are user allocated and should be managed by whoever * originates the structure. */ struct wireless_lan { + dladm_wlan_attr_t attrs; + boolean_t known; + boolean_t connected; + boolean_t scanned; + boolean_t rescan; char *essid; char *bssid; char *signal_strength; char *raw_key; dladm_wlan_key_t *cooked_key; - dladm_wlan_secmode_t sec_mode; - char *wl_if_name; + char wl_if_name[LIFNAMSIZ]; }; /* @@ -186,5 +210,52 @@ typedef struct scf_resources { scf_value_t *sr_val; } scf_resources_t; +/* + * These are used to deliver events to the GUI. See door.c and libnwam. + */ +typedef struct nwam_descr_event_s { + libnwam_descr_evtype_t nde_type; + libnwam_diag_cause_t nde_cause; + struct in_addr nde_v4address; + int nde_prefixlen; + dladm_wlan_attr_t nde_attrs; + struct wireless_lan *nde_wlans; + size_t nde_wlansize; + char nde_interface[LIFNAMSIZ]; +} nwam_descr_event_t; + +typedef enum nwam_door_cmd_type_e { + ndcNull, + ndcWaitEvent, + ndcGetLLPList, + ndcSetLLPPriority, + ndcLockLLP, + ndcGetWlanList, + ndcSelectWlan, + ndcWlanKey, + ndcStartRescan, + ndcGetKnownAPList, + ndcAddKnownAP, + ndcDeleteKnownAP +} nwam_door_cmd_type_t; + +typedef struct nwam_door_cmd_s { + nwam_door_cmd_type_t ndc_type; + char ndc_interface[LIFNAMSIZ]; + int ndc_priority; + char ndc_essid[DLADM_STRSIZE]; + char ndc_bssid[DLADM_STRSIZE]; + char ndc_key[DLADM_STRSIZE]; +} nwam_door_cmd_t; + +typedef struct nwam_llp_data_s { + uint_t nld_count; /* number of llp_t struct following */ + char nld_selected[LIFNAMSIZ]; + char nld_locked[LIFNAMSIZ]; +} nwam_llp_data_t; + +typedef struct nwam_known_ap_s { + uint_t nka_count; /* number of libnwam_known_ap_t */ +} nwam_known_ap_t; #endif /* _STRUCTURES_H */ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/util.c b/usr/src/cmd/cmd-inet/lib/nwamd/util.c index bad83540af..4321e78ee4 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/util.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/util.c @@ -20,12 +20,10 @@ */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* * util.c contains a set of miscellaneous utility functions which: * - syslog(LOG_DEBUG, ...) if debugging is enabled @@ -33,7 +31,6 @@ * - look up all flags for an IP interface * - start a child process * - schedule a timer - * - check to see if a user is logged in to a graphical console * - look up the zone name */ @@ -43,19 +40,16 @@ #include <unistd.h> #include <pthread.h> #include <string.h> -#include <strings.h> #include <stropts.h> #include <syslog.h> #include <sys/types.h> #include <sys/socket.h> -#include <sys/sockio.h> #include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> #include <spawn.h> #include <wait.h> #include <inetcfg.h> -#include <utmpx.h> -#include <pwd.h> -#include <limits.h> #include <errno.h> #include <zone.h> @@ -82,15 +76,6 @@ dprintf(const char *fmt, ...) va_end(ap); } -boolean_t -is_plugged_in(struct interface *i) -{ - if (i->if_type == IF_WIRELESS) - return (B_TRUE); - - return ((get_ifflags(i->if_name, i->if_family) & IFF_RUNNING) != 0); -} - uint64_t get_ifflags(const char *name, sa_family_t family) { @@ -123,6 +108,34 @@ get_ifflags(const char *name, sa_family_t family) return (flags); } +/* This is just a work-around for CR 6745448: clear out a toxic interface */ +void +zero_out_v4addr(const char *name) +{ + icfg_if_t intf; + icfg_handle_t h; + struct sockaddr_in sinv; + socklen_t sinlen; + int pfxlen; + + (void) strlcpy(intf.if_name, name, sizeof (intf.if_name)); + intf.if_protocol = AF_INET; + + if (icfg_open(&h, &intf) != ICFG_SUCCESS) + return; + + sinlen = sizeof (sinv); + if (icfg_get_addr(h, (struct sockaddr *)&sinv, &sinlen, &pfxlen, + B_FALSE) == ICFG_SUCCESS && + sinv.sin_addr.s_addr != INADDR_ANY) { + dprintf("bug workaround: clear out address %s on %s", + inet_ntoa(sinv.sin_addr), name); + sinv.sin_addr.s_addr = INADDR_ANY; + (void) icfg_set_addr(h, (const struct sockaddr *)&sinv, sinlen); + } + icfg_close(h); +} + /* * * This starts a child process determined by command. If command contains a @@ -249,71 +262,6 @@ start_timer(uint32_t now, uint32_t delay) (void) alarm(delay); } -boolean_t -valid_graphical_user(boolean_t query) -{ - struct utmpx *utp; - char *user = NULL; - const char HOMESTR[] = "HOME="; - char buf[1024]; /* == sysconf(_SC_GETPW_R_SIZE_MAX) == NSS_BUFSIZ */ - static char home_dir[PATH_MAX + sizeof (HOMESTR)]; - struct passwd passwd; - struct passwd *pw; - boolean_t popup_ok; - - /* - * Check to see if our SMF property says popups are OK. - */ - if ((lookup_boolean_property(OUR_PG, query ? "popup_query" : - "popup_info", &popup_ok) == 0) && !popup_ok) - return (B_FALSE); - - /* - * Look for someone logged into the console from host ":0" (i.e., - * the X display. Down the road, we should generalize this so - * ":0" is not hard-coded. Note that the entry we want is usually - * an ordinary user process but sometimes if a session leader has - * exited, it can come from a DEAD_PROCESS, as is known to happen - * when the user logs in via gdm(1m). - */ - setutxent(); - while ((utp = getutxent()) != NULL) { - if (((utp->ut_type == USER_PROCESS) || - (utp->ut_type == DEAD_PROCESS)) && - (strcmp(utp->ut_line, "console") == 0) && - (strcmp(utp->ut_host, ":0") == 0)) { - user = strdup(utp->ut_user); - break; - } - } - endutxent(); - dprintf("utmpx: done %s", user != NULL ? user : ""); - - if (user == NULL) - return (B_FALSE); - - pw = getpwnam_r(user, &passwd, buf, sizeof (buf)); - if (pw == NULL) { - syslog(LOG_ERR, "couldn't get user %s: %m", user); - free(user); - return (B_FALSE); - } - free(user); - - /* - * We shouldn't be dumping this into our environment or changing - * our uid/gid but instead starting up the zenity processes with - * this display as this user. RFE to change this. - */ - (void) putenv("DISPLAY=:0.0"); - - (void) strlcpy(home_dir, HOMESTR, sizeof (home_dir)); - (void) strlcat(home_dir, pw->pw_dir, sizeof (home_dir)); - (void) putenv(home_dir); - - return (pw != NULL); -} - void lookup_zonename(char *zonename, size_t zonesize) { @@ -324,78 +272,3 @@ lookup_zonename(char *zonename, size_t zonesize) syslog(LOG_ERR, "could not determine zone name"); (void) strlcpy(zonename, GLOBAL_ZONENAME, zonesize); } - -/* return B_TRUE if sin_family and sin_addr are the same, B_FALSE if not */ -boolean_t -cmpsockaddr(const struct sockaddr *addr1, const struct sockaddr *addr2) -{ - struct sockaddr_in *sina, *sinb; - struct sockaddr_in6 *sin6a, *sin6b; - - if (addr1 == addr2) - return (B_TRUE); - - if (addr1 == NULL || addr2 == NULL) - return (B_FALSE); - - if (addr1->sa_family != addr2->sa_family) - return (B_FALSE); - - switch (addr1->sa_family) { - case AF_INET: - /* LINTED E_BAD_PTR_CAST_ALIGN */ - sina = (struct sockaddr_in *)addr1; - /* LINTED E_BAD_PTR_CAST_ALIGN */ - sinb = (struct sockaddr_in *)addr2; - return (sina->sin_addr.s_addr == sinb->sin_addr.s_addr); - case AF_INET6: - /* LINTED E_BAD_PTR_CAST_ALIGN */ - sin6a = (struct sockaddr_in6 *)addr1; - /* LINTED E_BAD_PTR_CAST_ALIGN */ - sin6b = (struct sockaddr_in6 *)addr2; - return - (IN6_ARE_ADDR_EQUAL(&sin6a->sin6_addr, &sin6b->sin6_addr)); - default: - dprintf("cmpsockaddr: unsupported af (%d)", addr1->sa_family); - return (B_FALSE); - } -} - -/* - * Duplicate a sockaddr. Caller will be responsible for freeing memory when it - * is no longer needed. Currently only supports AF_INET and AF_INET6 - * (returns NULL otherwise). - */ -struct sockaddr * -dupsockaddr(const struct sockaddr *addr) -{ - struct sockaddr_in *t1, *ret1; - struct sockaddr_in6 *t2, *ret2; - - switch (addr->sa_family) { - case AF_INET: - if ((ret1 = calloc(1, sizeof (struct sockaddr_in))) == NULL) { - syslog(LOG_ERR, "dupsockaddr: calloc failed"); - return (NULL); - } - /* LINTED E_BAD_PTR_CAST_ALIGN */ - t1 = (struct sockaddr_in *)addr; - ret1->sin_family = t1->sin_family; - ret1->sin_addr.s_addr = t1->sin_addr.s_addr; - return ((struct sockaddr *)ret1); - case AF_INET6: - if ((ret2 = calloc(1, sizeof (struct sockaddr_in6))) == NULL) { - syslog(LOG_ERR, "dupsockaddr: calloc failed"); - return (NULL); - } - /* LINTED E_BAD_PTR_CAST_ALIGN */ - t2 = (struct sockaddr_in6 *)addr; - ret2->sin6_family = t2->sin6_family; - (void) memcpy((void *)&ret2->sin6_addr, - (const void *)&t2->sin6_addr, sizeof (struct in6_addr)); - return ((struct sockaddr *)ret2); - default: - dprintf("dupsockaddr: unsupported af (%d)", addr->sa_family); - return (NULL); - } -} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/variables.h b/usr/src/cmd/cmd-inet/lib/nwamd/variables.h index 2a4794f1be..a9efd4ee4c 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/variables.h +++ b/usr/src/cmd/cmd-inet/lib/nwamd/variables.h @@ -20,20 +20,19 @@ */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _VARIABLES_H #define _VARIABLES_H -#pragma ident "%Z%%M% %I% %E% SMI" - #include <libdlwlan.h> #include <sys/zone.h> extern struct np_event *equeue; +extern pthread_mutex_t machine_lock; extern pthread_mutex_t queue_mutex; extern pthread_cond_t queue_cond; extern pthread_t routing, scan; @@ -50,6 +49,8 @@ extern uint32_t timer_expire; extern uint_t wlan_scan_interval; extern dladm_wlan_strength_t wireless_scan_level; +extern uint_t door_idle_time; + extern const char *OUR_FMRI; extern const char *OUR_PG; diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c index f3d2a14c14..4be6af66c8 100644 --- a/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c +++ b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c @@ -24,41 +24,32 @@ * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" - /* - * This file containes all the routines to handle wireless (more + * This file contains 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/WPA 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 - * connects to an AP, the AP's ESSID/BSSID will be added to that file. - * This file is used in the following way. + * code will send a message to the GUI, which then must gather the results. * - * 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. + * WEP/WPA 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 connects to an AP, the AP's ESSID/BSSID will + * be added to that file. This file is used in the following way. * - * 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 one known AP (plus any number of unknown + * APs), the code will automatically connect to that AP without contacting the + * GUI. But if the detected signal strength of that one known AP is weaker + * than any of the unknown APs, the code will block on the GUI. * - * 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. + * If the AP scan results contain more than one known APs or no known APs, the + * GUI is notified. * * 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. + * Thus, the GUI can specify an AP's data. * * The code also periodically (specified by wlan_scan_interval) checks * for the health of the AP connection. If the signal strength of the @@ -68,13 +59,8 @@ * 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. + * Lock ordering note: wifi_mutex and wifi_init_mutex are not held at the same + * time. */ #include <unistd.h> @@ -83,7 +69,6 @@ #include <stdio.h> #include <strings.h> #include <syslog.h> -#include <assert.h> #include <limits.h> #include <errno.h> #include <sys/stat.h> @@ -93,8 +78,6 @@ #include <stropts.h> #include <sys/types.h> #include <fcntl.h> -#include <locale.h> -#include <libintl.h> #include <libdladm.h> #include <libdllink.h> #include <libinetutil.h> @@ -113,15 +96,6 @@ (sec == DLADM_WLAN_SECMODE_WPA || sec == DLADM_WLAN_SECMODE_WEP) static pthread_mutex_t wifi_mutex; -static pthread_mutexattr_t wifi_mutex_attr; -static pthread_mutex_t wifi_init_mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t wifi_init_cond = PTHREAD_COND_INITIALIZER; - -typedef enum { - SUCCESS = 0, - FAILURE, - TRY_AGAIN -} return_vals_t; typedef enum { ESSID = 0, @@ -130,18 +104,29 @@ typedef enum { } known_wifi_nets_fields_t; /* + * List of wireless interfaces; protected by wifi_mutex. + */ +static struct qelem wi_list; +static uint_t wi_link_count; + +/* * 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. */ -static struct interface *wifi_scan_intf = NULL; +static char wifi_scan_intf[LIFNAMSIZ]; +static pthread_mutex_t wifi_init_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t wifi_init_cond = PTHREAD_COND_INITIALIZER; -/* used entries have non NULL memebers */ -static struct wireless_lan *wlans = NULL; -static uint_t wireless_lan_count = 0; /* allocated */ -static uint_t wireless_lan_used = 0; /* used entries */ +/* + * Array of wireless LAN entries; protected by wifi_mutex. + */ +static struct wireless_lan *wlans; +static uint_t wireless_lan_count; /* allocated */ +static uint_t wireless_lan_used; /* used entries */ +static boolean_t new_ap_found; static int key_string_to_secobj_value(char *, uint8_t *, uint_t *, dladm_secobj_class_t); @@ -149,34 +134,24 @@ static int store_key(struct wireless_lan *); static dladm_wlan_key_t *retrieve_key(const char *, const char *, dladm_secobj_class_t); -static boolean_t add_wlan_entry(struct interface *, char *, char *, char *, - dladm_wlan_secmode_t); -static boolean_t already_in_visited_wlan_list(const struct wireless_lan *); -static boolean_t check_wlan(struct interface *, const char *); -static boolean_t connect_or_autoconf(struct wireless_lan *, struct interface *); -static return_vals_t connect_to_new_wlan(const struct wireless_lan *, int, - struct interface *); -static boolean_t find_wlan_entry(struct interface *, char *, char *); +static struct wireless_lan *add_wlan_entry(const char *, const char *, + const char *, dladm_wlan_attr_t *); +static boolean_t check_wlan(const wireless_if_t *, const char *, const char *, + boolean_t); +static return_vals_t connect_or_autoconf(struct wireless_lan *, + wireless_if_t *); +static struct wireless_lan *find_wlan_entry(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_key(struct wireless_lan *); -static int get_user_preference(char *const *, const char *, const char *, - struct wireless_lan **, const struct wireless_lan *); -static char **alloc_argv(int, size_t); -static void free_argv(char **); -static char **build_wlanlist_zargv(const struct wireless_lan *, int, - const char *, int, const char **, int); -static char *get_zenity_response(char *const *); -static boolean_t wlan_autoconf(struct interface *); -static int zenity_height(int); +static return_vals_t get_user_key(struct wireless_lan *); +static boolean_t wlan_autoconf(const wireless_if_t *); static boolean_t get_scan_results(void *, dladm_wlan_attr_t *); +static int add_known_wifi_nets_file(const char *, const char *); static boolean_t known_wifi_nets_lookup(const char *, const char *, char *); - +static return_vals_t connect_chosen_lan(struct wireless_lan *, wireless_if_t *); #define WIRELESS_LAN_INIT_COUNT 8 -struct visited_wlans_list *visited_wlan_list = NULL; - /* * The variable wlan_scan_interval controls the interval in seconds * between periodic scans. @@ -189,167 +164,116 @@ uint_t wlan_scan_interval = 120; */ dladm_wlan_strength_t wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK; -/* - * Some constants for zenity args - */ -/* - * For a wlan table, command begins with 5 general args: - * cmdname (argv[0]), window type (list), title, height, and width. - */ -#define ZENITY_LIST_INIT_ARGS 5 -/* - * Columns: index, ESSID, BSSID, Encryption Type, Signal Strength - */ -#define ZENITY_COLUMNS_PER_WLAN 5 -/* - * Cap the length of an individual arg at 64 bytes. - * Longest args tend to be extra row strings. - */ -#define ZENITY_ARG_LEN 64 -/* - * Typical zenity return buffers are index number strings - * or extra row strings; 1024 should be sufficient. - */ -#define ZENITY_RTN_BUF_SIZE 1024 - -/* - * Alloc an array of (cnt + 1) pointers, where the first cnt pointers - * point to an alloc'd buffer of specified len. The last pointer in - * the array is NULL. - */ -static char ** -alloc_argv(int cnt, size_t buflen) +void +initialize_wireless(void) { - int i; - char **argv; + pthread_mutexattr_t wifi_mutex_attr; - if ((argv = calloc(cnt + 1, sizeof (char *))) == NULL) { - syslog(LOG_ERR, "calloc failed: %m"); - return (NULL); - } - for (i = 0; i < cnt; i++) { - if ((argv[i] = malloc(buflen)) == NULL) { - syslog(LOG_ERR, "malloc failed: %m"); - free_argv(argv); - return (NULL); + (void) pthread_mutexattr_init(&wifi_mutex_attr); + (void) pthread_mutexattr_settype(&wifi_mutex_attr, + PTHREAD_MUTEX_RECURSIVE); + (void) pthread_mutex_init(&wifi_mutex, &wifi_mutex_attr); + wi_list.q_forw = wi_list.q_back = &wi_list; +} + +void +add_wireless_if(const char *ifname) +{ + wireless_if_t *wip; + + if ((wip = calloc(1, sizeof (*wip))) != NULL) { + (void) strlcpy(wip->wi_name, ifname, sizeof (wip->wi_name)); + (void) dladm_name2info(ifname, &wip->wi_linkid, NULL, NULL, + NULL); + if (pthread_mutex_lock(&wifi_mutex) == 0) { + insque(&wip->wi_links, wi_list.q_back); + wi_link_count++; + (void) pthread_mutex_unlock(&wifi_mutex); + } else { + free(wip); } } - argv[cnt] = NULL; - - return (argv); } -/* - * Free an argv. Assumes that the first NULL pointer encountered - * indicates the end of the array: that is, that the array is null- - * terminated and that no other elements are NULL. - */ -static void -free_argv(char **argv) +static wireless_if_t * +find_wireless_if(const char *ifname) { - int i = 0; - - if (argv == NULL) - return; + wireless_if_t *wip; - while (argv[i] != NULL) - free(argv[i++]); - free(argv); + for (wip = (wireless_if_t *)wi_list.q_forw; + wip != (wireless_if_t *)&wi_list; + wip = (wireless_if_t *)wip->wi_links.q_forw) { + if (strcmp(wip->wi_name, ifname) == 0) + return (wip); + } + return (NULL); } void -init_mutexes(void) +remove_wireless_if(const char *ifname) { - (void) pthread_mutexattr_init(&wifi_mutex_attr); - (void) pthread_mutexattr_settype(&wifi_mutex_attr, - PTHREAD_MUTEX_RECURSIVE); - (void) pthread_mutex_init(&wifi_mutex, &wifi_mutex_attr); + wireless_if_t *wip; + + if (pthread_mutex_lock(&wifi_mutex) == 0) { + if ((wip = find_wireless_if(ifname)) != NULL) { + remque(&wip->wi_links); + wi_link_count--; + } + (void) pthread_mutex_unlock(&wifi_mutex); + free(wip); + } } /* * wlan is expected to be non-NULL. */ -static void +static return_vals_t get_user_key(struct wireless_lan *wlan) { dladm_secobj_class_t class; - int zargc, cur; - char **zargv; /* * First, test if we have key stored as secobj. If so, * no need to prompt for it. */ - class = (wlan->sec_mode == DLADM_WLAN_SECMODE_WEP ? + class = (wlan->attrs.wa_secmode == DLADM_WLAN_SECMODE_WEP ? DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA); wlan->cooked_key = retrieve_key(wlan->essid, wlan->bssid, class); if (wlan->cooked_key != NULL) { dprintf("get_user_key: retrieve_key() returns non NULL"); - return; - } - - if (!valid_graphical_user(B_TRUE)) - return; - - /* - * build zenity 'entry' argv, with text hidden: - * 'zenity --entry --title=foo --text=bar --hide-text' - * Five args needed. - */ - zargc = 5; - if ((zargv = alloc_argv(zargc, ZENITY_ARG_LEN)) == NULL) - return; - - cur = 0; - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, ZENITY); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--entry"); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--title=%s", - gettext("Enter Key")); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--text=%s %s", - gettext("Enter key for WiFi network"), wlan->essid); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--hide-text"); - - wlan->raw_key = get_zenity_response(zargv); - if (wlan->raw_key == NULL) { - dprintf("get_user_key: failed to obtain user-specified key"); - goto cleanup; - } - - /* Store key persistently */ - if (store_key(wlan) != 0) { - syslog(LOG_ERR, "get_user_key: failed to store user-specified " - "key"); + return (SUCCESS); + } else if (request_wlan_key(wlan)) { + return (WAITING); + } else { + return (FAILURE); } - -cleanup: - free_argv(zargv); } -static boolean_t -find_wlan_entry(struct interface *intf, char *essid, char *bssid) +/* + * This function assumes that wifi_mutex is held. If bssid is specified, then + * an exact match is returned. If it's not specified, then the best match is + * returned. + */ +static struct wireless_lan * +find_wlan_entry(const char *ifname, const char *essid, const char *bssid) { - int i; + struct wireless_lan *wlan, *best; - (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. - */ - if (strcmp(wlans[i].essid, essid) == 0 && - strcmp(wlans[i].bssid, bssid) == 0 && - strcmp(wlans[i].wl_if_name, intf->if_name) == 0) { - (void) pthread_mutex_unlock(&wifi_mutex); - return (B_TRUE); + best = NULL; + for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { + if (strcmp(wlan->essid, essid) != 0 || + strcmp(wlan->wl_if_name, ifname) != 0) + continue; + if (bssid[0] == '\0') { + if (best == NULL || + wlan->attrs.wa_strength > best->attrs.wa_strength) + best = wlan; + } else { + if (strcmp(wlan->bssid, bssid) == 0) + return (wlan); } } - (void) pthread_mutex_unlock(&wifi_mutex); - return (B_FALSE); + return (best); } static void @@ -357,7 +281,9 @@ free_wireless_lan(struct wireless_lan *wlp) { free(wlp->essid); wlp->essid = NULL; - free(wlp->bssid); + /* empty string is not allocated */ + if (wlp->bssid != NULL && wlp->bssid[0] != '\0') + free(wlp->bssid); wlp->bssid = NULL; free(wlp->signal_strength); wlp->signal_strength = NULL; @@ -365,189 +291,435 @@ free_wireless_lan(struct wireless_lan *wlp) wlp->raw_key = NULL; free(wlp->cooked_key); wlp->cooked_key = NULL; - free(wlp->wl_if_name); - wlp->wl_if_name = NULL; } -static boolean_t -add_wlan_entry(struct interface *intf, char *essid, char *bssid, - char *signal_strength, dladm_wlan_secmode_t sec) +/* + * This function assumes that wifi_mutex is held. + */ +static struct wireless_lan * +add_wlan_entry(const char *ifname, const char *essid, const char *bssid, + dladm_wlan_attr_t *attrp) { - int n; - - (void) pthread_mutex_lock(&wifi_mutex); + char strength[DLADM_STRSIZE]; + struct wireless_lan *wlan; if (wireless_lan_used == wireless_lan_count) { int newcnt; - struct wireless_lan *r; newcnt = (wireless_lan_count == 0) ? WIRELESS_LAN_INIT_COUNT : wireless_lan_count * 2; - r = realloc(wlans, newcnt * sizeof (*wlans)); - if (r == NULL) { + wlan = realloc(wlans, newcnt * sizeof (*wlans)); + if (wlan == NULL) { syslog(LOG_ERR, "add_wlan_entry: realloc failed"); - (void) pthread_mutex_unlock(&wifi_mutex); - return (B_FALSE); + return (NULL); } - (void) memset((void *)(r + wireless_lan_count), 0, - (newcnt - wireless_lan_count) * sizeof (*r)); wireless_lan_count = newcnt; - wlans = r; - } - - n = wireless_lan_used; - wlans[n].essid = strdup(essid); - wlans[n].bssid = strdup(bssid); - wlans[n].signal_strength = strdup(signal_strength); - wlans[n].wl_if_name = strdup(intf->if_name); - wlans[n].sec_mode = sec; - wlans[n].raw_key = NULL; - wlans[n].cooked_key = NULL; - if (wlans[n].essid == NULL || wlans[n].bssid == NULL || - wlans[n].signal_strength == NULL || wlans[n].wl_if_name == NULL) { + wlans = wlan; + } + + (void) dladm_wlan_strength2str(&attrp->wa_strength, strength); + + wlan = wlans + wireless_lan_used; + (void) memset(wlan, 0, sizeof (*wlan)); + wlan->attrs = *attrp; + wlan->essid = strdup(essid); + /* do not do allocation for zero-length */ + wlan->bssid = *bssid == '\0' ? "" : strdup(bssid); + wlan->signal_strength = strdup(strength); + (void) strlcpy(wlan->wl_if_name, ifname, sizeof (wlan->wl_if_name)); + wlan->scanned = B_TRUE; + if (wlan->essid == NULL || wlan->bssid == NULL || + wlan->signal_strength == NULL) { syslog(LOG_ERR, "add_wlan_entry: strdup failed"); - free_wireless_lan(&(wlans[n])); - (void) pthread_mutex_unlock(&wifi_mutex); - return (B_FALSE); + free_wireless_lan(wlan); + return (NULL); } wireless_lan_used++; - (void) pthread_mutex_unlock(&wifi_mutex); - return (B_TRUE); + new_ap_found = B_TRUE; + return (wlan); } -static void -clear_lan_entries(void) +/* + * Remove entries that are no longer seen on the network. The caller does not + * hold wifi_mutex, but is the only thread that can modify the wlan list. + * Retain connected entries, as lack of visibility in a scan may just be a + * temporary condition (driver problem) and may not reflect an actual + * disconnect. + */ +static boolean_t +clear_unscanned_entries(const char *ifname) { - int i; + struct wireless_lan *wlan, *wlput; + boolean_t dropped; - (void) pthread_mutex_lock(&wifi_mutex); - for (i = 0; i < wireless_lan_used; i++) - free_wireless_lan(&(wlans[i])); - wireless_lan_used = 0; + if (pthread_mutex_lock(&wifi_mutex) != 0) + return (B_FALSE); + wlput = wlans; + dropped = B_FALSE; + for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { + if (strcmp(ifname, wlan->wl_if_name) != 0 || wlan->scanned || + wlan->connected) { + if (wlput != wlan) + *wlput = *wlan; + wlput++; + } else { + dprintf("dropping unseen AP %s %s", wlan->essid, + wlan->bssid); + dropped = B_TRUE; + free_wireless_lan(wlan); + } + } + wireless_lan_used = wlput - wlans; (void) pthread_mutex_unlock(&wifi_mutex); + return (dropped); } /* - * 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. + * Verify if a WiFi NIC is associated with the given ESSID and BSSID. 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. + * 1. If the NIC is associated with the given ESSID/BSSID, 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. + * 3. If the NIC is associated with a different AP, tear down IP interface, + * tell the driver to disassociate with AP, and then return false. */ static boolean_t -check_wlan(struct interface *intf, const char *exp_essid) +check_wlan(const wireless_if_t *wip, const char *exp_essid, + const char *exp_bssid, boolean_t sendevent) { dladm_wlan_linkattr_t attr; dladm_status_t status; char cur_essid[DLADM_STRSIZE]; + char cur_bssid[DLADM_STRSIZE]; char errmsg[DLADM_STRSIZE]; - status = dladm_wlan_get_linkattr(intf->if_linkid, &attr); + status = dladm_wlan_get_linkattr(wip->wi_linkid, &attr); if (status != DLADM_STATUS_OK) { dprintf("check_wlan: dladm_wlan_get_linkattr() for %s " - "failed: %s", intf->if_name, + "failed: %s", wip->wi_name, dladm_status2str(status, errmsg)); return (B_FALSE); } if (attr.la_status == DLADM_WLAN_LINK_DISCONNECTED) return (B_FALSE); + + /* If we're expecting "any" connection, then we're done. */ if (exp_essid == NULL) return (B_TRUE); + + /* Is the NIC associated with the expected access point? */ (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, cur_essid); + if (strcmp(cur_essid, exp_essid) != 0) { + dprintf("wrong ESSID: have %s expect %s; taking down", + cur_essid, exp_essid); + goto unexpected; + } + + if (exp_bssid == NULL) + return (B_TRUE); - /* Is the NIC associated with the expected one? */ - if (strcmp(cur_essid, exp_essid) == 0) + (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, cur_bssid); + if (strcmp(cur_bssid, exp_bssid) == 0) return (B_TRUE); + dprintf("wrong BSSID: have %s expect %s; taking down", + cur_bssid, exp_bssid); - /* Tell the driver to disassociate with the current AP. */ - if (dladm_wlan_disconnect(intf->if_linkid) != DLADM_STATUS_OK) { - dprintf("check_wlan: dladm_wlan_disconnect() for %s fails", - intf->if_name); +unexpected: + if (sendevent) { + /* If not, then shut the interface down normally */ + (void) np_queue_add_event(EV_TAKEDOWN, wip->wi_name); + (void) dladm_wlan_disconnect(wip->wi_linkid); } return (B_FALSE); } /* - * Given a wireless interface, use it to scan for available networks. + * Examine all WLANs associated with an interface, and update the 'connected' + * and 'known' attributes appropriately. The caller holds wifi_mutex. */ -boolean_t -scan_wireless_nets(struct interface *intf) +static boolean_t +update_connected_wlan(wireless_if_t *wip) { - boolean_t new_ap = B_FALSE; + dladm_wlan_linkattr_t attr; + struct wireless_lan *wlan, *lastconn, *newconn; + char essid[DLADM_STRSIZE]; + char bssid[DLADM_STRSIZE]; + boolean_t connected, wasconn; + int retries = 0; + + /* + * This is awful, but some wireless drivers (particularly 'ath') will + * erroneously report "disconnected" if queried right after a scan. If + * we see 'down' reported here, we retry a few times to make sure. + */ + while (retries++ < 4) { + if (dladm_wlan_get_linkattr(wip->wi_linkid, &attr) != + DLADM_STATUS_OK) + attr.la_status = DLADM_WLAN_LINK_DISCONNECTED; + else if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) + break; + } + if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) { + (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, essid); + (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, bssid); + connected = B_TRUE; + wip->wi_wireless_done = B_TRUE; + dprintf("update: %s is connected to %s %s", wip->wi_name, essid, + bssid); + } else { + connected = B_FALSE; + dprintf("update: %s is currently unconnected", wip->wi_name); + } + wasconn = B_FALSE; + lastconn = newconn = NULL; + for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { + if (strcmp(wlan->wl_if_name, wip->wi_name) != 0) + continue; + if (connected && strcmp(wlan->essid, essid) == 0 && + strcmp(wlan->bssid, bssid) == 0) { + wasconn = wlan->connected; + wlan->connected = connected; + newconn = wlan; + } else { + if (wlan->connected) + lastconn = wlan; + wlan->connected = B_FALSE; + } + } + if (newconn == NULL && connected) { + newconn = add_wlan_entry(wip->wi_name, essid, bssid, + &attr.la_wlan_attr); + if (newconn != NULL) + newconn->connected = connected; + } + if (lastconn != NULL) + report_wlan_disconnect(lastconn); + if (newconn != NULL && !wasconn && connected) { + /* + * If we're already connected but not yet known, then it's + * clear that this is a "known wlan" for the user. He must + * have issued a dladm connect-wifi command to get here. + */ + if (!newconn->known) { + newconn->known = B_TRUE; + (void) add_known_wifi_nets_file(newconn->essid, + newconn->bssid); + } + report_wlan_connected(newconn); + } + return (connected); +} + +/* + * Given a wireless interface, use it to scan for available networks. The + * caller must not hold wifi_mutex. + */ +static void +scan_wireless_nets(const char *ifname) +{ + boolean_t already_done; + boolean_t dropped; + boolean_t new_found; dladm_status_t status; - int num_ap; + int i; + datalink_id_t linkid; + wireless_if_t *wip; - assert(intf->if_type == IF_WIRELESS); /* - * 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. + * If there is already a scan in progress, defer until the scan is done + * to avoid radio interference. But if the interface doing the scan is + * the same as the one requesting the new scan, then wait for it to + * finish, and then we're done. */ - (void) pthread_mutex_lock(&wifi_init_mutex); - while (wifi_scan_intf != NULL) { + if (pthread_mutex_lock(&wifi_init_mutex) != 0) + return; + already_done = B_FALSE; + while (wifi_scan_intf[0] != '\0') { dprintf("scan_wireless_nets in progress: old %s new %s", - wifi_scan_intf->if_name, intf->if_name); - if (strcmp(wifi_scan_intf->if_name, intf->if_name) == 0) { + wifi_scan_intf, ifname); + if (strcmp(wifi_scan_intf, ifname) == 0) + already_done = B_TRUE; + (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); + if (already_done) { (void) pthread_mutex_unlock(&wifi_init_mutex); - return (B_FALSE); + return; } - (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); } - wifi_scan_intf = intf; + (void) strlcpy(wifi_scan_intf, ifname, sizeof (wifi_scan_intf)); (void) pthread_mutex_unlock(&wifi_init_mutex); + /* Grab the linkid from the wireless interface */ + if (pthread_mutex_lock(&wifi_mutex) != 0) + goto scan_end; + if ((wip = find_wireless_if(ifname)) == NULL) { + (void) pthread_mutex_unlock(&wifi_mutex); + dprintf("aborted scan on %s; unable to locate interface", + ifname); + goto scan_end; + } + linkid = wip->wi_linkid; + (void) pthread_mutex_unlock(&wifi_mutex); + /* - * Since only one scan is allowed at any one time, no need to grab - * a lock in checking wireless_lan_used. + * Since only one scan is allowed at any one time, and only scans can + * modify the list, there's no need to grab a lock in checking + * wireless_lan_used or the wlans list itself, or for the new_ap_found + * global. + * + * All other threads must hold the mutex when reading this data, and + * this thread must hold the mutex only when writing portions that + * those other threads may read. */ - num_ap = wireless_lan_used; - status = dladm_wlan_scan(intf->if_linkid, intf, get_scan_results); - if (status != DLADM_STATUS_OK) - syslog(LOG_NOTICE, "cannot scan link '%s'", intf->if_name); - else - new_ap = (wireless_lan_used > num_ap); + for (i = 0; i < wireless_lan_used; i++) + wlans[i].scanned = B_FALSE; + new_ap_found = B_FALSE; + dprintf("starting scan on %s", ifname); + status = dladm_wlan_scan(linkid, (char *)ifname, get_scan_results); + if (status == DLADM_STATUS_OK) { + dropped = clear_unscanned_entries(ifname); + } else { + dropped = B_FALSE; + syslog(LOG_NOTICE, "cannot scan link '%s'", ifname); + } + +scan_end: + /* Need to sample this global before clearing out scan lock */ + new_found = new_ap_found; + + if (pthread_mutex_lock(&wifi_mutex) == 0) { + if ((wip = find_wireless_if(ifname)) != NULL) { + wip->wi_scan_running = B_FALSE; + (void) update_connected_wlan(wip); + } + (void) pthread_mutex_unlock(&wifi_mutex); + } (void) pthread_mutex_lock(&wifi_init_mutex); - wifi_scan_intf = NULL; - (void) pthread_cond_signal(&wifi_init_cond); + wifi_scan_intf[0] = '\0'; + (void) pthread_cond_broadcast(&wifi_init_cond); (void) pthread_mutex_unlock(&wifi_init_mutex); - return (new_ap); + if (status == DLADM_STATUS_OK) + report_scan_complete(ifname, dropped || new_found, wlans, + wireless_lan_used); + + if (new_found) { + dprintf("new AP added: %s", ifname); + (void) np_queue_add_event(EV_NEWAP, ifname); + } } -/* ARGSUSED */ +/* + * Rescan all wireless interfaces. This routine intentionally does not hold + * wifi_mutex during the scan, as scans can take a long time to accomplish, and + * there may be more than one wireless interface. The counter is used to make + * sure that we don't run "forever" if the list is changing quickly. + */ static void -wireless_scan(struct interface *ifp, void *arg) +rescan_wifi_no_lock(void) { - if (ifp->if_type == IF_WIRELESS) { - dprintf("periodic_wireless_scan: %s", ifp->if_name); - if (scan_wireless_nets(ifp)) { - dprintf("new AP added: %s", ifp->if_name); - gen_newif_event(ifp); - } + uint_t cnt = 0; + wireless_if_t *wip; + char ifname[LIFNAMSIZ]; + + if (pthread_mutex_lock(&wifi_mutex) != 0) + return; + wip = (wireless_if_t *)wi_list.q_forw; + while (cnt++ < wi_link_count && wip != (wireless_if_t *)&wi_list) { + (void) strlcpy(ifname, wip->wi_name, sizeof (ifname)); + dprintf("periodic wireless scan: %s", ifname); + /* Even less than "very weak" */ + wip->wi_strength = 0; + wip->wi_scan_running = B_TRUE; + (void) pthread_mutex_unlock(&wifi_mutex); + + scan_wireless_nets(ifname); + + if (pthread_mutex_lock(&wifi_mutex) != 0) + return; + if ((wip = find_wireless_if(ifname)) == NULL) + wip = (wireless_if_t *)&wi_list; + else + wip = (wireless_if_t *)wip->wi_links.q_forw; + } + (void) pthread_mutex_unlock(&wifi_mutex); +} + +/* + * This thread is given the name of the interface to scan, and must free that + * name when done. + */ +static void * +scan_thread(void *arg) +{ + char *ifname = arg; + + scan_wireless_nets(ifname); + free(ifname); + + return (NULL); +} + +/* + * Launch a thread to scan the given wireless interface. We copy the interface + * name over to allocated storage because it's not possible to hand off a lock + * on the interface list to the new thread, and the caller's storage (our input + * argument) isn't guaranteed to be stable after we return to the caller. + */ +int +launch_wireless_scan(const char *ifname) +{ + int retv; + wireless_if_t *wip; + pthread_t if_thr; + pthread_attr_t attr; + char *winame; + + if ((winame = strdup(ifname)) == NULL) + return (ENOMEM); + + if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) { + free(winame); + return (retv); } + + if ((wip = find_wireless_if(ifname)) == NULL) { + retv = ENXIO; + } else if (wip->wi_scan_running) { + retv = EINPROGRESS; + } else { + (void) pthread_attr_init(&attr); + (void) pthread_attr_setdetachstate(&attr, + PTHREAD_CREATE_DETACHED); + retv = pthread_create(&if_thr, &attr, scan_thread, winame); + if (retv == 0) + wip->wi_scan_running = B_TRUE; + } + (void) pthread_mutex_unlock(&wifi_mutex); + + /* If thread not started, then discard the name. */ + if (retv != 0) + free(winame); + + return (retv); } +/* + * Caller does not hold wifi_mutex. + */ static boolean_t get_scan_results(void *arg, dladm_wlan_attr_t *attrp) { - dladm_wlan_secmode_t sec; + const char *ifname = arg; + wireless_if_t *wip; + struct wireless_lan *wlan; char essid_name[DLADM_STRSIZE]; char bssid_name[DLADM_STRSIZE]; - char strength[DLADM_STRSIZE]; + boolean_t retv; (void) dladm_wlan_essid2str(&attrp->wa_essid, essid_name); (void) dladm_wlan_bssid2str(&attrp->wa_bssid, bssid_name); - (void) dladm_wlan_strength2str(&attrp->wa_strength, strength); - - sec = attrp->wa_secmode; /* * Check whether ESSID is "hidden". @@ -556,25 +728,48 @@ get_scan_results(void *arg, dladm_wlan_attr_t *attrp) */ if (essid_name[0] == '\0') { if (known_wifi_nets_lookup(essid_name, bssid_name, - essid_name)) { + essid_name) && + dladm_wlan_str2essid(essid_name, &attrp->wa_essid) == + DLADM_STATUS_OK) { dprintf("Using ESSID %s with BSSID %s", essid_name, bssid_name); } } - if (!find_wlan_entry(arg, essid_name, bssid_name) && - add_wlan_entry(arg, essid_name, bssid_name, strength, sec)) { - return (B_TRUE); + if (pthread_mutex_lock(&wifi_mutex) != 0) + return (B_FALSE); + + if ((wip = find_wireless_if(ifname)) == NULL) { + (void) pthread_mutex_unlock(&wifi_mutex); + return (B_FALSE); } - return (B_FALSE); + + /* Remember the strongest we encounter */ + if (attrp->wa_strength > wip->wi_strength) + wip->wi_strength = attrp->wa_strength; + + wlan = find_wlan_entry(ifname, essid_name, bssid_name); + if (wlan != NULL) { + if (wlan->rescan) + new_ap_found = B_TRUE; + wlan->rescan = B_FALSE; + wlan->scanned = B_TRUE; + wlan->attrs = *attrp; + retv = B_TRUE; + } else if (add_wlan_entry(ifname, essid_name, bssid_name, attrp) != + NULL) { + retv = B_TRUE; + } else { + retv = B_FALSE; + } + (void) pthread_mutex_unlock(&wifi_mutex); + return (retv); } /* ARGSUSED */ void * periodic_wireless_scan(void *arg) { - datalink_id_t linkid; - /* * No periodic scan if the "-i" option is used to change the * interval to 0. @@ -585,73 +780,94 @@ periodic_wireless_scan(void *arg) for (;;) { int ret; dladm_wlan_linkattr_t attr; - llp_t *cur_llp; - struct interface *ifp; + char ifname[LIFNAMSIZ]; + libnwam_interface_type_t ift; + datalink_id_t linkid; ret = poll(NULL, 0, wlan_scan_interval * MILLISEC); + if (ret == -1) { + if (errno == EINTR) + continue; + syslog(LOG_INFO, "periodic_wireless_scan: poll failed"); + break; + } - /* - * 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. - */ - cur_llp = link_layer_profile; + /* Get current profile name, if any */ + llp_get_name_and_type(ifname, sizeof (ifname), &ift); /* * We do a scan if * * 1. There is no active profile. Or - * 2. We are now disconnected from the AP. Or + * 2. Profile is wireless and we're not connected to the AP. Or * 3. The signal strength falls below a certain specified level. */ - if (ret == 0) { - if (cur_llp != NULL) { - if (cur_llp->llp_type != IF_WIRELESS) - continue; + if (ifname[0] != '\0') { + if (ift != IF_WIRELESS) + continue; - if (dladm_name2info(cur_llp->llp_lname, &linkid, - NULL, NULL, NULL) != DLADM_STATUS_OK || - dladm_wlan_get_linkattr(linkid, &attr) != - DLADM_STATUS_OK) { - continue; - } + /* + * If these things fail, it means that our wireless + * link isn't viable. Proceed in that way. + */ + if (dladm_name2info(ifname, &linkid, NULL, NULL, + NULL) != DLADM_STATUS_OK || + dladm_wlan_get_linkattr(linkid, &attr) != + DLADM_STATUS_OK) { + attr.la_status = DLADM_WLAN_LINK_DISCONNECTED; + attr.la_wlan_attr.wa_strength = 0; + } + + if (attr.la_status == DLADM_WLAN_LINK_CONNECTED && + attr.la_wlan_attr.wa_strength > + wireless_scan_level) { + continue; + } + } + + /* Rescan the wireless interfaces */ + rescan_wifi_no_lock(); + if (ifname[0] != '\0') { + wireless_if_t *wip; + + /* + * If we're still connected and there's nothing better + * around, then there's no point in switching now. + */ + if (pthread_mutex_lock(&wifi_mutex) != 0) + continue; + if ((wip = find_wireless_if(ifname)) != NULL) { if (attr.la_status == DLADM_WLAN_LINK_CONNECTED && - attr.la_wlan_attr.wa_strength > - wireless_scan_level) { + wip->wi_strength <= + attr.la_wlan_attr.wa_strength) { + (void) pthread_mutex_unlock(& + wifi_mutex); continue; } - /* - * Clear the DHCP flags on this interface; - * this is a "fresh start" for the interface, - * so we should retry dhcp. - */ - ifp = get_interface(cur_llp->llp_lname); - if (ifp != NULL) - ifp->if_lflags &= ~IF_DHCPFLAGS; - - /* - * 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. - */ - llp_deactivate(); } - /* We should start from fresh. */ - clear_lan_entries(); - walk_interface(wireless_scan, NULL); - } else if (ret == -1) { - if (errno == EINTR) - continue; - syslog(LOG_INFO, "periodic_wireless_scan: poll failed"); - return (NULL); + (void) pthread_mutex_unlock(&wifi_mutex); + + /* + * Try to work around known driver bugs: if the driver + * says we're disconnected, then tell it to disconnect + * for sure. + */ + (void) dladm_wlan_disconnect(linkid); + + /* + * Deactivate the original AP. 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. + */ + (void) np_queue_add_event( + (attr.la_status == DLADM_WLAN_LINK_CONNECTED ? + EV_LINKFADE : EV_LINKDISC), ifname); } } - /* NOTREACHED */ return (NULL); } @@ -674,9 +890,8 @@ key_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp, dprintf("before: key_string_to_secobj_value: buf_len = %d", buf_len); if (buf_len == 0) { - syslog(LOG_ERR, - "key_string_to_secobj_value: empty key"); - return (-1); + /* length zero means "delete" */ + return (0); } if (buf[buf_len - 1] == '\n') @@ -738,13 +953,17 @@ key_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp, * 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. + * + * (Note that much of the system uses DLADM_WLAN_MAX_KEYNAME_LEN, which is 64 + * rather than 32, but that dladm_get_secobj will fail if a length greater than + * DLD_SECOBJ_NAME_MAX is seen, and that's 32. This is all horribly broken.) */ static void set_key_name(const char *essid, const char *bssid, char *name, size_t nsz) { int i, rtn, len; - if (bssid == NULL) + if (bssid[0] == '\0') rtn = snprintf(name, nsz, "nwam-%s", essid); else rtn = snprintf(name, nsz, "nwam-%s-%s", essid, bssid); @@ -771,7 +990,7 @@ store_key(struct wireless_lan *wlan) set_key_name(wlan->essid, wlan->bssid, obj_name, sizeof (obj_name)); dprintf("store_key: obj_name is %s", obj_name); - class = (wlan->sec_mode == DLADM_WLAN_SECMODE_WEP ? + class = (wlan->attrs.wa_secmode == DLADM_WLAN_SECMODE_WEP ? DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA); if (key_string_to_secobj_value(wlan->raw_key, obj_val, &obj_len, class) != 0) { @@ -779,6 +998,20 @@ store_key(struct wireless_lan *wlan) return (-1); } + /* we've validated the new key, so remove the old one */ + status = dladm_unset_secobj(obj_name, + DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST); + if (status != DLADM_STATUS_OK && status != DLADM_STATUS_NOTFOUND) { + syslog(LOG_ERR, "store_key: could not remove old secure object " + "'%s' for key: %s", obj_name, + dladm_status2str(status, errmsg)); + return (-1); + } + + /* if we're just deleting the key, then we're done */ + if (wlan->raw_key[0] == '\0') + return (0); + status = dladm_set_secobj(obj_name, class, obj_val, obj_len, DLADM_OPT_CREATE | DLADM_OPT_PERSIST | DLADM_OPT_ACTIVE); @@ -825,9 +1058,12 @@ retrieve_key(const char *essid, const char *bssid, dladm_secobj_class_t req) return (NULL); } - /* Set name appropriately to retrieve key for this WLAN */ - set_key_name(essid, bssid, cooked_key->wk_name, - DLADM_SECOBJ_NAME_MAX); + /* + * Set name appropriately to retrieve key for this WLAN. Note that we + * cannot use the actual wk_name buffer size, as it's two times too + * large for dladm_get_secobj. + */ + set_key_name(essid, bssid, cooked_key->wk_name, DLADM_SECOBJ_NAME_MAX); dprintf("retrieve_key: len = %d, object = %s\n", strlen(cooked_key->wk_name), cooked_key->wk_name); cooked_key->wk_len = sizeof (cooked_key->wk_val); @@ -875,66 +1111,111 @@ retrieve_key(const char *essid, const char *bssid, dladm_secobj_class_t req) return (cooked_key); } -/* Create the KNOWN_WIFI_NETS using info from the interface list. */ -void -create_known_wifi_nets_file(void) +/* + * Add an entry to known_wifi_nets file given the parameters. The caller holds + * wifi_mutex. + */ +static int +add_known_wifi_nets_file(const char *essid, const char *bssid) { - FILE *fp; - int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + int retv; + FILE *fp = NULL; + + dprintf("add_known_wifi_nets_file(%s, %s)", essid, bssid); /* Create the NWAM directory in case it does not exist. */ - if (mkdir(LLPDIR, dirmode) != 0) { - if (errno != EEXIST) { - syslog(LOG_ERR, "could not create %s: %m", LLPDIR); - return; - } - } - if ((fp = fopen(KNOWN_WIFI_NETS, "a+")) == NULL) { - syslog(LOG_ERR, "could not open %s: %m", KNOWN_WIFI_NETS); - return; + if (mkdir(LLPDIRNAME, LLPDIRMODE) != 0 && + errno != EEXIST) { + retv = errno; + syslog(LOG_ERR, "could not create %s: %m", LLPDIRNAME); + } else if ((fp = fopen(KNOWN_WIFI_NETS, "a+")) == NULL) { + retv = errno; + syslog(LOG_ERR, "fopen(%s) failed: %m", KNOWN_WIFI_NETS); + } else if (known_wifi_nets_lookup(essid, bssid, NULL)) { + retv = EEXIST; + } else { + /* now add this to the file */ + (void) fprintf(fp, "%s\t%s\n", essid, bssid); + retv = 0; } - dprintf("Creating %s", KNOWN_WIFI_NETS); - (void) fclose(fp); + if (fp != NULL) + (void) fclose(fp); + return (retv); } -/* - * Add an entry to known_wifi_nets file given the parameters. - */ -void -update_known_wifi_nets_file(const char *essid, const char *bssid) +static int +delete_known_wifi_nets_file(const char *essid, const char *bssid) { - FILE *fp; + FILE *fpin, *fpout; + char line[LINE_MAX]; + char *cp; + int retv; + size_t essidlen, bssidlen; + boolean_t found; - dprintf("update_known_wifi_nets_file(%s, %s)", essid, STRING(bssid)); - fp = fopen(KNOWN_WIFI_NETS, "a+"); - if (fp == NULL) { - if (errno != ENOENT) { - syslog(LOG_ERR, "fopen(%s) failed: %m", - KNOWN_WIFI_NETS); - return; + if ((fpin = fopen(KNOWN_WIFI_NETS, "r")) == NULL) + return (errno); + + if ((fpout = fopen(KNOWN_WIFI_TMP, "w")) == NULL) { + retv = errno; + (void) fclose(fpin); + return (retv); + } + + found = B_FALSE; + essidlen = strlen(essid); + bssidlen = strlen(bssid); + while (fgets(line, sizeof (line), fpin) != NULL) { + cp = line; + while (isspace(*cp)) + cp++; + + if (*cp == '#' || *cp == '\0' || + strncmp(essid, cp, essidlen) != 0 || + (cp[essidlen] != '\0' && !isspace(cp[essidlen]))) { + (void) fputs(line, fpout); + continue; } + /* skip over the essid to examine bssid */ + while (*cp != '\0' && !isspace(*cp)) + cp++; + while (isspace(*cp)) + cp++; + /* - * 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... + * Deleting with bssid empty means "all entries under this + * essid." As a result, deleting a wildcard entry for a bssid + * means deleting all entries for that bssid. */ - create_known_wifi_nets_file(); - fp = fopen(KNOWN_WIFI_NETS, "a"); - if (fp == NULL) { - syslog(LOG_ERR, "second fopen(%s) failed: %m", - KNOWN_WIFI_NETS); - return; + + if (bssidlen == 0 || + (strncmp(bssid, cp, bssidlen) == 0 && + (cp[bssidlen] == '\0' || isspace(cp[bssidlen])))) { + /* delete this entry */ + found = B_TRUE; + continue; } + + (void) fputs(line, fpout); } - /* now see if this info is already in the file */ - if (!known_wifi_nets_lookup(essid, bssid, NULL)) { - /* now add this to the file */ - (void) fprintf(fp, "%s\t%s\n", essid, - bssid == NULL ? "" : bssid); + + (void) fclose(fpin); + (void) fclose(fpout); + + if (found) { + if (rename(KNOWN_WIFI_TMP, KNOWN_WIFI_NETS) == 0) { + retv = 0; + } else { + retv = errno; + (void) unlink(KNOWN_WIFI_TMP); + } + } else { + retv = ENXIO; + (void) unlink(KNOWN_WIFI_TMP); } - (void) fclose(fp); + + return (retv); } /* @@ -959,16 +1240,9 @@ known_wifi_nets_lookup(const char *new_essid, const char *new_bssid, * essid\tbssid * (essid followed by tab followed by bssid) */ - fp = fopen(KNOWN_WIFI_NETS, "r+"); - if (fp == NULL) { - if (errno != ENOENT) { - syslog(LOG_ERR, "fopen(%s) failed: %m", - KNOWN_WIFI_NETS); - return (B_FALSE); - } - create_known_wifi_nets_file(); + fp = fopen(KNOWN_WIFI_NETS, "r"); + if (fp == NULL) return (B_FALSE); - } for (line_num = 1; fgets(line, sizeof (line), fp) != NULL; line_num++) { cp = line; @@ -1012,12 +1286,179 @@ known_wifi_nets_lookup(const char *new_essid, const char *new_bssid, return (found); } +static uint_t +extract_known_aps(FILE *fp, libnwam_known_ap_t *kap, char *sbuf, size_t *totstr) +{ + char line[LINE_MAX]; + char *cp; + char *tok[MAX_FIELDS]; + size_t accstr = 0; + uint_t count = 0; + char key[DLADM_SECOBJ_NAME_MAX]; + uint8_t keyval[DLADM_SECOBJ_VAL_MAX]; + dladm_secobj_class_t class; + uint_t keylen; + + while (fgets(line, sizeof (line), fp) != NULL) { + cp = line; + while (isspace(*cp)) + cp++; + + if (*cp == '#' || *cp == '\0') + continue; + + if (bufsplit(cp, MAX_FIELDS, tok) != MAX_FIELDS) + continue; + + if (totstr != NULL) + accstr += strlen(tok[BSSID]) + strlen(tok[ESSID]) + 2; + count++; + + if (kap != NULL) { + kap->ka_essid = strcpy(sbuf, tok[ESSID]); + sbuf += strlen(sbuf) + 1; + kap->ka_bssid = strcpy(sbuf, tok[BSSID]); + sbuf += strlen(sbuf) + 1; + set_key_name(tok[ESSID], tok[BSSID], key, sizeof (key)); + keylen = sizeof (keyval); + if (dladm_get_secobj(key, &class, keyval, &keylen, + DLADM_OPT_ACTIVE) == DLADM_STATUS_OK) + kap->ka_haskey = B_TRUE; + else + kap->ka_haskey = B_FALSE; + kap++; + } + } + if (totstr != NULL) + *totstr = accstr; + return (count); +} + +libnwam_known_ap_t * +get_known_ap_list(size_t *kasizep, uint_t *countp) +{ + FILE *fp; + libnwam_known_ap_t *kap = NULL; + size_t kasize; + uint_t count; + int retv; + + if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) { + errno = retv; + return (kap); + } + if ((fp = fopen(KNOWN_WIFI_NETS, "r")) != NULL) { + count = extract_known_aps(fp, NULL, NULL, &kasize); + rewind(fp); + kasize += count * sizeof (*kap); + if (count != 0 && (kap = malloc(kasize)) != NULL) { + (void) extract_known_aps(fp, kap, (char *)(kap + count), + NULL); + *kasizep = kasize; + *countp = count; + } + (void) fclose(fp); + } + (void) pthread_mutex_unlock(&wifi_mutex); + return (kap); +} + +int +add_known_ap(const char *essid, const char *bssid) +{ + int retv; + char ifname[LIFNAMSIZ]; + libnwam_interface_type_t ift; + struct wireless_lan *wlan, *savedwlan; + + /* + * First check the current LLP. If there is one, then its connection + * state determines what to do after adding the known AP to the list. + * If not, then we act if there are no connected APs. + */ + llp_get_name_and_type(ifname, sizeof (ifname), &ift); + + if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) + return (retv); + + retv = add_known_wifi_nets_file(essid, bssid); + if (retv == 0 && (ift == IF_UNKNOWN || ift == IF_WIRELESS)) { + boolean_t any_connected, one_matches; + + /* + * If this is in our list of scanned APs and if no interface is + * connected, then we have a reevaluation event. + */ + any_connected = one_matches = B_FALSE; + for (wlan = wlans; wlan < wlans + wireless_lan_used; + wlan++) { + /* + * If LLP is selected, then ignore all others. Only + * the state of this one interface is at issue. + */ + if (ifname[0] != '\0' && + strcmp(ifname, wlan->wl_if_name) != 0) + continue; + if (wlan->connected) + any_connected = B_TRUE; + if (strcmp(essid, wlan->essid) == 0 && + (bssid[0] == '\0' || + strcmp(bssid, wlan->bssid) == 0)) { + one_matches = B_TRUE; + savedwlan = wlan; + } + } + if (!any_connected && one_matches) { + (void) np_queue_add_event(EV_RESELECT, + savedwlan->wl_if_name); + } + } + (void) pthread_mutex_unlock(&wifi_mutex); + return (retv); +} + +int +delete_known_ap(const char *essid, const char *bssid) +{ + int retv; + struct wireless_lan *wlan; + wireless_if_t *wip; + + if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) + return (retv); + + retv = delete_known_wifi_nets_file(essid, bssid); + if (retv == 0) { + for (wlan = wlans; wlan < wlans + wireless_lan_used; + wlan++) { + if (wlan->connected && + strcmp(essid, wlan->essid) == 0 && + (bssid[0] == '\0' || + strcmp(bssid, wlan->bssid) == 0)) { + wlan->connected = B_FALSE; + report_wlan_disconnect(wlan); + wip = find_wireless_if(wlan->wl_if_name); + if (wip != NULL) { + wip->wi_wireless_done = B_FALSE; + wip->wi_need_key = B_FALSE; + (void) dladm_wlan_disconnect(wip-> + wi_linkid); + } + (void) np_queue_add_event(EV_RESELECT, + wlan->wl_if_name); + } + } + } + (void) pthread_mutex_unlock(&wifi_mutex); + return (retv); +} + /* - * reqlan->essid is required (i.e., cannot be NULL) - * reqlan->bssid is optional (i.e., may be NULL) + * reqlan->essid is required (i.e., cannot be zero-length) + * reqlan->bssid is optional (i.e., may be zero-length) */ -boolean_t -connect_chosen_lan(struct wireless_lan *reqlan, struct interface *intf) +static return_vals_t +connect_chosen_lan(struct wireless_lan *reqlan, wireless_if_t *wip) { uint_t keycount; dladm_wlan_key_t *key; @@ -1026,44 +1467,50 @@ connect_chosen_lan(struct wireless_lan *reqlan, struct interface *intf) uint_t flags = DLADM_WLAN_CONNECT_NOSCAN; int timeout = DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT; char errmsg[DLADM_STRSIZE]; + return_vals_t rval; + + wip->wi_need_key = B_FALSE; (void) memset(&attr, 0, sizeof (attr)); /* try to apply essid selected by the user */ if (reqlan->essid == NULL) - return (B_FALSE); + return (FAILURE); dprintf("connect_chosen_lan(%s, %s, %s)", reqlan->essid, - STRING(reqlan->bssid), intf->if_name); + reqlan->bssid, wip->wi_name); /* If it is already connected to the required AP, just return. */ - if (check_wlan(intf, reqlan->essid)) - return (B_TRUE); + if (check_wlan(wip, reqlan->essid, NULL, B_TRUE)) + return (SUCCESS); if (dladm_wlan_str2essid(reqlan->essid, &attr.wa_essid) != DLADM_STATUS_OK) { syslog(LOG_ERR, "connect_chosen_lan: invalid ESSID '%s' for '%s'", - reqlan->essid, intf->if_name); - return (B_FALSE); + reqlan->essid, wip->wi_name); + return (FAILURE); } attr.wa_valid = DLADM_WLAN_ATTR_ESSID; - if (reqlan->bssid != NULL) { + if (reqlan->bssid[0] != '\0') { if (dladm_wlan_str2bssid(reqlan->bssid, &attr.wa_bssid) != DLADM_STATUS_OK) { syslog(LOG_ERR, "connect_chosen_lan: invalid BSSID '%s' for '%s'", - reqlan->bssid, intf->if_name); - return (B_FALSE); + reqlan->bssid, wip->wi_name); + return (FAILURE); } attr.wa_valid |= DLADM_WLAN_ATTR_BSSID; } /* First check for the key */ - if (NEED_ENC(reqlan->sec_mode)) { - get_user_key(reqlan); - if (reqlan->cooked_key == NULL) - return (B_FALSE); + if (NEED_ENC(reqlan->attrs.wa_secmode)) { + /* Note that this happens only for known APs from the list */ + if ((rval = get_user_key(reqlan)) != SUCCESS) { + if (rval == WAITING) + wip->wi_need_key = B_TRUE; + return (rval); + } attr.wa_valid |= DLADM_WLAN_ATTR_SECMODE; - attr.wa_secmode = reqlan->sec_mode; + attr.wa_secmode = reqlan->attrs.wa_secmode; key = reqlan->cooked_key; keycount = 1; dprintf("connect_chosen_lan: retrieved key"); @@ -1078,325 +1525,231 @@ connect_chosen_lan(struct wireless_lan *reqlan, struct interface *intf) * try a second time with just the ESSID. */ - status = dladm_wlan_connect(intf->if_linkid, &attr, timeout, key, + status = dladm_wlan_connect(wip->wi_linkid, &attr, timeout, key, keycount, flags); dprintf("connect_chosen_lan: dladm_wlan_connect returned %s", dladm_status2str(status, errmsg)); - if (status == DLADM_STATUS_TIMEDOUT && reqlan->bssid != NULL) { + if (status == DLADM_STATUS_TIMEDOUT && reqlan->bssid[0] != '\0') { syslog(LOG_INFO, "connect_chosen_lan: failed for (%s, %s), " "trying again with just (%s)", reqlan->essid, reqlan->bssid, reqlan->essid); attr.wa_valid &= ~DLADM_WLAN_ATTR_BSSID; flags = 0; - status = dladm_wlan_connect(intf->if_linkid, &attr, timeout, + status = dladm_wlan_connect(wip->wi_linkid, &attr, timeout, key, keycount, flags); } - if (status != DLADM_STATUS_OK) { + if (status == DLADM_STATUS_OK) { + return (SUCCESS); + } else { syslog(LOG_ERR, "connect_chosen_lan: connect to '%s' failed on '%s': %s", - reqlan->essid, intf->if_name, + reqlan->essid, wip->wi_name, dladm_status2str(status, errmsg)); - return (B_FALSE); + return (FAILURE); } - return (B_TRUE); } /* * First attempt to connect to the network specified by essid. * If that fails, attempt to connect using autoconf. */ -static boolean_t -connect_or_autoconf(struct wireless_lan *reqlan, struct interface *intf) +static return_vals_t +connect_or_autoconf(struct wireless_lan *reqlan, wireless_if_t *wip) { - if (!connect_chosen_lan(reqlan, intf)) { + return_vals_t rval; + + rval = connect_chosen_lan(reqlan, wip); + if (rval == FAILURE) { + report_wlan_connect_fail(wip->wi_name); + reqlan->rescan = B_TRUE; syslog(LOG_WARNING, "Could not connect to chosen WLAN %s, going to auto-conf", reqlan->essid); - return (wlan_autoconf(intf)); + rval = (wlan_autoconf(wip) ? SUCCESS : FAILURE); } - return (B_TRUE); + return (rval); } /* - * 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. + * Check that the wireless LAN is connected to the desired ESSID/BSSID. This + * is used by the GUI to check for connectivity before doing anything + * destructive. */ -static int -zenity_height(int rows) -{ - return (((rows + 1) * 24) + 125); -} - -/* - * Construct an arg vector for zenity which displays a list of wlans. - * Additional options may be added at the end of the list of wlans with - * the "extra_rows" arg. - * - * Parameters include: - * lanlist: a linked list of wlans; one row per list node. - * nlans: the number of nodes in lanlist. - * title: the string that should be the zenity window title. - * width: the width of the zenity window. - * extra_rows: pointer to an array of strings; each string will - * appear on its own row in the table, in the first column - * of that row. - * nrows: the number of extra row strings. - * - * A pointer to the arg vector is returned; the caller must free that - * memory using free_argv(). - */ -static char ** -build_wlanlist_zargv(const struct wireless_lan *lanlist, int nlans, - const char *title, int width, const char **extra_rows, int nrows) +boolean_t +check_wlan_connected(const char *ifname, const char *essid, const char *bssid) { - int cur, i, j; - int zargc, hdrargc, wlanargc; - char **zargv; + wireless_if_t *wip; + boolean_t retv; - /* - * There are three sections of arguments: the initial args, specifying - * general formatting info; the column titles (one arg per column); - * and the row data (one row per wlan, plus any extra rows; and one - * arg per column per row). - */ - hdrargc = ZENITY_LIST_INIT_ARGS + ZENITY_COLUMNS_PER_WLAN; - wlanargc = (nlans + nrows) * ZENITY_COLUMNS_PER_WLAN; - zargc = hdrargc + wlanargc; - if ((zargv = alloc_argv(zargc, ZENITY_ARG_LEN)) == NULL) - return (NULL); + if (pthread_mutex_lock(&wifi_mutex) != 0) + return (B_FALSE); - /* initial args */ - cur = 0; - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, ZENITY); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--list"); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--title=%s", title); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--height=%d", - zenity_height(nlans + nrows)); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--width=%d", width); - - /* column titles */ - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--column=#"); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--column=ESSID"); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--column=BSSID"); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--column=%s", - gettext("Encryption")); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--column=%s", - gettext("Signal")); - - /* wlan rows */ - for (i = 0; i < nlans; i++) { - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "%d", i + 1); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "%s", - lanlist[i].essid); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "%s", - lanlist[i].bssid); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "%s", - WLAN_ENC(lanlist[i].sec_mode)); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "%s", - lanlist[i].signal_strength); - } - - /* extra rows */ - for (i = 0; i < nrows; i++) { - /* all columns are empty except the first */ - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, extra_rows[i]); - for (j = 0; j < ZENITY_COLUMNS_PER_WLAN - 1; j++) - *zargv[cur++] = '\0'; - } - - return (zargv); + if ((wip = find_wireless_if(ifname)) == NULL) { + retv = B_FALSE; + } else { + if (essid[0] == '\0' && bssid[0] == '\0') + essid = NULL; + retv = check_wlan(wip, essid, bssid, B_FALSE); + } + (void) pthread_mutex_unlock(&wifi_mutex); + return (retv); } -static return_vals_t -connect_to_new_wlan(const struct wireless_lan *lanlist, int nlans, - struct interface *intf) +/* + * This is the entry point for GUI "select access point" requests. We attempt + * to do what the GUI requested. If we fail, then there will be a new request + * enqueued for the GUI to act on. + * Returns: + * 0 - ok (or more data requested with new event) + * ENXIO - no such interface + * ENODEV - requested access point unknown + * EINVAL - failed to perform requested action + */ +int +set_specific_lan(const char *ifname, const char *essid, const char *bssid) { - int i, dlist_cnt; - int rtn; - char **zargv; - const char *title = gettext("Choose WiFi network you wish to activate"); - const char *extra_rows[2]; - struct wireless_lan *dlist, *reqlan; - boolean_t autoconf = B_FALSE; - - dprintf("connect_to_new_wlan(..., %d, %s)", nlans, intf->if_name); - - if (nlans == 0) { - display(gettext("No Wifi networks found; continuing in case " - "you know of any which do not broadcast.")); - } - - /* build list of wlans to be displayed */ - if ((dlist = calloc(nlans, sizeof (struct wireless_lan))) == NULL) - return (FAILURE); + libnwam_interface_type_t ift; + wireless_if_t *wip; + struct wireless_lan *wlan, local_wlan; + int retv; + boolean_t key_wait = B_FALSE; - for (i = 0, dlist_cnt = 0; i < nlans; i++) { - if ((lanlist[i].essid == NULL) || (lanlist[i].bssid == NULL)) { - syslog(LOG_WARNING, "wifi list entry %d broken: " - "essid %s, bssid %s; ignoring", i, - STRING(lanlist[i].essid), STRING(lanlist[i].bssid)); - continue; - } - /* - * Only use the interface which finds the AP to connect to it. - */ - if (strcmp(lanlist[i].wl_if_name, intf->if_name) != 0) { - dprintf("connect_to_new_wlan: wrong interface (%s) for " - "%s (should be %s)", intf->if_name, - lanlist[i].essid, lanlist[i].wl_if_name); - continue; - } + ift = get_if_type(ifname); + if (ift != IF_UNKNOWN && ift != IF_WIRELESS) + return (EINVAL); - dlist[dlist_cnt++] = lanlist[i]; - } + if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) + return (retv); - extra_rows[0] = gettext("Other"); - extra_rows[1] = gettext("Rescan"); - /* width = 500 is the result of trial-and-error testing */ - zargv = build_wlanlist_zargv(dlist, dlist_cnt, title, 500, extra_rows, - sizeof (extra_rows) / sizeof (extra_rows[0])); - if (zargv == NULL) { - free(dlist); - return (FAILURE); + if ((wip = find_wireless_if(ifname)) == NULL) { + retv = ENXIO; + goto done; } - /* present list to user and get selection */ - rtn = get_user_preference(zargv, extra_rows[0], extra_rows[1], - &reqlan, dlist); - 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); - rtn = TRY_AGAIN; - goto cleanup; - case -1: - reqlan = NULL; - break; - default: - /* common case: reqlan was set in get_user_preference() */ - break; + /* This is an autoconf request. */ + if (essid[0] == '\0' && bssid[0] == '\0') { + retv = (wlan_autoconf(wip) ? 0 : EINVAL); + goto done; } - if ((reqlan == NULL) || (reqlan->essid == NULL)) { - dprintf("did not get user preference; attempting autoconf"); - rtn = wlan_autoconf(intf) ? SUCCESS : FAILURE; - goto cleanup; + if ((wlan = find_wlan_entry(ifname, essid, bssid)) == NULL) { + local_wlan.essid = (char *)essid; + local_wlan.bssid = (char *)bssid; + wlan = &local_wlan; } - dprintf("get_user_preference() returned essid %s, bssid %s, encr %s", - reqlan->essid, STRING(reqlan->bssid), - WLAN_ENC(reqlan->sec_mode)); - /* set key before first time connection */ - if (NEED_ENC(reqlan->sec_mode) && reqlan->raw_key == NULL && - reqlan->cooked_key == NULL) - get_user_key(reqlan); + retv = 0; /* - * now attempt to connect to selection, backing - * off to autoconf if the connect fails + * now attempt to connect to selection */ - if (connect_chosen_lan(reqlan, intf)) { + switch (connect_chosen_lan(wlan, wip)) { + case WAITING: + key_wait = B_TRUE; + break; + + case SUCCESS: /* * Succeeded, so add entry to known_essid_list_file; - * but first make sure the reqlan->bssid isn't empty. + * but first make sure the wlan->bssid isn't empty. + * Note that empty bssid is never allocated. */ - if (reqlan->bssid == NULL) { + if (wlan->bssid[0] == '\0') { dladm_status_t status; dladm_wlan_linkattr_t attr; - char bssid[DLADM_STRSIZE]; + char lclbssid[DLADM_STRSIZE]; - status = dladm_wlan_get_linkattr(intf->if_linkid, + status = dladm_wlan_get_linkattr(wip->wi_linkid, &attr); if (status == DLADM_STATUS_OK) { (void) dladm_wlan_bssid2str( - &attr.la_wlan_attr.wa_bssid, bssid); - reqlan->bssid = strdup(bssid); + &attr.la_wlan_attr.wa_bssid, lclbssid); + wlan->bssid = strdup(lclbssid); } else { dprintf("failed to get linkattr after " - "connecting to %s", reqlan->essid); + "connecting to %s", wlan->essid); } } - update_known_wifi_nets_file(reqlan->essid, reqlan->bssid); - } else { - /* failed to connect; try auto-conf */ - syslog(LOG_WARNING, "Could not connect to chosen WLAN " - "%s; going to auto-conf", reqlan->essid); - autoconf = B_TRUE; - } - free_wireless_lan(reqlan); - - if (autoconf) - rtn = wlan_autoconf(intf) ? SUCCESS : FAILURE; - else - rtn = SUCCESS; - -cleanup: - free_argv(zargv); - free(dlist); - - return (rtn); -} - -struct wireless_lan * -prompt_for_visited(void) -{ - int dlist_cnt; - char **zargv; - const char *title = gettext("Choose from pre-visited WiFi network"); - const char *extra_rows[1]; - struct wireless_lan *req_conf, *dlist; - struct visited_wlans *vlp; - struct wireless_lan *wlp; - - /* build list of wlans to be displayed */ - dlist = calloc(visited_wlan_list->total, sizeof (struct wireless_lan)); - if (dlist == NULL) { - syslog(LOG_ERR, "prompt_for_visited: calloc failed"); - return (NULL); - } - dlist_cnt = 0; - for (vlp = visited_wlan_list->head; vlp != NULL; vlp = vlp->next) { - wlp = vlp->wifi_net; - if (wlp->essid == NULL || wlp->bssid == NULL) { - dprintf("Invalid essid/bssid values"); - continue; + if (wlan->bssid != NULL && wlan->bssid[0] != '\0') { + wlan->known = B_TRUE; + (void) add_known_wifi_nets_file(wlan->essid, + wlan->bssid); + if (wlan == &local_wlan && local_wlan.bssid != bssid) + free(local_wlan.bssid); + } else { + /* Don't leave it as NULL (for simplicity) */ + wlan->bssid = ""; + retv = EINVAL; } - dlist[dlist_cnt++] = *wlp; + break; + + default: + retv = EINVAL; + break; } - extra_rows[0] = gettext("Select from all available WiFi networks"); - /* width = 670 is the result of trial-and-error testing */ - zargv = build_wlanlist_zargv(dlist, dlist_cnt, title, 670, extra_rows, - sizeof (extra_rows) / sizeof (extra_rows[0])); - if (zargv == NULL) { - free(dlist); - return (NULL); +done: + if (retv == 0 && !update_connected_wlan(wip)) + retv = EINVAL; + if (retv != 0) { + /* + * Failed to connect. Set 'rescan' flag so that we treat this + * AP as new if it's seen again, because the wireless radio may + * have just been off briefly while we were trying to connect. + */ + syslog(LOG_WARNING, "Could not connect to chosen WLAN %s", + wlan->essid); + report_wlan_connect_fail(ifname); + wip->wi_need_key = B_FALSE; + wip->wi_wireless_done = B_FALSE; + wlan->rescan = B_TRUE; } + (void) pthread_mutex_unlock(&wifi_mutex); /* - * 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. + * If this is the selected profile, then go ahead and bring up IP now. */ - if (get_user_preference(zargv, extra_rows[0], NULL, &req_conf, - dlist) != 0) - req_conf = NULL; + if (retv == 0 && !key_wait) + (void) np_queue_add_event(EV_RESELECT, ifname); + return (retv); +} - free(dlist); - free_argv(zargv); - return (req_conf); +int +set_wlan_key(const char *ifname, const char *essid, const char *bssid, + const char *key) +{ + libnwam_interface_type_t ift; + struct wireless_lan *wlan; + int retv; + + ift = get_if_type(ifname); + if (ift == IF_UNKNOWN) + return (ENXIO); + if (ift != IF_WIRELESS) + return (EINVAL); + + if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) + return (retv); + + if ((wlan = find_wlan_entry(ifname, essid, bssid)) == NULL) + retv = ENODEV; + else if ((wlan->raw_key = strdup(key)) == NULL) + retv = ENOMEM; + else if (store_key(wlan) != 0) + retv = EINVAL; + else + retv = 0; + (void) pthread_mutex_unlock(&wifi_mutex); + + if (retv == 0) + retv = set_specific_lan(ifname, essid, bssid); + + return (retv); } static boolean_t -wlan_autoconf(struct interface *intf) +wlan_autoconf(const wireless_if_t *wip) { dladm_status_t status; boolean_t autoconf; @@ -1407,7 +1760,7 @@ wlan_autoconf(struct interface *intf) } /* If the NIC is already associated with something, just return. */ - if (check_wlan(intf, NULL)) + if (check_wlan(wip, NULL, NULL, B_TRUE)) return (B_TRUE); /* @@ -1415,348 +1768,75 @@ wlan_autoconf(struct interface *intf) * to cycle through WLANs detected in priority order, attempting * to connect. */ - status = dladm_wlan_connect(intf->if_linkid, NULL, + status = dladm_wlan_connect(wip->wi_linkid, NULL, DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT, NULL, 0, 0); if (status != DLADM_STATUS_OK) { char errmsg[DLADM_STRSIZE]; syslog(LOG_ERR, "wlan_autoconf: dladm_wlan_connect failed for '%s': %s", - intf->if_name, dladm_status2str(status, errmsg)); + wip->wi_name, dladm_status2str(status, errmsg)); 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 -already_in_visited_wlan_list(const struct wireless_lan *new_wlan) -{ - struct visited_wlans *vwlp; - - vwlp = visited_wlan_list->head; - while (vwlp != NULL && vwlp->wifi_net != NULL) { - if (strcmp(vwlp->wifi_net->essid, new_wlan->essid) == 0) { - dprintf("%s already in visited_wlan_list", - vwlp->wifi_net->essid); - return (B_TRUE); - } else { - vwlp = vwlp->next; - } - } - return (B_FALSE); -} - -static char * -get_zenity_response(char *const *zargv) -{ - int pfds[2]; - pid_t pid; - int status, i; - char inbuf[ZENITY_RTN_BUF_SIZE]; - ssize_t n, bytes_read; - char *rtnp = NULL; - - if (!valid_graphical_user(B_TRUE)) - return (NULL); - - if (pipe(pfds) < 0) { - syslog(LOG_ERR, "pipe() failed: %m"); - return (NULL); - } - if ((pid = fork()) < 0) { - syslog(LOG_ERR, "fork() failed: %m"); - return (NULL); - } else if (pid == 0) { - /* - * child: close read side of pipe, point stdout at write side - */ - (void) close(pfds[0]); - if (dup2(pfds[1], STDOUT_FILENO) < 0) { - syslog(LOG_ERR, "dup2() failed: %m"); - _exit(EXIT_FAILURE); - } - (void) close(pfds[1]); - (void) execv(ZENITY, zargv); - syslog(LOG_ERR, "execv() failed: %m"); - _exit(EXIT_FAILURE); - } else { - /* - * parent: close write side of pipe, read from read side - * to get zenity output from child. - */ - (void) close(pfds[1]); - (void) waitpid(pid, &status, 0); - if (WIFSIGNALED(status) || WIFSTOPPED(status)) { - i = WIFSIGNALED(status) ? WTERMSIG(status) : - WSTOPSIG(status); - syslog(LOG_ERR, "%s %s with signal %d (%s)", - ZENITY, (WIFSIGNALED(status) ? "terminated" : - "stopped"), i, strsignal(i)); - return (NULL); - } - bytes_read = 0; - do { - n = read(pfds[0], inbuf + bytes_read, - sizeof (inbuf) - bytes_read); - if (n < 0) { - if (errno != EINTR) { - syslog(LOG_ERR, "read() failed: %m"); - break; - } - } else { - bytes_read += n; - } - if (bytes_read == sizeof (inbuf)) { - bytes_read--; - syslog(LOG_WARNING, "get_zenity_response: too " - "much data; input read will be limited to " - "%d bytes", bytes_read); - break; - } - } while (n != 0); - (void) close(pfds[0]); - if (bytes_read == 0) { - syslog(LOG_ERR, "failed to read zenity output"); - return (NULL); - } - if (inbuf[bytes_read - 1] == '\n') - inbuf[bytes_read - 1] = '\0'; - else - inbuf[bytes_read] = '\0'; - - if ((rtnp = strdup(inbuf)) == NULL) - syslog(LOG_ERR, "get_zenity_response: strdup failed"); - } - - return (rtnp); -} - -/* - * get_user_preference(): Present a list of essid/bssid pairs to the - * 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. + * This function searches through the wlans[] array and determines which ones + * have been visited before. * - * Four possible return values: - * -1: No response from user, or other error. *req_lan is undefined. - * 0: essid/bssid pair was selected. *req_lan has these values in - * 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. - */ -static int -get_user_preference(char *const *zargv, const char *compare_other, - const char *compare_rescan, struct wireless_lan **req_lan, - const struct wireless_lan *list) -{ - char *response; - struct wireless_lan *wlp; - const struct wireless_lan *sel; - int answer; - - assert(req_lan != NULL); - wlp = calloc(1, sizeof (struct wireless_lan)); - if (wlp == NULL) { - syslog(LOG_ERR, "calloc failed"); - return (-1); - } - - response = get_zenity_response(zargv); - if (response == NULL) { - free(wlp); - return (-1); - } - if (compare_other != NULL && strcmp(response, compare_other) == 0) { - free(response); - free(wlp); - return (1); - } - if (compare_rescan != NULL && strcmp(response, compare_rescan) == 0) { - free(response); - free(wlp); - return (2); - } - answer = atoi(response); - if (answer <= 0) { - dprintf("%s returned invalid string", ZENITY); - free(response); - free(wlp); - return (-1); - } - sel = &list[answer - 1]; - - if ((wlp->essid = strdup(sel->essid)) == NULL) - goto dup_error; - - if ((sel->bssid != NULL) && ((wlp->bssid = strdup(sel->bssid)) == NULL)) - goto dup_error; - - wlp->sec_mode = sel->sec_mode; - - if ((sel->raw_key != NULL) && - ((wlp->raw_key = strdup(sel->raw_key)) == NULL)) - goto dup_error; - - if (sel->cooked_key != NULL) { - wlp->cooked_key = malloc(sizeof (dladm_wlan_key_t)); - if (wlp->cooked_key == NULL) - goto dup_error; - *(wlp->cooked_key) = *(sel->cooked_key); - } - - if ((sel->signal_strength != NULL) && - ((wlp->signal_strength = strdup(sel->signal_strength)) == NULL)) - goto dup_error; - - if ((sel->wl_if_name != NULL) && - ((wlp->wl_if_name = strdup(sel->wl_if_name)) == NULL)) - goto dup_error; - - dprintf("selected: %s, %s, %s, '%s', %s", wlp->essid, - STRING(wlp->bssid), WLAN_ENC(wlp->sec_mode), - STRING(wlp->signal_strength), STRING(wlp->wl_if_name)); - - free(response); - - *req_lan = wlp; - return (0); - -dup_error: - syslog(LOG_ERR, "get_user_preference: strdup failed"); - free_wireless_lan(wlp); - free(wlp); - free(response); - 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) -{ - int zargc, cur; - char **zargv; - char *response; - struct wireless_lan *wlp = NULL; - - /* - * build zenity 'entry' argv to get an ESSID: - * 'zenity --entry --title=foo --text=bar' - * Four args needed. - */ - zargc = 4; - if ((zargv = alloc_argv(zargc, ZENITY_ARG_LEN)) == NULL) - return (NULL); - - cur = 0; - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, ZENITY); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--entry"); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--title=%s", - gettext("Specify WiFi Network")); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--text=%s %s", - gettext("Enter"), "ESSID"); - - response = get_zenity_response(zargv); - if (response == NULL) - goto cleanup; - free_argv(zargv); - - wlp = calloc(1, sizeof (struct wireless_lan)); - if (wlp == NULL) { - syslog(LOG_ERR, "calloc failed: %m"); - goto cleanup; - } - wlp->essid = response; - wlp->sec_mode = DLADM_WLAN_SECMODE_NONE; - - /* - * build zenity 'list' argv to get security mode: - * 'zenity --list --title=foo --text=bar --column=baz <3 modes>' - * Eight args needed. - */ - zargc = 8; - if ((zargv = alloc_argv(zargc, ZENITY_ARG_LEN)) == NULL) { - /* assume the default security mode, "none" */ - return (wlp); - } - - cur = 0; - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, ZENITY); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--list"); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--title=%s", - gettext("Security")); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--text=%s", - gettext("Enter security mode")); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "--column=%s", - gettext("Type")); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "None"); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "WEP"); - (void) snprintf(zargv[cur++], ZENITY_ARG_LEN, "WPA"); - - response = get_zenity_response(zargv); - /* "none" was set as the default earlier */ - if (response != NULL) { - if (strcmp(response, "WEP") == 0) - wlp->sec_mode = DLADM_WLAN_SECMODE_WEP; - else if (strcmp(response, "WPA") == 0) - wlp->sec_mode = DLADM_WLAN_SECMODE_WPA; - } -cleanup: - free_argv(zargv); - free(response); - - return (wlp); -} - -/* - * Returns: - * B_TRUE if things go well - * B_FALSE if we were unable to connect to anything + * If exactly one has been visited before, and it has the highest signal + * strength, then we attempt to connect to it right away. + * + * In all other cases -- if none have been visited before, or more than one was + * visited, or if the one that was visited doesn't have the highest signal + * strength, or if the automatic connect attempt fails for any reason -- then + * we hand over the data to the GUI for resolution. The user will have to be + * prompted for a choice. + * + * If no GUI exists, we'll get back FAILURE (instead of WAITING), which will + * cause the autoconf mechanism to run instead. */ -boolean_t -handle_wireless_lan(struct interface *intf) +return_vals_t +handle_wireless_lan(const char *ifname) { - const struct wireless_lan *cur_wlans; - int i, num_wlans; - struct wireless_lan *req_conf = NULL; - boolean_t result; + wireless_if_t *wip; + struct wireless_lan *cur_wlan, *max_wlan; + struct wireless_lan *most_recent; + boolean_t many_present; dladm_wlan_strength_t strongest = DLADM_WLAN_STRENGTH_VERY_WEAK; - dladm_wlan_strength_t strength; return_vals_t connect_result; -start_over: - if (visited_wlan_list == NULL) { - if ((visited_wlan_list = calloc(1, - sizeof (struct visited_wlans_list))) == NULL) { - syslog(LOG_ERR, "handle_wireless_lan: calloc failed"); - 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) + if (pthread_mutex_lock(&wifi_init_mutex) != 0) + return (FAILURE); + while (wifi_scan_intf[0] != '\0') (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); (void) pthread_mutex_unlock(&wifi_init_mutex); - (void) pthread_mutex_lock(&wifi_mutex); - num_wlans = wireless_lan_used; - cur_wlans = wlans; + if (pthread_mutex_lock(&wifi_mutex) != 0) + return (FAILURE); + + if ((wip = find_wireless_if(ifname)) == NULL) { + connect_result = FAILURE; + goto finished; + } + + if (wip->wi_wireless_done) { + dprintf("handle_wireless_lan: skipping policy scan; done"); + connect_result = SUCCESS; + goto finished; + } + + dprintf("handle_wireless_lan: starting policy scan"); + cur_wlan = wlans; + max_wlan = wlans + wireless_lan_used; + most_recent = NULL; + many_present = B_FALSE; /* * Try to see if any of the wifi nets currently available @@ -1765,140 +1845,143 @@ start_over: * 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; - + for (; cur_wlan < max_wlan; cur_wlan++) { /* Find the AP with the highest signal. */ - if (dladm_wlan_str2strength(cur_wlans[i].signal_strength, - &strength) != DLADM_STATUS_OK) { - continue; - } - if (strength > strongest) - strongest = strength; + if (cur_wlan->attrs.wa_strength > strongest) + strongest = cur_wlan->attrs.wa_strength; - if (!known_wifi_nets_lookup(cur_wlans[i].essid, - cur_wlans[i].bssid, NULL)) - continue; + if (known_wifi_nets_lookup(cur_wlan->essid, cur_wlan->bssid, + NULL)) + cur_wlan->known = B_TRUE; - 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", - cur_wlans[i].essid, STRING(cur_wlans[i].bssid)); - - new_wlan = calloc(1, sizeof (struct visited_wlans)); - if (new_wlan == NULL) { - syslog(LOG_ERR, "handle_wireless_lan: calloc failed"); - result = B_FALSE; - connect_result = FAILURE; - goto all_done; - } - new_wlan->wifi_net = calloc(1, sizeof (struct wireless_lan)); - if (new_wlan->wifi_net == NULL) { - free(new_wlan); - syslog(LOG_ERR, "handle_wireless_lan: calloc failed"); - result = B_FALSE; - connect_result = FAILURE; - goto all_done; - } - new_wlan->wifi_net->essid = strdup(cur_wlans[i].essid); - new_wlan->wifi_net->bssid = strdup(cur_wlans[i].bssid); - new_wlan->wifi_net->raw_key = NULL; - new_wlan->wifi_net->cooked_key = NULL; - new_wlan->wifi_net->sec_mode = cur_wlans[i].sec_mode; - new_wlan->wifi_net->signal_strength = - strdup(cur_wlans[i].signal_strength); - new_wlan->wifi_net->wl_if_name = - strdup(cur_wlans[i].wl_if_name); - if (new_wlan->wifi_net->essid == NULL || - new_wlan->wifi_net->bssid == NULL || - new_wlan->wifi_net->signal_strength == NULL || - new_wlan->wifi_net->wl_if_name == NULL) { - syslog(LOG_ERR, "handle_wireless_lan: strdup failed"); - free_wireless_lan(new_wlan->wifi_net); - free(new_wlan->wifi_net); - free(new_wlan); - result = B_FALSE; - connect_result = FAILURE; - goto all_done; + if (cur_wlan->known || cur_wlan->connected) { + /* + * The ESSID comparison here mimics what the "already + * in visited wlan list" function once did, but + * slightly better as we also pay attention to signal + * strength to pick the best of the duplicates. + */ + if (most_recent == NULL) { + most_recent = cur_wlan; + } else if (strcmp(cur_wlan->essid, + most_recent->essid) != 0) { + many_present = B_TRUE; + } else if (cur_wlan->attrs.wa_strength > + most_recent->attrs.wa_strength) { + if (most_recent->connected) { + (void) dladm_wlan_disconnect( + wip->wi_linkid); + most_recent->connected = B_FALSE; + report_wlan_disconnect(most_recent); + wip->wi_wireless_done = B_FALSE; + } + most_recent = cur_wlan; + } } - new_wlan->next = visited_wlan_list->head; - visited_wlan_list->head = new_wlan; - visited_wlan_list->total++; + /* Reset any security information we may have had. */ + free(cur_wlan->raw_key); + cur_wlan->raw_key = NULL; + free(cur_wlan->cooked_key); + cur_wlan->cooked_key = NULL; } - if (visited_wlan_list->total == 1) { - struct wireless_lan *target = visited_wlan_list->head->wifi_net; - - /* - * 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. - */ - if (dladm_wlan_str2strength(target->signal_strength, - &strength) == DLADM_STATUS_OK) { - if (strength < strongest) - goto connect_any; - } - result = connect_or_autoconf(target, intf); - connect_result = result ? SUCCESS : FAILURE; - - } else if (visited_wlan_list->total > 1) { - /* - * more than one previously visited wifi nets seen. - * prompt user for which one should we connect to - */ - if ((req_conf = prompt_for_visited()) != NULL) { - result = connect_or_autoconf(req_conf, intf); - connect_result = result ? SUCCESS : FAILURE; + if (most_recent != NULL && !many_present && + most_recent->attrs.wa_strength >= strongest) { + if (most_recent->connected) { + dprintf("%s already connected to %s", ifname, + most_recent->essid); + connect_result = SUCCESS; } else { - /* - * The user didn't make a choice; offer the full list. - */ - connect_result = connect_to_new_wlan(cur_wlans, - num_wlans, intf); - result = (connect_result == SUCCESS); + dprintf("%s auto-connect to %s", ifname, + most_recent->essid); + connect_result = connect_or_autoconf(most_recent, wip); } + } else if (request_wlan_selection(ifname, wlans, wireless_lan_used)) { + dprintf("%s is unknown and not connected; requested help", + ifname); + connect_result = WAITING; } else { -connect_any: - /* last case, no previously visited wlan found */ - connect_result = connect_to_new_wlan(cur_wlans, num_wlans, - intf); - result = (connect_result == SUCCESS); + dprintf("%s has no connected AP or GUI; try auto", ifname); + connect_result = wlan_autoconf(wip) ? SUCCESS : FAILURE; } -all_done: - /* - * We locked down the list above; free it now that we're done. - */ +finished: + if (connect_result == SUCCESS && !update_connected_wlan(wip)) + connect_result = FAILURE; (void) pthread_mutex_unlock(&wifi_mutex); - if (visited_wlan_list != NULL) { - struct visited_wlans *vwlp = visited_wlan_list->head, *next; - - for (; vwlp != NULL; vwlp = next) { - if (vwlp->wifi_net != NULL) { - free_wireless_lan(vwlp->wifi_net); - free(vwlp->wifi_net); - vwlp->wifi_net = NULL; + + return (connect_result); +} + +void +disconnect_wlan(const char *ifname) +{ + wireless_if_t *wip; + struct wireless_lan *wlan; + + if (pthread_mutex_lock(&wifi_mutex) == 0) { + if ((wip = find_wireless_if(ifname)) != NULL) { + wip->wi_wireless_done = B_FALSE; + wip->wi_need_key = B_FALSE; + (void) dladm_wlan_disconnect(wip->wi_linkid); + } + for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { + if (strcmp(ifname, wlan->wl_if_name) == 0 && + wlan->connected) { + wlan->connected = B_FALSE; + report_wlan_disconnect(wlan); } - next = vwlp->next; - free(vwlp); } - free(visited_wlan_list); - visited_wlan_list = NULL; + (void) pthread_mutex_unlock(&wifi_mutex); } - if (req_conf != NULL) { - free_wireless_lan(req_conf); - free(req_conf); - req_conf = NULL; +} + +void +get_wireless_state(const char *ifname, boolean_t *need_wlan, + boolean_t *need_key) +{ + wireless_if_t *wip; + + *need_wlan = *need_key = B_FALSE; + if (pthread_mutex_lock(&wifi_mutex) == 0) { + if ((wip = find_wireless_if(ifname)) != NULL) { + *need_key = wip->wi_need_key; + if (!wip->wi_need_key && !wip->wi_wireless_done) + *need_wlan = B_TRUE; + } + (void) pthread_mutex_unlock(&wifi_mutex); } - if (connect_result == TRY_AGAIN) { - dprintf("end of handle_wireless_lan() TRY_AGAIN"); - goto start_over; +} + +void +print_wireless_status(void) +{ + wireless_if_t *wip; + struct wireless_lan *wlan; + + if (pthread_mutex_lock(&wifi_mutex) == 0) { + for (wip = (wireless_if_t *)wi_list.q_forw; + wip != (wireless_if_t *)&wi_list; + wip = (wireless_if_t *)wip->wi_links.q_forw) { + dprintf("WIF %s linkid %d scan %srunning " + "wireless %sdone %sneed key strength %d", + wip->wi_name, wip->wi_linkid, + wip->wi_scan_running ? "" : "not ", + wip->wi_wireless_done ? "" : "not ", + wip->wi_need_key ? "" : "don't ", + wip->wi_strength); + } + for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) { + dprintf("WLAN I/F %s ESS %s BSS %s signal %s key %sset " + "%sknown %sconnected %sscanned", + wlan->wl_if_name, wlan->essid, wlan->bssid, + wlan->signal_strength, + wlan->raw_key == NULL ? "un" : "", + wlan->known ? "" : "not ", + wlan->connected ? "" : "not ", + wlan->scanned ? "" : "not "); + } + (void) pthread_mutex_unlock(&wifi_mutex); } - return (result); } diff --git a/usr/src/cmd/svc/milestone/network-physical.xml b/usr/src/cmd/svc/milestone/network-physical.xml index 9995b0d48d..ae967e1e1a 100644 --- a/usr/src/cmd/svc/milestone/network-physical.xml +++ b/usr/src/cmd/svc/milestone/network-physical.xml @@ -1,7 +1,7 @@ <?xml version="1.0"?> <!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> <!-- - Copyright 2007 Sun Microsystems, Inc. All rights reserved. + Copyright 2008 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. CDDL HEADER START @@ -23,8 +23,6 @@ CDDL HEADER END - ident "%Z%%M% %I% %E% SMI" - NOTE: This service manifest is not editable; its contents will be overwritten by package or patch operations, including operating system upgrade. Make customizations in a different @@ -111,11 +109,10 @@ <stability value='Unstable' /> <propval name='debug' type='boolean' value='false' /> <propval name='use_net_svc' type='boolean' value='true' /> - <propval name='popup_info' type='boolean' value='true' /> - <propval name='popup_query' type='boolean' value='true' /> <propval name='autoconf' type='boolean' value='false' /> <propval name='dhcp_wait_time' type='count' value='60' /> <propval name='scan_interval' type='count' value='120' /> + <propval name='idle_time' type='count' value='10' /> <propval name='value_authorization' type='astring' value='solaris.smf.value.nwam' /> </property_group> diff --git a/usr/src/cmd/svc/shell/net_include.sh b/usr/src/cmd/svc/shell/net_include.sh index 989a780384..51c87a40a8 100644 --- a/usr/src/cmd/svc/shell/net_include.sh +++ b/usr/src/cmd/svc/shell/net_include.sh @@ -23,8 +23,6 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -#ident "%Z%%M% %I% %E% SMI" -# # Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T. # All rights reserved. # @@ -595,8 +593,8 @@ net_reconfigure () # Is this a reconfigure boot? If not, then there's nothing # for us to do. # - svcprop -q -p system/reconfigure system/svc/restarter:default - if [ $? -ne 0 ]; then + reconfig=`svcprop -c -p system/reconfigure system/svc/restarter:default` + if [ $? -ne 0 -o "$reconfig" = false ]; then return 0 fi diff --git a/usr/src/head/auth_list.h b/usr/src/head/auth_list.h index 4931ac9519..76f0ceb47a 100644 --- a/usr/src/head/auth_list.h +++ b/usr/src/head/auth_list.h @@ -42,11 +42,12 @@ extern "C" { #define CRONUSER_AUTH "solaris.jobs.user" #define DEFAULT_DEV_ALLOC_AUTH "solaris.device.allocate" #define DEVICE_REVOKE_AUTH "solaris.device.revoke" +#define LINK_SEC_AUTH "solaris.network.link.security" #define MAILQ_AUTH "solaris.mail.mailq" +#define NET_AUTOCONF_AUTH "solaris.network.autoconf" #define SET_DATE_AUTH "solaris.system.date" #define WIFI_CONFIG_AUTH "solaris.network.wifi.config" #define WIFI_WEP_AUTH "solaris.network.wifi.wep" -#define LINK_SEC_AUTH "solaris.network.link.security" /* * Authorizations used by Trusted Solaris. diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile index b0cb3d2094..4905a502e7 100644 --- a/usr/src/lib/Makefile +++ b/usr/src/lib/Makefile @@ -122,6 +122,7 @@ SUBDIRS += \ libmapmalloc \ libmtmalloc \ libnls \ + libnwam \ libsmbios \ libtecla \ libumem \ @@ -383,6 +384,7 @@ HDRSUBDIRS= \ libmtmalloc \ libnvpair \ libnsl \ + libnwam \ libpam \ libpctx \ libpicl \ @@ -523,6 +525,7 @@ libinetcfg: libnsl libsocket libdlpi libkmf: libcryptoutil pkcs11 openssl libnsl: libmd5 libscf libmapid: libresolv +libnwam: libdoor libuuid: libdlpi libinetutil: libsocket libsecdb: libnsl diff --git a/usr/src/lib/libbsm/audit_event.txt b/usr/src/lib/libbsm/audit_event.txt index 848ace7e37..2be51f5c53 100644 --- a/usr/src/lib/libbsm/audit_event.txt +++ b/usr/src/lib/libbsm/audit_event.txt @@ -22,8 +22,6 @@ # # CDDL HEADER END # -# ident "%Z%%M% %I% %E% SMI" -# # Audit Event Database # # File Format: @@ -499,6 +497,11 @@ 6291:AUE_smf_change_prop:change service instance property:as 6292:AUE_smf_delete_prop:delete service instance property:as # +# nwamd(1M) events +# +6300:AUE_nwam_attach:attach nwam user:ss +6301:AUE_nwam_detach:detach nwam user:ss +# # Trusted Extensions events: # 9035:AUE_sl_change:Workspace label change:ap diff --git a/usr/src/lib/libbsm/common/adt.xml b/usr/src/lib/libbsm/common/adt.xml index 19dd0ec37e..ada693bf8d 100644 --- a/usr/src/lib/libbsm/common/adt.xml +++ b/usr/src/lib/libbsm/common/adt.xml @@ -1838,8 +1838,36 @@ Use is subject to license terms. </entry> </event> +<!-- NWAM events --> + + <event id="AUE_nwam_attach" header="0" idNo="97" omit="JNI"> + <title>Network Autoconfig Client</title> + <program>nwamd</program> + <see>nwamd(1M)</see> + <entry id="subject"> + <internal token="subject"/> + <external opt="none"/> + </entry> + <entry id="auth_used"> + <internal token="uauth"/> + <external opt="required" type="char *"/> + <comment>authorization used</comment> + </entry> + <entry id="return"> + <internal token="return"/> + <external opt="none"/> + </entry> + </event> + + <event id="AUE_nwam_detach" instance_of="AUE_generic_basic" header="0" + idNo="98" omit="JNI"> + <title>Network Autoconfig Client</title> + <program>nwamd</program> + <see>nwamd(1M)</see> + </event> + <!-- add new events here with the next higher idNo --> -<!-- Highest idNo is 96, so next is 97, then fix this comment --> +<!-- Highest idNo is 98, so next is 99, then fix this comment --> <!-- end of C Only events --> diff --git a/usr/src/lib/libnwam/Makefile b/usr/src/lib/libnwam/Makefile new file mode 100644 index 0000000000..9110a09447 --- /dev/null +++ b/usr/src/lib/libnwam/Makefile @@ -0,0 +1,52 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include $(SRC)/lib/Makefile.lib + +HDRS = libnwam.h +HDRDIR = common +SUBDIRS = $(MACH) + +all := TARGET = all +clean := TARGET = clean +clobber := TARGET = clobber +install := TARGET = install +lint := TARGET = lint + +.KEEP_STATE: + +all clean clobber install lint: $(SUBDIRS) + +install_h: $(ROOTHDRS) + +check: + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include $(SRC)/Makefile.msg.targ +include $(SRC)/lib/Makefile.targ diff --git a/usr/src/lib/libnwam/Makefile.com b/usr/src/lib/libnwam/Makefile.com new file mode 100644 index 0000000000..8ef4d2ba07 --- /dev/null +++ b/usr/src/lib/libnwam/Makefile.com @@ -0,0 +1,52 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +LIBRARY = libnwam.a +VERS = .1 +OBJECTS = door.o + +include ../../Makefile.lib +include ../../Makefile.rootfs + +LIBS = $(DYNLIB) $(LINTLIB) + +NWAMDIR= $(SRC)/cmd/cmd-inet/lib/nwamd +SRCDIR = ../common +SRCS = $(OBJECTS:%.o=$(SRCDIR)/%.c) + +$(LINTLIB):= SRCS = $(SRCDIR)/$(LINTSRC) + +LDLIBS += -ldoor -ldladm -lc + +CFLAGS += $(CCVERBOSE) +CPPFLAGS += -I$(SRCDIR) -I$(NWAMDIR) + +.KEEP_STATE: + +all: $(LIBS) + +lint: lintcheck + +include ../../Makefile.targ diff --git a/usr/src/lib/libnwam/common/door.c b/usr/src/lib/libnwam/common/door.c new file mode 100644 index 0000000000..a48dd69aa9 --- /dev/null +++ b/usr/src/lib/libnwam/common/door.c @@ -0,0 +1,748 @@ +/* + * 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 + * or http://www.opensolaris.org/os/licensing. + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * This file contains the library interface for the nwamd libdoor(3LIB) + * service. This library is intended for use by an external GUI utility to + * provide status information to users and allow control over nwam behavior in + * certain situations. + */ + +#include <stdlib.h> +#include <unistd.h> +#include <pthread.h> +#include <door.h> +#include <sys/types.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <libdladm.h> + +#include <libnwam.h> + +/* Special include files; data structures shared with nwamd */ +#include <defines.h> +#include <structures.h> + +static int door_fd = -1; + +/* + * This lock protects the library against descriptor leaks caused by multiple + * threads attempting to race at opening the door. It also protects the door + * users by preventing reinit while busy. + */ +static pthread_mutex_t door_lock = PTHREAD_MUTEX_INITIALIZER; +static uint_t door_users; + +#define BUFFER_SIZE 256 + +/* ARGSUSED */ +static void +door_cancel_handler(void *arg) +{ + (void) pthread_mutex_lock(&door_lock); + door_users--; + (void) pthread_mutex_unlock(&door_lock); +} + +/* + * This wraps around door_call(3C), and makes sure that interrupts and error + * cases are handled in a reasonable way and that we always have an accurate + * door user count. + */ +static int +make_door_call(nwam_door_cmd_t *cmd, void **bufp, size_t *size, + size_t *rsize) +{ + door_arg_t arg; + int retv; + + (void) memset(&arg, 0, sizeof (arg)); + arg.data_ptr = (char *)cmd; + arg.data_size = sizeof (*cmd); + arg.rbuf = *bufp; + arg.rsize = *rsize; + + /* + * In order to avoid blocking and starving out the init routine, we + * check here for a descriptor before attempting to take a lock. + */ + if (door_fd == -1) { + errno = EBADF; + return (-1); + } + + if ((retv = pthread_mutex_lock(&door_lock)) != 0) { + errno = retv; + return (-1); + } + pthread_cleanup_push(door_cancel_handler, NULL); + door_users++; + (void) pthread_mutex_unlock(&door_lock); + /* The door_call function doesn't restart, so take care of that */ + do { + errno = 0; + if ((retv = door_call(door_fd, &arg)) == 0) + break; + } while (errno == EINTR); + pthread_cleanup_pop(1); + + /* No legitimate door call on our server returns without data */ + if (retv == 0 && arg.data_size == 0) { + retv = -1; + errno = EBADF; + } + + *bufp = arg.rbuf; + *size = arg.data_size; + *rsize = arg.rsize; + + return (retv); +} + +/* + * This is a common clean-up function for the door-calling routines. It checks + * for the daemon's standard error return mechanism (single integer with an + * errno) and for the special case of an oversized return buffer. + */ +static int +handle_errors(int retv, void *dbuf, void *obuf, size_t dsize, size_t rsize) +{ + int err = errno; + + if (retv == 0 && dsize == sizeof (int) && (err = *(int *)dbuf) != 0) + retv = -1; + if (dbuf != obuf) + (void) munmap(dbuf, rsize); + errno = err; + return (retv); +} + +/* + * Convert the internal libdladm representation of WLAN attributes into a text + * representation that we can send to the client. The passed-in 'strbuf' + * parameter points to a buffer that's known to be large enough to hold all of + * the strings. + */ +static char * +wlan_convert(libnwam_wlan_attr_t *wla, dladm_wlan_attr_t *wa, char *strbuf) +{ + static const struct { + uint_t flag; + char *(*cvt)(void *, char *); + size_t offsf; + size_t offst; + } cvtable[] = { + { + DLADM_WLAN_ATTR_ESSID, + (char *(*)(void *, char *))dladm_wlan_essid2str, + offsetof(dladm_wlan_attr_t, wa_essid), + offsetof(libnwam_wlan_attr_t, wla_essid) + }, + { + DLADM_WLAN_ATTR_BSSID, + (char *(*)(void *, char *))dladm_wlan_bssid2str, + offsetof(dladm_wlan_attr_t, wa_bssid), + offsetof(libnwam_wlan_attr_t, wla_bssid) + }, + { + DLADM_WLAN_ATTR_SECMODE, + (char *(*)(void *, char *))dladm_wlan_secmode2str, + offsetof(dladm_wlan_attr_t, wa_secmode), + offsetof(libnwam_wlan_attr_t, wla_secmode) + }, + { + DLADM_WLAN_ATTR_STRENGTH, + (char *(*)(void *, char *))dladm_wlan_strength2str, + offsetof(dladm_wlan_attr_t, wa_strength), + offsetof(libnwam_wlan_attr_t, wla_strength) + }, + { + DLADM_WLAN_ATTR_MODE, + (char *(*)(void *, char *))dladm_wlan_mode2str, + offsetof(dladm_wlan_attr_t, wa_mode), + offsetof(libnwam_wlan_attr_t, wla_mode) + }, + { + DLADM_WLAN_ATTR_SPEED, + (char *(*)(void *, char *))dladm_wlan_speed2str, + offsetof(dladm_wlan_attr_t, wa_speed), + offsetof(libnwam_wlan_attr_t, wla_speed) + }, + { + DLADM_WLAN_ATTR_AUTH, + (char *(*)(void *, char *))dladm_wlan_auth2str, + offsetof(dladm_wlan_attr_t, wa_auth), + offsetof(libnwam_wlan_attr_t, wla_auth) + }, + { + DLADM_WLAN_ATTR_BSSTYPE, + (char *(*)(void *, char *))dladm_wlan_bsstype2str, + offsetof(dladm_wlan_attr_t, wa_bsstype), + offsetof(libnwam_wlan_attr_t, wla_bsstype) + }, + { 0, NULL, 0 } + }; + int i; + char **cptr; + + for (i = 0; cvtable[i].cvt != NULL; i++) { + /* LINTED: pointer alignment */ + cptr = (char **)((char *)wla + cvtable[i].offst); + if (wa->wa_valid & cvtable[i].flag) { + *cptr = cvtable[i].cvt((char *)wa + cvtable[i].offsf, + strbuf); + strbuf += strlen(strbuf) + 1; + } else { + *cptr = ""; + } + } + /* This one element is a simple integer, not a string */ + if (wa->wa_valid & DLADM_WLAN_ATTR_CHANNEL) + wla->wla_channel = wa->wa_channel; + return (strbuf); +} + +/* + * Wait for an event from the daemon and return it to the caller in allocated + * storage. + */ +libnwam_event_data_t * +libnwam_wait_event(void) +{ + libnwam_event_data_t *led = NULL; + int retv; + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + nwam_descr_event_t *nde; + boolean_t has_wlan_attrs; + char *str; + + cmd.ndc_type = ndcWaitEvent; + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + if (retv == 0 && cmd_size == sizeof (int)) + retv = *(int *)cmd_ret; + if (retv == 0 && cmd_size == sizeof (*nde)) { + nde = cmd_ret; + has_wlan_attrs = (nde->nde_type == deWlanKeyNeeded || + nde->nde_type == deWlanDisconnect || + nde->nde_type == deWlanConnected); + led = calloc(1, sizeof (*led) + strlen(nde->nde_interface) + 1 + + (has_wlan_attrs ? DLADM_STRSIZE * WLA_NUM_STRS : 0)); + if (led != NULL) { + led->led_type = nde->nde_type; + if (led->led_type == deInterfaceUp) { + led->led_v4address = nde->nde_v4address; + led->led_prefixlen = nde->nde_prefixlen; + } + if (led->led_type == deInterfaceDown || + led->led_type == deLLPUnselected) + led->led_cause = nde->nde_cause; + str = (char *)(led + 1); + (void) strcpy(led->led_interface = str, + nde->nde_interface); + str += strlen(str) + 1; + if (has_wlan_attrs) + (void) wlan_convert(&led->led_wlan, + &nde->nde_attrs, str); + } + } + if (cmd_ret != cmd_buf) + (void) munmap(cmd_ret, cmd_rsize); + if (led == NULL && retv > 0) + errno = retv; + return (led); +} + +/* Free an allocated event */ +void +libnwam_free_event(libnwam_event_data_t *led) +{ + free(led); +} + +/* + * Get a list of Lower-Layer Profiles (interfaces) in a single allocated array. + */ +libnwam_llp_t * +libnwam_get_llp_list(uint_t *numllp) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + const nwam_llp_data_t *nld; + const llp_t *llp, *maxllp; + size_t strsize; + libnwam_llp_t *nllp, *nllpret; + char *sbuf; + + *numllp = 0; + + cmd.ndc_type = ndcGetLLPList; + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + nld = cmd_ret; + if (retv != 0 || cmd_size < sizeof (*nld) || + cmd_size < sizeof (*nld) + nld->nld_count * sizeof (*llp)) { + if (cmd_ret != cmd_buf) + (void) munmap(cmd_ret, cmd_rsize); + errno = retv == 0 ? EBADF : retv; + return (NULL); + } + /* Figure room needed to return strings to caller */ + llp = (const llp_t *)(nld + 1); + maxllp = llp + nld->nld_count; + strsize = 0; + while (llp < maxllp) { + strsize += strlen(llp->llp_lname) + 1; + llp++; + } + nllpret = malloc(nld->nld_count * sizeof (*nllp) + strsize); + if (nllpret != NULL) { + nllp = nllpret; + sbuf = (char *)(nllp + nld->nld_count); + llp = (const llp_t *)(nld + 1); + /* Convert internal to external structures */ + while (llp < maxllp) { + nllp->llp_interface = strcpy(sbuf, llp->llp_lname); + sbuf += strlen(llp->llp_lname) + 1; + nllp->llp_pri = llp->llp_pri; + nllp->llp_type = llp->llp_type; + nllp->llp_ipv4src = llp->llp_ipv4src; + nllp->llp_primary = + strcmp(nld->nld_selected, llp->llp_lname) == 0; + nllp->llp_locked = + strcmp(nld->nld_locked, llp->llp_lname) == 0; + nllp->llp_link_failed = llp->llp_failed; + nllp->llp_dhcp_failed = llp->llp_dhcp_failed; + nllp->llp_link_up = llp->llp_link_up; + nllp->llp_need_wlan = llp->llp_need_wlan; + nllp->llp_need_key = llp->llp_need_key; + nllp++; + llp++; + } + *numllp = nld->nld_count; + } + if (cmd_ret != cmd_buf) + (void) munmap(cmd_ret, cmd_rsize); + return (nllpret); +} + +/* Free an LLP list */ +void +libnwam_free_llp_list(libnwam_llp_t *llp) +{ + free(llp); +} + +/* Set the priority for a single LLP */ +int +libnwam_set_llp_priority(const char *ifname, int prio) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + + cmd.ndc_type = ndcSetLLPPriority; + (void) strlcpy(cmd.ndc_interface, ifname, sizeof (cmd.ndc_interface)); + cmd.ndc_priority = prio; + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + return (handle_errors(retv, cmd_ret, cmd_buf, cmd_size, cmd_rsize)); +} + +/* Lock a single LLP as selected */ +int +libnwam_lock_llp(const char *ifname) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + + cmd.ndc_type = ndcLockLLP; + if (ifname != NULL) { + (void) strlcpy(cmd.ndc_interface, ifname, + sizeof (cmd.ndc_interface)); + } else { + cmd.ndc_interface[0] = '\0'; + } + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + return (handle_errors(retv, cmd_ret, cmd_buf, cmd_size, cmd_rsize)); +} + +/* Get an array listing the scanned WLANs (Access Points) and attributes */ +libnwam_wlan_t * +libnwam_get_wlan_list(uint_t *numwlans) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + struct wireless_lan *wllp, *maxwllp; + size_t count, strsize; + libnwam_wlan_t *wlan, *wlanret; + char *sbuf; + + cmd.ndc_type = ndcGetWlanList; + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + if (retv != 0 || cmd_size < sizeof (*wllp)) { + if (cmd_ret != cmd_buf) + (void) munmap(cmd_ret, cmd_rsize); + errno = retv == 0 ? EBADF : retv; + *numwlans = 0; + return (NULL); + } + /* Figure amount of storage needed for strings */ + wllp = cmd_ret; + count = cmd_size / sizeof (*wllp); + maxwllp = wllp + count; + strsize = 0; + while (wllp < maxwllp) { + strsize += strlen(wllp->wl_if_name) + 1; + wllp++; + } + wlanret = malloc(count * (sizeof (*wlan) + DLADM_STRSIZE * + WLA_NUM_STRS) + strsize); + if (wlanret != NULL) { + wlan = wlanret; + sbuf = (char *)(wlan + count); + wllp = cmd_ret; + /* Convert internal to external structures */ + while (wllp < maxwllp) { + wlan->wlan_interface = strcpy(sbuf, wllp->wl_if_name); + sbuf += strlen(wllp->wl_if_name) + 1; + wlan->wlan_known = wllp->known; + wlan->wlan_haskey = wllp->cooked_key != NULL; + wlan->wlan_connected = wllp->connected; + sbuf = wlan_convert(&wlan->wlan_attrs, &wllp->attrs, + sbuf); + wllp++; + wlan++; + } + } else { + count = 0; + } + *numwlans = count; + if (cmd_ret != cmd_buf) + (void) munmap(cmd_ret, cmd_rsize); + return (wlanret); +} + +/* Free array of WLANs */ +void +libnwam_free_wlan_list(libnwam_wlan_t *wlans) +{ + free(wlans); +} + +/* Get the non-volatile list of known user-specified Access Points */ +libnwam_known_ap_t * +libnwam_get_known_ap_list(uint_t *numkas) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + nwam_known_ap_t *nka; + libnwam_known_ap_t *kabuf, *kastart, *maxkabuf; + libnwam_known_ap_t *kap, *kapret; + char *sbuf; + uint_t count; + + cmd.ndc_type = ndcGetKnownAPList; + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + nka = cmd_ret; + if (retv != 0 || cmd_size < sizeof (*nka) || + nka->nka_count * sizeof (*kabuf) >= cmd_size) { + if (cmd_ret != cmd_buf) + (void) munmap(cmd_ret, cmd_rsize); + errno = retv == 0 ? EBADF : retv; + *numkas = 0; + return (NULL); + } + if ((kapret = malloc(cmd_size)) != NULL) { + kap = kapret; + sbuf = (char *)(kap + nka->nka_count); + kabuf = kastart = (libnwam_known_ap_t *)(nka + 1); + maxkabuf = kabuf + nka->nka_count; + cmd_size -= sizeof (*nka); + /* + * Buffer returned from daemon has string offsets in place of + * pointers; convert back to pointers for user. + */ + while (kabuf < maxkabuf) { + if ((uintptr_t)kabuf->ka_essid >= cmd_size || + memchr((char *)kastart + (uintptr_t)kabuf->ka_essid, + 0, cmd_size - (uintptr_t)kabuf->ka_essid) == NULL) + break; + kap->ka_essid = strcpy(sbuf, + (char *)kastart + (uintptr_t)kabuf->ka_essid); + sbuf += strlen(sbuf) + 1; + if ((uintptr_t)kabuf->ka_bssid >= cmd_size || + memchr((char *)kastart + (uintptr_t)kabuf->ka_bssid, + 0, cmd_size - (uintptr_t)kabuf->ka_bssid) == NULL) + break; + kap->ka_bssid = strcpy(sbuf, + (char *)kastart + (uintptr_t)kabuf->ka_bssid); + sbuf += strlen(sbuf) + 1; + kap->ka_haskey = kabuf->ka_haskey; + kabuf++; + kap++; + } + count = kap - kapret; + } else { + count = 0; + } + *numkas = count; + if (cmd_ret != cmd_buf) + (void) munmap(cmd_ret, cmd_rsize); + return (kapret); +} + +/* Free a Known AP list */ +void +libnwam_free_known_ap_list(libnwam_known_ap_t *kas) +{ + free(kas); +} + +/* + * Add a new AP to the "known" list so that we'll automatically connect. + * BSSID is optional. + */ +int +libnwam_add_known_ap(const char *essid, const char *bssid) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + + cmd.ndc_type = ndcAddKnownAP; + (void) strlcpy(cmd.ndc_essid, essid, sizeof (cmd.ndc_essid)); + (void) strlcpy(cmd.ndc_bssid, bssid, sizeof (cmd.ndc_bssid)); + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + return (handle_errors(retv, cmd_ret, cmd_buf, cmd_size, cmd_rsize)); +} + +/* + * Delete an AP from the "known" list so that we won't connect to it + * automatically. BSSID is optional. + */ +int +libnwam_delete_known_ap(const char *essid, const char *bssid) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + + cmd.ndc_type = ndcDeleteKnownAP; + (void) strlcpy(cmd.ndc_essid, essid, sizeof (cmd.ndc_essid)); + (void) strlcpy(cmd.ndc_bssid, bssid, sizeof (cmd.ndc_bssid)); + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + return (handle_errors(retv, cmd_ret, cmd_buf, cmd_size, cmd_rsize)); +} + +/* + * Select a particular Access Point (WLAN) for use on a given interface. This + * may disconnect from the current AP if the link is already connected. + */ +int +libnwam_select_wlan(const char *ifname, const char *essid, const char *bssid) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + + cmd.ndc_type = ndcSelectWlan; + (void) strlcpy(cmd.ndc_interface, ifname, sizeof (cmd.ndc_interface)); + (void) strlcpy(cmd.ndc_essid, essid, sizeof (cmd.ndc_essid)); + (void) strlcpy(cmd.ndc_bssid, bssid, sizeof (cmd.ndc_bssid)); + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + return (handle_errors(retv, cmd_ret, cmd_buf, cmd_size, cmd_rsize)); +} + +/* + * Set the encryption key needed for a given AP. The key string is cleartext. + */ +int +libnwam_wlan_key(const char *ifname, const char *essid, const char *bssid, + const char *key) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + + cmd.ndc_type = ndcWlanKey; + (void) strlcpy(cmd.ndc_interface, ifname, sizeof (cmd.ndc_interface)); + (void) strlcpy(cmd.ndc_essid, essid, sizeof (cmd.ndc_essid)); + (void) strlcpy(cmd.ndc_bssid, bssid, sizeof (cmd.ndc_bssid)); + (void) strlcpy(cmd.ndc_key, key, sizeof (cmd.ndc_key)); + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + return (handle_errors(retv, cmd_ret, cmd_buf, cmd_size, cmd_rsize)); +} + +/* Initiate wireless scan on the indicated interface */ +int +libnwam_start_rescan(const char *ifname) +{ + nwam_door_cmd_t cmd; + uintptr_t cmd_buf[BUFFER_SIZE]; + void *cmd_ret = cmd_buf; + size_t cmd_rsize = sizeof (cmd_buf); + size_t cmd_size; + int retv; + + cmd.ndc_type = ndcStartRescan; + (void) strlcpy(cmd.ndc_interface, ifname, sizeof (cmd.ndc_interface)); + retv = make_door_call(&cmd, &cmd_ret, &cmd_size, &cmd_rsize); + return (handle_errors(retv, cmd_ret, cmd_buf, cmd_size, cmd_rsize)); +} + +/* Shut down the library. */ +int +libnwam_fini(void) +{ + int retv; + + if ((retv = pthread_mutex_lock(&door_lock)) != 0) { + errno = retv; + return (-1); + } + if (door_fd != -1) { + retv = close(door_fd); + door_fd = -1; + } + (void) pthread_mutex_unlock(&door_lock); + return (retv); +} + +/* + * Initialize the library for use. Waittime is the number of seconds + * (approximate) to wait for the daemon to become available if it isn't ready + * immediately. This may be -1 to wait forever, or 0 to open the connection to + * the daemon without waiting (i.e., fail if not ready now). + */ +int +libnwam_init(int waittime) +{ + nwam_door_cmd_t cmd; + door_arg_t arg; + int newfd; + int retv; + + for (;;) { + if ((retv = pthread_mutex_lock(&door_lock)) != 0) { + errno = retv; + break; + } + if (door_users != 0) { + errno = EBUSY; + (void) pthread_mutex_unlock(&door_lock); + break; + } + if (door_fd != -1) { + (void) close(door_fd); + door_fd = -1; + } + newfd = open(DOOR_FILENAME, + O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_NOCTTY); + if (newfd != -1) { + /* Make a dummy call to make sure daemon is running */ + cmd.ndc_type = ndcNull; + (void) memset(&arg, 0, sizeof (arg)); + arg.data_ptr = (char *)&cmd; + arg.data_size = sizeof (cmd); + arg.rbuf = (char *)&retv; + arg.rsize = sizeof (retv); + if (door_call(newfd, &arg) == 0) { + if (arg.rbuf != (char *)&retv) + (void) munmap(arg.rbuf, arg.data_size); + if (arg.data_size == sizeof (int)) { + if (retv == 0) { + /* Call worked; we're done. */ + door_fd = newfd; + (void) pthread_mutex_unlock( + &door_lock); + return (0); + } else { + errno = retv; + } + } else { + errno = EINVAL; + } + /* + * Zero data means daemon terminated. + * Otherwise, this is a permanent error. No + * point in waiting around and retrying for a + * permanent problem. + */ + if (arg.data_size != 0) { + (void) pthread_mutex_unlock(&door_lock); + (void) close(newfd); + break; + } + } + (void) close(newfd); + } + (void) pthread_mutex_unlock(&door_lock); + if (waittime == 0) + break; + if (waittime > 0) + waittime--; + /* + * We could do something smarter here, but decline to for now. + * This is "good enough" for NWAM Phase 0.5. + */ + (void) sleep(1); + } + return (-1); +} diff --git a/usr/src/lib/libnwam/common/libnwam.h b/usr/src/lib/libnwam/common/libnwam.h new file mode 100644 index 0000000000..88e4afc8d4 --- /dev/null +++ b/usr/src/lib/libnwam/common/libnwam.h @@ -0,0 +1,171 @@ +/* + * 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 + * or http://www.opensolaris.org/os/licensing. + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBNWAM_H +#define _LIBNWAM_H + +#include <netinet/in.h> + +/* + * This file defines the programming interface for libnwam. It is a private + * (undocumented, subject to change) interface shared between the NWAM GUI and + * nwamd. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct libnwam_wlan_attr_s { + const char *wla_essid; + const char *wla_bssid; + const char *wla_secmode; + const char *wla_strength; + const char *wla_mode; + const char *wla_speed; + const char *wla_auth; + const char *wla_bsstype; + int wla_channel; +} libnwam_wlan_attr_t; + +/* Number of strings above; used for internal allocation purposes */ +#define WLA_NUM_STRS 8 + +/* + * The descriptive event types shared with nwamd and with the GUI client. With + * all events other than deInitial, led_interface is always valid. The other + * fields are present when indicated, and otherwise must be left unused. + */ +typedef enum libnwam_descr_evtype_e { + deInitial, /* no other fields; new active client */ + deInterfaceUp, /* led_v4address led_prefixlen */ + deInterfaceDown, /* led_cause */ + deInterfaceAdded, + deInterfaceRemoved, + deWlanConnectFail, + deWlanDisconnect, /* led_wlan */ + deWlanConnected, /* led_wlan */ + deLLPSelected, + deLLPUnselected, /* led_cause */ + deULPActivated, + deULPDeactivated, + deScanChange, + deScanSame, + deWlanKeyNeeded, /* led_wlan */ + deWlanSelectionNeeded +} libnwam_descr_evtype_t; + +typedef enum libnwam_diag_cause_e { + dcNone = 0, /* no cause */ + dcDHCP, /* DHCP left interface down or with zero addr */ + dcTimer, /* gave up on DHCP; switching to next best */ + dcUnplugged, /* interface lost RUNNING flag */ + dcUser, /* user changed priority */ + dcBetter, /* higher-priority interface became RUNNING */ + dcNewAP, /* scan completed on higher-priority i/f */ + dcGone, /* periodic wireless scan showed disconnect */ + dcFaded, /* periodic scan showed "very weak" signal */ + dcAllDown, /* all-but-one taken down (initial LLP) */ + dcUnwanted, /* another higher-priority interface is up */ + dcShutdown, /* daemon is being shut down */ + dcSelect, /* different AP selected (forced down/up) */ + dcRemoved, /* interface removed from system */ + dcFailed /* interface bring-up failed */ +} libnwam_diag_cause_t; + +typedef struct libnwam_event_data_s { + libnwam_descr_evtype_t led_type; + libnwam_diag_cause_t led_cause; + struct in_addr led_v4address; /* deInterfaceUp only */ + int led_prefixlen; /* deInterfaceUp only */ + libnwam_wlan_attr_t led_wlan; + char *led_interface; +} libnwam_event_data_t; + +typedef enum libnwam_ipv4src_e { + IPV4SRC_STATIC, + IPV4SRC_DHCP +} libnwam_ipv4src_t; + +typedef enum libnwam_interface_type_e { + IF_UNKNOWN, + IF_WIRED, + IF_WIRELESS, + IF_TUN +} libnwam_interface_type_t; + +typedef struct libnwam_llp_s { + const char *llp_interface; + int llp_pri; /* lower number => higher priority */ + libnwam_interface_type_t llp_type; + libnwam_ipv4src_t llp_ipv4src; + boolean_t llp_primary; /* selected primary interface */ + boolean_t llp_locked; /* selected is locked */ + boolean_t llp_link_failed; /* unusable due to link failure */ + boolean_t llp_dhcp_failed; /* unusable due to DHCP failure */ + boolean_t llp_link_up; /* datalink layer is up */ + boolean_t llp_need_wlan; /* wlan/AP not yet selected */ + boolean_t llp_need_key; /* wlan key not set */ +} libnwam_llp_t; + +typedef struct libnwam_wlan_s { + libnwam_wlan_attr_t wlan_attrs; + const char *wlan_interface; + boolean_t wlan_known; + boolean_t wlan_haskey; + boolean_t wlan_connected; +} libnwam_wlan_t; + +typedef struct libnwam_known_ap_s { + const char *ka_essid; + const char *ka_bssid; + boolean_t ka_haskey; +} libnwam_known_ap_t; + +extern libnwam_event_data_t *libnwam_wait_event(void); +extern void libnwam_free_event(libnwam_event_data_t *); +extern libnwam_llp_t *libnwam_get_llp_list(uint_t *); +extern void libnwam_free_llp_list(libnwam_llp_t *); +extern int libnwam_set_llp_priority(const char *, int); +extern int libnwam_lock_llp(const char *); +extern libnwam_wlan_t *libnwam_get_wlan_list(uint_t *); +extern void libnwam_free_wlan_list(libnwam_wlan_t *); +extern libnwam_known_ap_t *libnwam_get_known_ap_list(uint_t *); +extern void libnwam_free_known_ap_list(libnwam_known_ap_t *); +extern int libnwam_add_known_ap(const char *, const char *); +extern int libnwam_delete_known_ap(const char *, const char *); +extern int libnwam_select_wlan(const char *, const char *, const char *); +extern int libnwam_wlan_key(const char *, const char *, const char *, + const char *); +extern int libnwam_start_rescan(const char *); +extern int libnwam_fini(void); +extern int libnwam_init(int); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBNWAM_H */ diff --git a/usr/src/lib/libnwam/common/llib-lnwam b/usr/src/lib/libnwam/common/llib-lnwam new file mode 100644 index 0000000000..396526a369 --- /dev/null +++ b/usr/src/lib/libnwam/common/llib-lnwam @@ -0,0 +1,29 @@ +/* + * 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 + * or http://www.opensolaris.org/os/licensing. + * 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 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* LINTLIBRARY */ +/* PROTOLIB1 */ + +#include <libnwam.h> diff --git a/usr/src/lib/libnwam/common/mapfile-vers b/usr/src/lib/libnwam/common/mapfile-vers new file mode 100644 index 0000000000..c3293a9228 --- /dev/null +++ b/usr/src/lib/libnwam/common/mapfile-vers @@ -0,0 +1,47 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +SUNWprivate_1.1 { + global: + libnwam_wait_event; + libnwam_free_event; + libnwam_get_llp_list; + libnwam_free_llp_list; + libnwam_set_llp_priority; + libnwam_lock_llp; + libnwam_get_wlan_list; + libnwam_free_wlan_list; + libnwam_get_known_ap_list; + libnwam_free_known_ap_list; + libnwam_add_known_ap; + libnwam_delete_known_ap; + libnwam_select_wlan; + libnwam_wlan_key; + libnwam_start_rescan; + libnwam_fini; + libnwam_init; + local: + *; +}; diff --git a/usr/src/lib/libnwam/i386/Makefile b/usr/src/lib/libnwam/i386/Makefile new file mode 100644 index 0000000000..1d2aa5ae17 --- /dev/null +++ b/usr/src/lib/libnwam/i386/Makefile @@ -0,0 +1,29 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# lib/libnwam/i386/Makefile + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libnwam/sparc/Makefile b/usr/src/lib/libnwam/sparc/Makefile new file mode 100644 index 0000000000..93ab0970d2 --- /dev/null +++ b/usr/src/lib/libnwam/sparc/Makefile @@ -0,0 +1,29 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# lib/libnwam/sparc/Makefile + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libsecdb/auth_attr.txt b/usr/src/lib/libsecdb/auth_attr.txt index f24cb88ed2..093e7a3b38 100644 --- a/usr/src/lib/libsecdb/auth_attr.txt +++ b/usr/src/lib/libsecdb/auth_attr.txt @@ -82,6 +82,7 @@ solaris.mms.io.read:::Read Permission for MMS Volumes::help=AuthMMSIORead.html solaris.mms.io.write:::Read and Write Permission for MMS Volumes::help=AuthMMSIOWrite.html # solaris.network.:::Network::help=NetworkHeader.html +solaris.network.autoconf:::Network Auto-Magic Configuration::help=NetworkAutoconf.html solaris.network.link.security:::Link Security::help=LinkSecurity.html solaris.network.wifi.config:::Wifi Config::help=WifiConfig.html solaris.network.wifi.wep:::Wifi Wep::help=WifiWep.html diff --git a/usr/src/lib/libsecdb/help/auths/Makefile b/usr/src/lib/libsecdb/help/auths/Makefile index ab610e0cc8..e2779afef0 100644 --- a/usr/src/lib/libsecdb/help/auths/Makefile +++ b/usr/src/lib/libsecdb/help/auths/Makefile @@ -108,6 +108,7 @@ HTMLENTS = \ SmfValueVscan.html \ SmfVscanStates.html \ SmfWpaStates.html \ + NetworkAutoconf.html \ NetworkHeader.html \ WifiConfig.html \ WifiWep.html \ diff --git a/usr/src/lib/libsecdb/help/auths/NetworkAutoconf.html b/usr/src/lib/libsecdb/help/auths/NetworkAutoconf.html new file mode 100644 index 0000000000..afdd492da9 --- /dev/null +++ b/usr/src/lib/libsecdb/help/auths/NetworkAutoconf.html @@ -0,0 +1,38 @@ +<html> + +<!-- + Copyright 2008 Sun Microsystems, Inc. All rights reserved. + Use is subject to license terms. + + 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 + or http://www.opensolaris.org/os/licensing. + 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 +--> + +<head> +<!-- +meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" +--> +</head> +<body> +When Network Auto-Magic Configuration is in the Authorizations +Included column, it grants permission to direct the activity of the +nwamd(1M) daemon through the GUI interface, allowing the user to set +link preferences and control Wireless Access Point selection. +</body> +</html> diff --git a/usr/src/lib/libsecdb/help/profiles/Makefile b/usr/src/lib/libsecdb/help/profiles/Makefile index aa6f9b2011..5b4a584789 100644 --- a/usr/src/lib/libsecdb/help/profiles/Makefile +++ b/usr/src/lib/libsecdb/help/profiles/Makefile @@ -21,8 +21,6 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -#ident "%Z%%M% %I% %E% SMI" -# # lib/libsecdb/help/profiles/Makefile # @@ -58,6 +56,7 @@ HTMLENTS = \ RtNDMPMngmnt.html \ RtNameServiceAdmin.html \ RtNameServiceSecure.html \ + RtNetAutoconf.html \ RtNetIPsec.html \ RtNetMngmnt.html \ RtNetSecure.html \ diff --git a/usr/src/lib/libsecdb/help/profiles/RtNetAutoconf.html b/usr/src/lib/libsecdb/help/profiles/RtNetAutoconf.html new file mode 100644 index 0000000000..28f5de6ccd --- /dev/null +++ b/usr/src/lib/libsecdb/help/profiles/RtNetAutoconf.html @@ -0,0 +1,37 @@ +<HTML> +<!-- + 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 + or http://www.opensolaris.org/os/licensing. + 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 2008 Sun Microsystems, Inc. All rights reserved. +-- Use is subject to license terms. +--> +<head> +<title></title> +</head> +<body> +When Network Autoconf is in the Rights Included column, it grants the +right to manage the behavior and priorities of the nwamd(1M) network +auto-magic daemon. +<p> +If Network Autoconf is grayed, then you are not entitled to Add or +Remove this right. +<p> +</body> +</html> diff --git a/usr/src/lib/libsecdb/prof_attr.txt b/usr/src/lib/libsecdb/prof_attr.txt index c3815f128a..4c09ff6b49 100644 --- a/usr/src/lib/libsecdb/prof_attr.txt +++ b/usr/src/lib/libsecdb/prof_attr.txt @@ -23,7 +23,6 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# # # /etc/security/prof_attr @@ -33,7 +32,7 @@ All:::Execute any command as the user or role:help=RtAll.html Audit Control:::Configure Solaris Auditing:auths=solaris.audit.config,solaris.jobs.admin;help=RtAuditCtrl.html Audit Review:::Review Solaris Auditing logs:auths=solaris.audit.read;help=RtAuditReview.html -Console User:::Manage System as the Console User:profiles=Suspend To RAM,Suspend To Disk,Brightness,CPU Power Management;auths=solaris.system.shutdown;help=RtConsUser.html +Console User:::Manage System as the Console User:profiles=Suspend To RAM,Suspend To Disk,Brightness,CPU Power Management,Network Autoconf;auths=solaris.system.shutdown;help=RtConsUser.html Contract Observer:::Reliably observe any/all contract events:help=RtContractObserver.html Device Management:::Control Access to Removable Media:auths=solaris.device.*;help=RtDeviceMngmnt.html Printer Management:::Manage printers, daemons, spooling:auths=solaris.print.*,solaris.label.print,solaris.smf.manage.discovery.printers.*,solaris.smf.value.discovery.printers.*;help=RtPrntAdmin.html @@ -59,7 +58,8 @@ MMS Administrator:::MMS Media Manager Administrator:auths=solaris.smf.manage.mms MMS Operator:::MMS Media Manager Operator:auths=solaris.smf.manage.mms,solaris.mms.media.*,solaris.mms.request.*,solaris.mms.device.state.*,solaris.mms.device.log.* MMS User:::MMS Tape User:auths=solaris.mms.io.* NDMP Management:::Manage the NDMP service:auths=solaris.smf.manage.ndmp,solaris.smf.value.ndmp,solaris.smf.read.ndmp;help=RtNdmpMngmnt.html -Network Management:::Manage the host and network configuration:auths=solaris.smf.manage.name-service-cache,solaris.smf.manage.bind,solaris.smf.value.routing,solaris.smf.manage.routing,solaris.smf.value.nwam,solaris.smf.manage.nwam,solaris.smf.manage.tnd,solaris.smf.manage.tnctl,solaris.smf.manage.wpa,solaris.smf.value.mdns,solaris.smf.manage.mdns;profiles=Network Wifi Management,Inetd Management;help=RtNetMngmnt.html +Network Autoconf:::Manage network auto-magic configuration via nwamd:auths=solaris.network.autoconf;help=RtNetAutoconf.html +Network Management:::Manage the host and network configuration:auths=solaris.smf.manage.name-service-cache,solaris.smf.manage.bind,solaris.smf.value.routing,solaris.smf.manage.routing,solaris.smf.value.nwam,solaris.smf.manage.nwam,solaris.smf.manage.tnd,solaris.smf.manage.tnctl,solaris.smf.manage.wpa,solaris.smf.value.mdns,solaris.smf.manage.mdns;profiles=Network Wifi Management,Inetd Management,Network Autoconf;help=RtNetMngmnt.html Network Security:::Manage network and host security:auths=solaris.smf.manage.ssh,solaris.smf.value.tnd;profiles=Network Wifi Security,Network Link Security,Network IPsec Management;help=RtNetSecure.html Network Wifi Management:::Manage wifi network configuration:auths=solaris.network.wifi.config;help=RtNetWifiMngmnt.html Network Wifi Security:::Manage wifi network security:auths=solaris.network.wifi.wep;help=RtNetWifiSecure.html diff --git a/usr/src/pkgdefs/Makefile b/usr/src/pkgdefs/Makefile index 221992069e..9c69626855 100644 --- a/usr/src/pkgdefs/Makefile +++ b/usr/src/pkgdefs/Makefile @@ -298,6 +298,8 @@ COMMON_SUBDIRS= \ SUNWnisu \ SUNWntpr \ SUNWntpu \ + SUNWnwamintr \ + SUNWnwamintu \ SUNWocf \ SUNWocfd \ SUNWocfh \ diff --git a/usr/src/pkgdefs/SUNW0on/prototype_com b/usr/src/pkgdefs/SUNW0on/prototype_com index 1b728b6d78..79add75d8f 100644 --- a/usr/src/pkgdefs/SUNW0on/prototype_com +++ b/usr/src/pkgdefs/SUNW0on/prototype_com @@ -18,7 +18,6 @@ # # CDDL HEADER END # -# # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # @@ -281,6 +280,7 @@ f none usr/lib/help/auths/locale/SmfValueSMB.html 444 root bin f none usr/lib/help/auths/locale/AuthReadSMB.html 444 root bin f none usr/lib/help/auths/locale/SmfValueVscan.html 444 root bin f none usr/lib/help/auths/locale/SmfWpaStates.html 444 root bin +f none usr/lib/help/auths/locale/NetworkAutoconf.html 444 root bin f none usr/lib/help/auths/locale/NetworkHeader.html 444 root bin f none usr/lib/help/auths/locale/WifiConfig.html 444 root bin f none usr/lib/help/auths/locale/WifiWep.html 444 root bin @@ -349,6 +349,7 @@ f none usr/lib/help/profiles/locale/RtMediaBkup.html 444 root bin f none usr/lib/help/profiles/locale/RtMediaRestore.html 444 root bin f none usr/lib/help/profiles/locale/RtNameServiceAdmin.html 444 root bin f none usr/lib/help/profiles/locale/RtNameServiceSecure.html 444 root bin +f none usr/lib/help/profiles/locale/RtNetAutoconf.html 444 root bin f none usr/lib/help/profiles/locale/RtNetIPsec.html 444 root bin f none usr/lib/help/profiles/locale/RtNetMngmnt.html 444 root bin f none usr/lib/help/profiles/locale/RtNetSecure.html 444 root bin diff --git a/usr/src/pkgdefs/SUNWcslr/prototype_com b/usr/src/pkgdefs/SUNWcslr/prototype_com index a427ce2385..7491ecf060 100644 --- a/usr/src/pkgdefs/SUNWcslr/prototype_com +++ b/usr/src/pkgdefs/SUNWcslr/prototype_com @@ -22,8 +22,6 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" -# # This required package information file contains a list of package contents. # The 'pkgmk' command uses this file to identify the contents of a package # and their location on the development machine when building the package. @@ -104,6 +102,7 @@ s none lib/libnsl.so=libnsl.so.1 f none lib/libnsl.so.1 755 root bin s none lib/libnvpair.so=libnvpair.so.1 f none lib/libnvpair.so.1 755 root bin +f none lib/libnwam.so.1 755 root bin s none lib/libpam.so=libpam.so.1 f none lib/libpam.so.1 755 root bin s none lib/libposix4.so.1=librt.so.1 diff --git a/usr/src/pkgdefs/SUNWcsu/prototype_com b/usr/src/pkgdefs/SUNWcsu/prototype_com index 80fce41035..ce8b6de5ae 100644 --- a/usr/src/pkgdefs/SUNWcsu/prototype_com +++ b/usr/src/pkgdefs/SUNWcsu/prototype_com @@ -18,7 +18,6 @@ # # CDDL HEADER END # -# # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # @@ -473,6 +472,7 @@ f none usr/lib/help/auths/locale/C/JobsGrant.html 444 root bin f none usr/lib/help/auths/locale/C/LoginEnable.html 444 root bin f none usr/lib/help/auths/locale/C/LoginHeader.html 444 root bin f none usr/lib/help/auths/locale/C/LoginRemote.html 444 root bin +f none usr/lib/help/auths/locale/C/NetworkAutoconf.html 444 root bin f none usr/lib/help/auths/locale/C/NetworkHeader.html 444 root bin f none usr/lib/help/auths/locale/C/PriAdmin.html 444 root bin f none usr/lib/help/auths/locale/C/ProfmgrHeader.html 444 root bin @@ -571,6 +571,7 @@ f none usr/lib/help/profiles/locale/C/RtMediaBkup.html 444 root bin f none usr/lib/help/profiles/locale/C/RtMediaRestore.html 444 root bin f none usr/lib/help/profiles/locale/C/RtNameServiceAdmin.html 444 root bin f none usr/lib/help/profiles/locale/C/RtNameServiceSecure.html 444 root bin +f none usr/lib/help/profiles/locale/C/RtNetAutoconf.html 444 root bin f none usr/lib/help/profiles/locale/C/RtNetIPsec.html 444 root bin f none usr/lib/help/profiles/locale/C/RtNetMngmnt.html 444 root bin f none usr/lib/help/profiles/locale/C/RtNetSecure.html 444 root bin diff --git a/usr/src/pkgdefs/SUNWnwamintr/Makefile b/usr/src/pkgdefs/SUNWnwamintr/Makefile new file mode 100644 index 0000000000..0ff58fdf86 --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintr/Makefile @@ -0,0 +1,35 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +DATAFILES += depend + +.KEEP_STATE: + +all: $(FILES) + +install: all pkg + +include ../Makefile.targ diff --git a/usr/src/pkgdefs/SUNWnwamintr/pkginfo.tmpl b/usr/src/pkgdefs/SUNWnwamintr/pkginfo.tmpl new file mode 100644 index 0000000000..8a69335727 --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintr/pkginfo.tmpl @@ -0,0 +1,43 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWnwamintr" +NAME="Solaris NWAM Internal Files - root" +ARCH="ISA" +VERSION="ONVERS,REV=0.0.0" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +SUNW_PKGTYPE="root" +MAXINST="1000" +CATEGORY="system" +DESC="Solaris NWAM internal files - root" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +CLASSES="none" +BASEDIR=/ +SUNW_PKGVERS="1.0" diff --git a/usr/src/pkgdefs/SUNWnwamintr/prototype_com b/usr/src/pkgdefs/SUNWnwamintr/prototype_com new file mode 100644 index 0000000000..835cbb48cd --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintr/prototype_com @@ -0,0 +1,47 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. +# + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# packaging files +i pkginfo +i copyright +i depend +# +# source locations relative to the prototype file +# +# SUNWnwamintr +# +d none lib 755 root bin +s none lib/libnwam.so=./libnwam.so.1 +f none lib/llib-lnwam 644 root bin +f none lib/llib-lnwam.ln 644 root bin diff --git a/usr/src/pkgdefs/SUNWnwamintr/prototype_i386 b/usr/src/pkgdefs/SUNWnwamintr/prototype_i386 new file mode 100644 index 0000000000..6f85cc6c1b --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintr/prototype_i386 @@ -0,0 +1,45 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. +# + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# List files which are i386 specific here +# +# source locations relative to the prototype file +# +# SUNWnwamintr +# diff --git a/usr/src/pkgdefs/SUNWnwamintr/prototype_sparc b/usr/src/pkgdefs/SUNWnwamintr/prototype_sparc new file mode 100644 index 0000000000..91200ec6a1 --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintr/prototype_sparc @@ -0,0 +1,45 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. +# + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# SUNWnwamintr +# diff --git a/usr/src/pkgdefs/SUNWnwamintu/Makefile b/usr/src/pkgdefs/SUNWnwamintu/Makefile new file mode 100644 index 0000000000..0ff58fdf86 --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintu/Makefile @@ -0,0 +1,35 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +DATAFILES += depend + +.KEEP_STATE: + +all: $(FILES) + +install: all pkg + +include ../Makefile.targ diff --git a/usr/src/pkgdefs/SUNWnwamintu/pkginfo.tmpl b/usr/src/pkgdefs/SUNWnwamintu/pkginfo.tmpl new file mode 100644 index 0000000000..bde594d6c6 --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintu/pkginfo.tmpl @@ -0,0 +1,43 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWnwamintu" +NAME="Solaris NWAM Internal Files - usr" +ARCH="ISA" +VERSION="ONVERS,REV=0.0.0" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +SUNW_PKGTYPE="usr" +MAXINST="1000" +CATEGORY="system" +DESC="Solaris NWAM internal files - usr" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +CLASSES="none" +BASEDIR=/ +SUNW_PKGVERS="1.0" diff --git a/usr/src/pkgdefs/SUNWnwamintu/prototype_com b/usr/src/pkgdefs/SUNWnwamintu/prototype_com new file mode 100644 index 0000000000..051cd0f76e --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintu/prototype_com @@ -0,0 +1,46 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. +# + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# packaging files +i pkginfo +i copyright +i depend +# +# source locations relative to the prototype file +# +# SUNWnwamintu +# +d none usr 755 root sys +d none usr/include 755 root bin +f none usr/include/libnwam.h 644 root bin diff --git a/usr/src/pkgdefs/SUNWnwamintu/prototype_i386 b/usr/src/pkgdefs/SUNWnwamintu/prototype_i386 new file mode 100644 index 0000000000..d3632da695 --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintu/prototype_i386 @@ -0,0 +1,45 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. +# + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# List files which are i386 specific here +# +# source locations relative to the prototype file +# +# SUNWnwamintu +# diff --git a/usr/src/pkgdefs/SUNWnwamintu/prototype_sparc b/usr/src/pkgdefs/SUNWnwamintu/prototype_sparc new file mode 100644 index 0000000000..7f3f9c31b7 --- /dev/null +++ b/usr/src/pkgdefs/SUNWnwamintu/prototype_sparc @@ -0,0 +1,45 @@ +# +# 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 +# or http://www.opensolaris.org/os/licensing. +# 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 2008 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. +# + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# List files which are SPARC specific here +# +# source locations relative to the prototype file +# +# SUNWnwamintu +# |
