diff options
| author | jbeck <none@none> | 2007-03-30 17:01:13 -0700 | 
|---|---|---|
| committer | jbeck <none@none> | 2007-03-30 17:01:13 -0700 | 
| commit | d71dbb732372504daff1f1783bc0d8864ce9bd50 (patch) | |
| tree | 250aa1eba146725a05b071c536722a5e879d43f0 /usr/src | |
| parent | 623cc4421f2b351089438cb912f6d8b9f2fd3e6c (diff) | |
| download | illumos-joyent-d71dbb732372504daff1f1783bc0d8864ce9bd50.tar.gz | |
PSARC 2007/136 Network Auto-Magic (NWAM) Phase 0
6355747 /lib/svc/method/net-svc makes a mess of hosts and ipnodes
6366093 "ifconfig <wireless-lan-device> dhcp" not enough to surf with browser
6539574 _link_aton() underallocates a buffer
Diffstat (limited to 'usr/src')
31 files changed, 5844 insertions, 131 deletions
| diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint index 70de3ea291..4c9f107de7 100644 --- a/usr/src/Makefile.lint +++ b/usr/src/Makefile.lint @@ -67,6 +67,7 @@ COMMON_SUBDIRS = \  	cmd/cksum \  	cmd/clinfo \  	cmd/cmd-crypto \ +	cmd/cmd-inet/lib \  	cmd/cmd-inet/sbin \  	cmd/cmd-inet/usr.bin \  	cmd/cmd-inet/usr.lib/dsvclockd \ diff --git a/usr/src/Targetdirs b/usr/src/Targetdirs index 49abf05331..e8dbff7b77 100644 --- a/usr/src/Targetdirs +++ b/usr/src/Targetdirs @@ -203,6 +203,7 @@ ROOT.BIN= \  	/etc/sma \  	/etc/sma/snmp \  	/lib \ +	/lib/inet \  	/lib/secure \  	/lib/svc \  	/lib/svc/bin \ diff --git a/usr/src/cmd/cmd-inet/Makefile b/usr/src/cmd/cmd-inet/Makefile index fc35bd8e68..1993f2f8f5 100644 --- a/usr/src/cmd/cmd-inet/Makefile +++ b/usr/src/cmd/cmd-inet/Makefile @@ -2,9 +2,8 @@  # CDDL HEADER START  #  # The contents of this file are subject to the terms of the -# Common Development and Distribution License, Version 1.0 only -# (the "License").  You may not use this file except in compliance -# with the License. +# 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. @@ -22,15 +21,16 @@  #  #ident	"%Z%%M%	%I%	%E% SMI"  # -# Copyright 2006 Sun Microsystems, Inc.  All rights reserved. +# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.  # Use is subject to license terms.  # -include	../Makefile.cmd +include		../Makefile.cmd -SUBDIRS=	etc sbin usr.bin usr.sbin usr.lib usr.sadm -MSGSUBDIRS=	usr.bin usr.sbin usr.lib usr.sadm -POFILES=        usr.bin/usr.bin.po usr.sbin/usr.sbin.po usr.sadm/usr.sadm.po +SUBDIRS=	etc lib sbin usr.bin usr.sbin usr.lib usr.sadm +MSGSUBDIRS=	lib usr.bin usr.sbin usr.lib usr.sadm +POFILES=	lib/lib.po usr.bin/usr.bin.po usr.sbin/usr.sbin.po \ +		usr.sadm/usr.sadm.po  POFILE=		cmd-inet.po  all:=		TARGET= all diff --git a/usr/src/cmd/cmd-inet/Makefile.msg b/usr/src/cmd/cmd-inet/Makefile.msg new file mode 100644 index 0000000000..3e3d20911b --- /dev/null +++ b/usr/src/cmd/cmd-inet/Makefile.msg @@ -0,0 +1,34 @@ +# +# 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 +# +# +#ident	"%Z%%M%	%I%	%E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc.  All rights reserved. +# Use is subject to license terms. +# + +_msg:=          TARGET= _msg + +_msg: $(MSGSUBDIRS) .WAIT $(POFILE) + +$(POFILE): $(POFILES) +	$(RM) $(POFILE) +	$(CAT) $(POFILES) > $(POFILE) diff --git a/usr/src/cmd/cmd-inet/lib/Makefile b/usr/src/cmd/cmd-inet/lib/Makefile new file mode 100644 index 0000000000..3818acfa67 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/Makefile @@ -0,0 +1,51 @@ +# +# 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 2007 Sun Microsystems, Inc.  All rights reserved. +# Use is subject to license terms. +# +# ident	"%Z%%M%	%I%	%E% SMI" +# + +SUBDIRS=	nwamd +MSGSUBDIRS=	nwamd + +include 	../../Makefile.cmd + +POFILES=	$(MSGSUBDIRS:%=%/%.po) +POFILE=		lib.po + +all:=		TARGET= all +install:=	TARGET= install +clean:=		TARGET= clean +clobber:=	TARGET= clobber +lint:=		TARGET= lint + +.KEEP_STATE: + +all install clean clobber lint: $(SUBDIRS) + +$(SUBDIRS): FRC +	@cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include		../Makefile.msg diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/Makefile b/usr/src/cmd/cmd-inet/lib/nwamd/Makefile new file mode 100644 index 0000000000..e35f0bb4d3 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/Makefile @@ -0,0 +1,72 @@ +# +# 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 +# + +# +# ident	"%Z%%M%	%I%	%E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc.  All rights reserved. +# Use is subject to license terms. +# +# usr/src/cmd/cmd-inet/lib/nwamd/Makefile +# + +# Needed for ROOTFS_LIBDIR definition +include		../../../../lib/Makefile.lib + +PROG=		nwamd +OBJS=		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 + +include		../../../Makefile.cmd + +POFILE=		$(PROG).po +POFILES=	interface.po wireless.po + +ROOTCMDDIR=	$(ROOTFS_LIBDIR)/inet + +LDLIBS +=	-lsocket -lnsl -linetcfg -linetutil -lumem -lscf -ldladm + +.KEEP_STATE: + +.PARALLEL: + +all: $(PROG) + +$(PROG): $(OBJS) +	$(LINK.c) $(OBJS) -o $@ $(LDLIBS) +	$(POST_PROCESS) + +install: $(ROOTCMD) + +check:  $(SRCS) $(HEADERS) +	$(CSTYLE) -cpP $(SRCS) $(HEADERS) + +$(ROOTCMD): all + +clean: +	$(RM) $(OBJS) + +lint:	lint_SRCS + +include		../../../Makefile.targ +include		../../Makefile.msg diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/defines.h b/usr/src/cmd/cmd-inet/lib/nwamd/defines.h new file mode 100644 index 0000000000..1033f9d7ab --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/defines.h @@ -0,0 +1,58 @@ +/* + * 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 2007 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	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	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	TIMER_INFINITY		0xffffffff	/* we use uint32s for timers */ +#define	NSEC_TO_SEC(nsec)	(nsec) / NANOSEC + +#endif /* _DEFINES_H */ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/events.c b/usr/src/cmd/cmd-inet/lib/nwamd/events.c new file mode 100644 index 0000000000..8d23d0816c --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/events.c @@ -0,0 +1,656 @@ +/* + * 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 2007 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. + * + * struct np_event is the basic event structure.  The np_event structure and + * its npe_name member are allocated using malloc(3c).  free_event() frees both + * the npe_name member and the associated np_event structure. + * + * np_queue_add_event() and np_queue_get_event() provide functionality for + * adding events to a queue and blocking on that queue for an event. + * + * 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. + * + * routing_events() reads routing messages off of an IPv4 routing socket and + * by calling addevent_*() functions places appropriate events on the queue. + * + * start_event_collection() creates a thread to run routing_events() and one + * to run periodic_wireless_scan() in.  Finally it does an initial collection + * of information from each interface currently known. + */ + +#include <arpa/inet.h> +#include <assert.h> +#include <errno.h> +#include <libsysevent.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; + +pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; +pthread_t routing, scan; + +static void printaddrs(int mask, void *address); +static char *printaddr(void **address); +static struct sockaddr *dupsockaddr(struct sockaddr *); +static boolean_t cmpsockaddr(struct sockaddr *, struct sockaddr *); +static void *getaddr(int addrid, int mask, void *address); + +union rtm_buf +{ +	/* Routing information. */ +	struct +	{ +		struct rt_msghdr rtm; +		struct sockaddr_storage addr[RTAX_MAX]; +	} r; + +	/* Interface information. */ +	struct +	{ +		struct if_msghdr ifm; +		struct sockaddr_storage addr[RTAX_MAX]; +	} im; + +	/* Interface address information. */ +	struct +	{ +		struct ifa_msghdr ifa; +		struct sockaddr_storage addr[RTAX_MAX]; +	} ia; +}; + +void +free_event(struct np_event *e) +{ +	free(e->npe_name); +	free(e); +} + +void +np_queue_add_event(struct np_event *e) +{ +	(void) pthread_mutex_lock(&queue_mutex); +	if (equeue_end != NULL) { +		equeue_end->npe_next = e; +		equeue_end = e; +	} else { +		equeue = equeue_end = e; +	} +	equeue_end->npe_next = NULL; +	(void) pthread_cond_signal(&queue_cond); +	(void) pthread_mutex_unlock(&queue_mutex); +} + +/* + * Blocking getevent.  This routine will block until there is an event for + * it to return. + */ +struct np_event * +np_queue_get_event(void) +{ +	struct np_event *rv = NULL; + +	(void) pthread_mutex_lock(&queue_mutex); + +	while (equeue == NULL) +		(void) pthread_cond_wait(&queue_cond, &queue_mutex); + +	rv = equeue; +	equeue = equeue->npe_next; +	if (equeue == NULL) +		equeue_end = NULL; + +	(void) pthread_mutex_unlock(&queue_mutex); + +	rv->npe_next = NULL; +	return (rv); +} + +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; + +	default: +		dprintf("unhandled type in addevent_routing_msghdr %d", +		    ifm->ifm_type); +		break; +	} +} + +static const char * +rtmtype_str(int type) +{ +	static char typestr[12]; /* strlen("type ") + enough for an int */ + +	switch (type) { +		case RTM_ADD: +			return ("ADD"); +		case RTM_DELETE: +			return ("DELETE"); +		case RTM_NEWADDR: +			return ("NEWADDR"); +		case RTM_DELADDR: +			return ("DELADDR"); +		case RTM_IFINFO: +			return ("IFINFO"); +		default: +			(void) snprintf(typestr, sizeof (typestr), "type %d", +			    type); +			return (typestr); +	} +} + +/* ARGSUSED */ +static void * +routing_events(void *arg) +{ +	int rtsock; +	int n; +	union rtm_buf buffer; +	struct rt_msghdr *rtm; +	struct ifa_msghdr *ifa; +	struct if_msghdr *ifm; + +	/* +	 * We use v4 interfaces as proxies for links so those are the only +	 * routing messages we need to listen to.  Look at the comments in +	 * structures.h for more information about the split between the +	 * llp and interfaces. +	 */ +	rtsock = socket(AF_ROUTE, SOCK_RAW, AF_INET); +	if (rtsock == -1) { +		syslog(LOG_ERR, "failed to open routing socket: %m"); +		exit(EXIT_FAILURE); +	} + +	dprintf("routing socket %d", rtsock); + +	for (;;) { +		struct interface *ifp; +		char *addrs, *if_name; +		struct sockaddr_dl *addr_dl; +		struct sockaddr *addr; + +		rtm = &buffer.r.rtm; +		n = read(rtsock, &buffer, sizeof (buffer)); +		if (n == -1 && errno == EAGAIN) { +			continue; +		} else if (n == -1) { +			syslog(LOG_ERR, "error reading routing socket " +			    "%d: %m", rtsock); +			/* Low likelihood.  What's recovery path?  */ +			continue; +		} + +		if (rtm->rtm_msglen < n) { +			syslog(LOG_ERR, "only read %d bytes from " +			    "routing socket but message claims to be " +			    "of length %d", rtm->rtm_msglen); +			continue; +		} + +		if (rtm->rtm_version != RTM_VERSION) { +			syslog(LOG_ERR, "tossing routing message of " +			    "version %d type %d", rtm->rtm_version, +			    rtm->rtm_type); +			continue; +		} + +		if (rtm->rtm_msglen != n) { +			dprintf("routing message of %d size came from " +			    "read of %d on socket %d", rtm->rtm_msglen, +			    n, rtsock); +		} + +		switch (rtm->rtm_type) { +		case RTM_NEWADDR: +			ifa = (void *)rtm; +			addrs = (char *)ifa + sizeof (*ifa); + +			dprintf("routing message NEWADDR: index %d flags %x", +			    ifa->ifam_index, ifa->ifam_flags); +			printaddrs(ifa->ifam_addrs, addrs); + +			if ((addr = (struct sockaddr *)getaddr(RTA_IFA, +			    ifa->ifam_addrs, addrs)) == NULL) +				break; + +			if ((addr_dl = (struct sockaddr_dl *)getaddr +			    (RTA_IFP, ifa->ifam_addrs, addrs)) == NULL) +				break; +			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); +			} +			break; +		case RTM_IFINFO: +		{ +			boolean_t plugged_in; + +			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) +				break; +			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 DHCPFAILED and DHCPSTARTED +			 * 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_DHCPFAILED; +				ifp->if_lflags &= ~IF_DHCPSTARTED; +			} +			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); +			} +			break; +		} +		default: +			dprintf("routing message %s socket %d discarded", +			    rtmtype_str(rtm->rtm_type), rtsock); +			break; +		} +	} +	/* NOTREACHED */ +	return (NULL); +} + +/* return B_TRUE if sin_family and sin_addr are the same, B_FALSE if not */ +static boolean_t +cmpsockaddr(struct sockaddr *addr1, struct sockaddr *addr2) +{ +	struct sockaddr_in *sina, *sinb; +	struct sockaddr_in6 *sin6a, *sin6b; + +	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). + */ +static struct sockaddr * +dupsockaddr(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); +	} +} + +static char * +printaddr(void **address) +{ +	static char buffer[80]; +	sa_family_t family = *(sa_family_t *)*address; +	struct sockaddr_in *s4 = *address; +	struct sockaddr_in6 *s6 = *address; +	struct sockaddr_dl *dl = *address; + +	switch (family) { +		case AF_UNSPEC: +			(void) inet_ntop(AF_UNSPEC, &s4->sin_addr, buffer, +			    sizeof (buffer)); +			*address = (char *)*address + sizeof (*s4); +			break; +		case AF_INET: +			(void) inet_ntop(AF_INET, &s4->sin_addr, buffer, +			    sizeof (buffer)); +			*address = (char *)*address + sizeof (*s4); +			break; +		case AF_INET6: +			(void) inet_ntop(AF_INET6, &s6->sin6_addr, buffer, +			    sizeof (buffer)); +			*address = (char *)*address + sizeof (*s6); +			break; +		case AF_LINK: +			(void) snprintf(buffer, sizeof (buffer), "link %s", +			    dl->sdl_data); +			*address = (char *)*address + sizeof (*dl); +			break; +		default: +			/* +			 * We can't reliably update the size of this thing +			 * because we don't know what its type is.  So bump +			 * it by a sockaddr_in and see what happens.  The +			 * caller should really make sure this never happens. +			 */ +			*address = (char *)*address + sizeof (*s4); +			(void) snprintf(buffer, sizeof (buffer), +			    "unknown address family %d", family); +			break; +	} +	return (buffer); +} + +static void +printaddrs(int mask, void *address) +{ +	if (mask == 0) +		return; +	if (mask & RTA_DST) +		dprintf("destination address: %s", printaddr(&address)); +	if (mask & RTA_GATEWAY) +		dprintf("gateway address: %s", printaddr(&address)); +	if (mask & RTA_NETMASK) +		dprintf("netmask: %s", printaddr(&address)); +	if (mask & RTA_GENMASK) +		dprintf("cloning mask: %s", printaddr(&address)); +	if (mask & RTA_IFP) +		dprintf("interface name: %s", printaddr(&address)); +	if (mask & RTA_IFA) +		dprintf("interface address: %s", printaddr(&address)); +	if (mask & RTA_AUTHOR) +		dprintf("author: %s", printaddr(&address)); +	if (mask & RTA_BRD) +		dprintf("broadcast address: %s", printaddr(&address)); +} + +static void +nextaddr(void **address) +{ +	sa_family_t family = *(sa_family_t *)*address; + +	switch (family) { +	case AF_UNSPEC: +	case AF_INET: +		*address = (char *)*address + sizeof (struct sockaddr_in); +		break; +	case AF_INET6: +		*address = (char *)*address + sizeof (struct sockaddr_in6); +		break; +	case AF_LINK: +		*address = (char *)*address + sizeof (struct sockaddr_dl); +		break; +	default: +		syslog(LOG_ERR, "unknown af (%d) while parsing rtm", family); +		break; +	} +} + +static void * +getaddr(int addrid, int mask, void *address) +{ +	int i; +	void *p = address; + +	if ((mask & addrid) == 0) +		return (NULL); + +	for (i = 1; i < addrid; i <<= 1) { +		if (i & mask) +			nextaddr(&p); +	} +	return (p); +} + +boolean_t +start_event_collection(void) +{ +	int err; + +	/* +	 * if these are ever created/destroyed repetitively then we will +	 * have to change this. +	 */ + +	if (err = pthread_create(&routing, NULL, routing_events, NULL)) { +		syslog(LOG_ERR, "pthread_create routing: %s", strerror(err)); +		exit(EXIT_FAILURE); +	} else { +		dprintf("routing thread: %d", routing); +	} + +	if (err = pthread_create(&scan, NULL, periodic_wireless_scan, NULL)) { +		syslog(LOG_ERR, "pthread_create wireless scan: %s", +		    strerror(err)); +		exit(EXIT_FAILURE); +	} else { +		dprintf("scan thread: %d", scan); +	} + +	walk_interface(start_if_info_collect, NULL); + +	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 new file mode 100644 index 0000000000..2f0d86ced0 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/functions.h @@ -0,0 +1,102 @@ +/* + * 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 2007 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _FUNCTIONS_H +#define	_FUNCTIONS_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +/* events.c: event queue handling */ +extern void free_event(struct np_event *); +extern void np_queue_add_event(struct np_event *); +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 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 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 *, +    boolean_t); +extern void takedowninterface(const char *, boolean_t, boolean_t, boolean_t); +extern void take_down_all_ifs(const char *); +extern void check_interface_timer(struct interface *, void *); +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 *); + +/* wireless.c: wifi link handling */ +extern void init_mutexes(void); +extern int get_user_preference(const char *, const char *, const char *, +    struct wireless_lan **, const struct wireless_lan *); +extern boolean_t connect_chosen_lan(struct wireless_lan *, const char *); +struct wireless_lan *prompt_for_visited(void); +boolean_t handle_wireless_lan(const char *); +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 void *periodic_wireless_scan(void *); +extern boolean_t known_wifi_nets_lookup(const char *, const char *); + +/* llp.c: link layer profile handling */ +extern void llp_parse_config(void); +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 char *llp_prnm(llp_t *); + +/* state_machine.c: state machine handling */ +extern void state_machine(struct np_event *); +extern void cleanup(void); + +/* util.c: utility & ipc functions */ +/* PRINTFLIKE1 */ +extern void dprintf(const char *fmt, ...); +extern uint32_t getcurrenttime(void); +extern uint64_t get_ifflags(const char *, sa_family_t); +extern boolean_t is_plugged_in(struct interface *); +extern int start_childv(const char *command, char const * const *argv); +extern int start_child(const char *command, ...); +extern void start_timer(uint32_t,  uint32_t); +extern boolean_t valid_graphical_user(boolean_t); +extern void lookup_zonename(char *zonename, size_t zonesize); + +#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 new file mode 100644 index 0000000000..3724275f6e --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/interface.c @@ -0,0 +1,1171 @@ +/* + * 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 2007 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 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. + * + * The daemon maintains a list of structures that represent each IPv4 + * interface found on the system (after doing 'ifconfig -a plumb'). + * This list represents the objects manipulated by the daemon; while + * the list of llp_t structures represents the configuration details + * requested by the user (either the automatic defaults or entries in + * /etc/nwam/llp).  IPv6 interfaces are not tracked in the interfaces + * list; rather, when the decision is made to make an interface active, + * IPv6 is brought up in addition to IPv4 (assuming the LLP configuration + * includes IPv6; this is the default for automatic configuration). + * + * Interfaces are brought up and torn down by a sequence of ifconfig + * commands (currently posix_spawn'd() by nwamd; the longer-term direction + * here is to use libinetcfg). + * + * Upper Layer Profile management is controlled by user-provided scripts, + * which should be created in /etc/nwam/ulp.  One script, + * /etc/nwam/ulp/check-conditions, checks the current network setup and + * returns the name of the ULP which should be active under the current + * conditions.  A ULP is specified by two scripts, found in + * /etc/nwam/ulp/<ulp name>: bringup and teardown.  All scripts are + * optional; if they do not exist or are not executable, nwamd will + * simply move on. + * + * When an interface has been successfully brought up (signalled by the + * assignment of an IP address to the interface), the daemon will first + * teardown the existing ULP (if there is one) by running the teardown + * script for that ULP.  It will then run the check-conditions script; + * if the name of a ULP is returned, it runs the bringup script for that + * ULP. + * + * A "gather info" thread is initiated for an interface when it becomes + * available.  For a wired interface, "available" means the IFF_RUNNING + * flag is set; wireless interfaces are considered to always be available, + * so a wireless interface's gather info thread will run once, when it is + * 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. + */ + +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#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 <libdllink.h> + +#include "defines.h" +#include "structures.h" +#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 char upper_layer_profile[MAXHOSTNAMELEN]; + +static void print_interface_list(); +static struct interface *get_next_interface(struct interface *); + +#define	IPFILTER_FMRI	"svc:/network/ipfilter:default" +#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; +	socklen_t addrlen = sizeof (struct sockaddr_in); +	int prefixlen = 0; + +	(void) strlcpy(intf.if_name, ifname, sizeof (intf.if_name)); +	/* We only display new addr info for v4 interfaces */ +	intf.if_protocol = AF_INET; +	if (icfg_open(&h, &intf) != ICFG_SUCCESS) { +		syslog(LOG_ERR, "icfg_open failed on interface %s", ifname); +		return; +	} +	if (icfg_get_addr(h, (struct sockaddr *)&sin, &addrlen, &prefixlen, +	    B_TRUE) != ICFG_SUCCESS) { +		syslog(LOG_ERR, "icfg_get_addr failed on interface %s", ifname); +		icfg_close(h); +		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); +} + +/* + * If this interface matches the currently active llp, return B_TRUE. + * Otherwise, return B_FALSE. + */ +boolean_t +interface_is_active(const struct interface *ifp) +{ +	if (link_layer_profile == NULL || ifp == NULL) +		return (B_FALSE); + +	return (strcmp(ifp->if_name, link_layer_profile->llp_lname) == 0); +} + +/* + * Execute 'ifconfig ifname dhcp wait 0'. + */ +static void +start_dhcp(struct interface *ifp) +{ +	int res; +	uint32_t now_s; +	uint64_t timer_s; + +	if ((ifp->if_lflags & IF_DHCPSTARTED) != 0) { +		dprintf("start_dhcp: already started; returning"); +		return; +	} +	ifp->if_lflags |= IF_DHCPSTARTED; + +	(void) start_child(IFCONFIG, ifp->if_name, "dhcp", "wait", "0", NULL); + +	/* start dhcp timer */ +	res = lookup_count_property(OUR_PG, "dhcp_wait_time", &timer_s); +	if (res == -1) +		timer_s = NWAM_DEFAULT_DHCP_WAIT_TIME; + +	now_s = NSEC_TO_SEC(gethrtime()); +	ifp->if_timer_expire = now_s + timer_s; + +	start_timer(now_s, timer_s); +} + +static boolean_t +check_svc_up(const char *fmri, int wait_time) +{ +	int i; +	char *state; + +	for (i = 1; i <= wait_time; i++) { +		state = smf_get_state(fmri); +		if (strcmp(SCF_STATE_STRING_ONLINE, state) == 0) { +			free(state); +			return (B_TRUE); +		} +		free(state); +		(void) sleep(1); +	} +	return (B_FALSE); +} + +boolean_t +ulp_is_active(void) +{ +	return (upper_layer_profile[0] != '\0'); +} + +/* + * Inputs: + *   res is a pointer to the scf_resources_t to be released. + */ +static void +release_scf_resources(scf_resources_t *res) +{ +	scf_value_destroy(res->sr_val); +	scf_property_destroy(res->sr_prop); +	scf_pg_destroy(res->sr_pg); +	scf_snapshot_destroy(res->sr_snap); +	scf_instance_destroy(res->sr_inst); +	(void) scf_handle_unbind(res->sr_handle); +	scf_handle_destroy(res->sr_handle); +} + +/* + * Inputs: + *   lpg is the property group to look up + *   lprop is the property within that group to look up + * Outputs: + *   res is a pointer to an scf_resources_t.  This is an internal + *   structure that holds all the handles needed to get a specific + *   property from the running snapshot; on a successful return it + *   contains the scf_value_t that should be passed to the desired + *   scf_value_get_foo() function, and must be freed after use by + *   calling release_scf_resources().  On a failure return, any + *   resources that may have been assigned to res are released, so + *   the caller does not need to do any cleanup in the failure case. + * Returns: + *    0 on success + *   -1 on failure + */ +static int +get_property_value(const char *lpg, const char *lprop, scf_resources_t *res) +{ +	res->sr_inst = NULL; +	res->sr_snap = NULL; +	res->sr_pg = NULL; +	res->sr_prop = NULL; +	res->sr_val = NULL; + +	if ((res->sr_handle = scf_handle_create(SCF_VERSION)) == NULL) { +		syslog(LOG_ERR, "scf_handle_create() failed: %s", +		    scf_strerror(scf_error())); +		return (-1); +	} + +	if (scf_handle_bind(res->sr_handle) != 0) { +		scf_handle_destroy(res->sr_handle); +		syslog(LOG_ERR, "scf_handle_destroy() failed: %s", +		    scf_strerror(scf_error())); +		return (-1); +	} +	if ((res->sr_inst = scf_instance_create(res->sr_handle)) == NULL) { +		syslog(LOG_ERR, "scf_instance_create() failed: %s", +		    scf_strerror(scf_error())); +		goto failure; +	} +	if (scf_handle_decode_fmri(res->sr_handle, OUR_FMRI, NULL, NULL, +	    res->sr_inst, NULL, NULL, SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { +		syslog(LOG_ERR, "scf_handle_decode_fmri() failed: %s", +		    scf_strerror(scf_error())); +		goto failure; +	} +	if ((res->sr_snap = scf_snapshot_create(res->sr_handle)) == NULL) { +		syslog(LOG_ERR, "scf_snapshot_create() failed: %s", +		    scf_strerror(scf_error())); +		goto failure; +	} +	if (scf_instance_get_snapshot(res->sr_inst, "running", +	    res->sr_snap) != 0) { +		syslog(LOG_ERR, "scf_instance_get_snapshot() failed: %s", +		    scf_strerror(scf_error())); +		goto failure; +	} +	if ((res->sr_pg = scf_pg_create(res->sr_handle)) == NULL) { +		syslog(LOG_ERR, "scf_pg_create() failed: %s", +		    scf_strerror(scf_error())); +		goto failure; +	} +	if (scf_instance_get_pg_composed(res->sr_inst, res->sr_snap, lpg, +	    res->sr_pg) != 0) { +		syslog(LOG_ERR, "scf_instance_get_pg_composed(%s) failed: %s", +		    lpg, scf_strerror(scf_error())); +		goto failure; +	} +	if ((res->sr_prop = scf_property_create(res->sr_handle)) == NULL) { +		syslog(LOG_ERR, "scf_property_create() failed: %s", +		    scf_strerror(scf_error())); +		goto failure; +	} +	if (scf_pg_get_property(res->sr_pg, lprop, res->sr_prop) != 0) { +		syslog(LOG_ERR, "scf_pg_get_property(%s) failed: %s", +		    lprop, scf_strerror(scf_error())); +		goto failure; +	} +	if ((res->sr_val = scf_value_create(res->sr_handle)) == NULL) { +		syslog(LOG_ERR, "scf_value_create() failed: %s", +		    scf_strerror(scf_error())); +		goto failure; +	} +	if (scf_property_get_value(res->sr_prop, res->sr_val) != 0) { +		syslog(LOG_ERR, "scf_property_get_value() failed: %s", +		    scf_strerror(scf_error())); +		goto failure; +	} +	return (0); + +failure: +	release_scf_resources(res); +	return (-1); +} + +/* + * Inputs: + *   lpg is the property group to look up + *   lprop is the property within that group to look up + * Outputs: + *   answer is a pointer to the property value + * Returns: + *    0 on success + *   -1 on failure + * If successful, the property value is retured in *answer. + * Otherwise, *answer is undefined, and it is up to the caller to decide + * how to handle that case. + */ +int +lookup_boolean_property(const char *lpg, const char *lprop, boolean_t *answer) +{ +	int result = -1; +	scf_resources_t res; +	uint8_t prop_val; + +	if (get_property_value(lpg, lprop, &res) != 0) { +		/* +		 * an error was already logged by get_property_value, +		 * and it released any resources assigned to res before +		 * returning. +		 */ +		return (result); +	} +	if (scf_value_get_boolean(res.sr_val, &prop_val) != 0) { +		syslog(LOG_ERR, "scf_value_get_boolean() failed: %s", +		    scf_strerror(scf_error())); +		goto cleanup; +	} +	*answer = (boolean_t)prop_val; +	dprintf("lookup_boolean_property(%s, %s) returns %s", lpg, lprop, +	    *answer ? "TRUE" : "FALSE"); +	result = 0; +cleanup: +	release_scf_resources(&res); +	return (result); +} + +/* + * Inputs: + *   lpg is the property group to look up + *   lprop is the property within that group to look up + * Outputs: + *   answer is a pointer to the property value + * Returns: + *    0 on success + *   -1 on failure + * If successful, the property value is retured in *answer. + * Otherwise, *answer is undefined, and it is up to the caller to decide + * how to handle that case. + */ +int +lookup_count_property(const char *lpg, const char *lprop, uint64_t *answer) +{ +	int result = -1; +	scf_resources_t res; + +	if (get_property_value(lpg, lprop, &res) != 0) { +		/* +		 * an error was already logged by get_property_value, +		 * and it released any resources assigned to res before +		 * returning. +		 */ +		return (result); +	} +	if (scf_value_get_count(res.sr_val, answer) != 0) { +		syslog(LOG_ERR, "scf_value_get_count() failed: %s", +		    scf_strerror(scf_error())); +		goto cleanup; +	} +	dprintf("lookup_count_property(%s, %s) returns %lld", lpg, lprop, +	    *answer); +	result = 0; +cleanup: +	release_scf_resources(&res); +	return (result); +} + +void +activate_upper_layer_profile(boolean_t do_dhcp, const char *ifname) +{ +	FILE *f; +	char buffer[1024]; +	size_t buflen; +	size_t offset; +	const char bringup[] = "/bringup"; + +	if (do_dhcp) { +		boolean_t should; +		int res; + +		res = lookup_boolean_property(OUR_PG, "use_net_svc", &should); +		/* +		 * If the look-up failed, try anyway: only avoid this if we +		 * know for sure not to. +		 */ +		if ((res == 0 && should) || (res == -1)) { +			if (check_svc_up(NET_SVC_FMRI, 5)) { +				(void) start_child(NET_SVC_METHOD, "start", +				    ifname, NULL); +			} else { +				syslog(LOG_WARNING, "timed out when waiting " +				    "for %s to come up, start method %s not " +				    "executed", NET_SVC_FMRI, NET_SVC_METHOD); +			} +		} +	} +	f = popen(ULP_DIR "/check-conditions", "r"); +	if (f == NULL) +		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, +	    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)); +	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); +} + +void +deactivate_upper_layer_profile(void) +{ +	char buffer[1024]; + +	/* +	 * If ULP wasn't defined... +	 */ +	if (!ulp_is_active()) +		return; + +	(void) snprintf(buffer, sizeof (buffer), ULP_DIR "/%s/teardown", +	    upper_layer_profile); +	(void) start_child(PFEXEC, "-P", "basic", buffer, NULL); + +	syslog(LOG_NOTICE, "upper layer profile %s deactivated", +	    upper_layer_profile); + +	upper_layer_profile[0] = '\0'; +} + +/* + * Returns B_TRUE if the interface is successfully brought up; + * B_FALSE if bringup fails. + */ +boolean_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); +	} + +	/* check current state; no point going on if flags are 0 */ +	if ((ifflags = get_ifflags(ifname, intf->if_family)) == 0) { +		dprintf("bringupinterface(%s): get_ifflags() returned 0", +		    ifname); +		return (B_FALSE); +	} + +	/* +	 * If the link layer profile says that we want v6 then plumb it and +	 * bring it up; if there's a static address, configure it as well. +	 */ +	if (ipv6onlink) { +		dprintf("bringupinterface: configuring ipv6"); +		(void) start_child(IFCONFIG, ifname, "inet6", "plumb", "up", +		    NULL); +		if (ipv6addr) { +			(void) start_child(IFCONFIG, ifname, "inet6", "addif", +			    ipv6addr, "up", NULL); +		} +	} + +	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(ifname)) { +			syslog(LOG_INFO, "Could not connect to any WLAN, not " +			    "bringing %s up", ifname); +			return (B_FALSE); +		} +	} + +	if (do_dhcp) { +		start_dhcp(intf); +	} else { +		(void) start_child(IFCONFIG, ifname, host, NULL); +		(void) start_child(IFCONFIG, ifname, "up", NULL); +	} + +	return (B_TRUE); +} + +void +takedowninterface(const char *ifname, boolean_t dhcp, boolean_t popup, +    boolean_t v6onlink) +{ +	uint64_t flags; +	struct interface *ifp; + +	dprintf("takedowninterface(%s, %s, %s, %s)", ifname, +	    BOOLEAN_TO_STRING(dhcp), BOOLEAN_TO_STRING(popup), +	    BOOLEAN_TO_STRING(v6onlink)); + +	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 (dhcp) { +		if ((flags & IFF_DHCPRUNNING) != 0) { +			/* +			 * We generally prefer doing a release, as that +			 * tells the server that it can relinquish the +			 * lease, whereas drop is just a client-side +			 * operation.  But if we never came up, release +			 * will fail, because dhcpagent does not allow +			 * an interface which is selecting to release, +			 * so we have to drop in that circumstance.  So +			 * try release first, then fall back to drop. +			 */ +			if (start_child(IFCONFIG, ifname, "dhcp", "release", +			    NULL) != 0) { +				(void) start_child(IFCONFIG, ifname, "dhcp", +				    "drop", NULL); +			} +		} +	} else { +		if ((flags & IFF_UP) != 0) +			(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) { +		/* +		 * Unplumbing the link local interface causes dhcp and ndpd to +		 * remove other addresses they have added. +		 */ +		(void) start_child(IFCONFIG, ifname, "inet6", "unplumb", NULL); +	} + +	if (find_if_type(ifname) == IF_WIRELESS) +		(void) dladm_wlan_disconnect(ifname); + +	dprintf("takedown interface, free cached ip address"); +	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); +	} +} + +/* + * 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. + */ +void +take_down_all_ifs(const char *ignore_if) +{ +	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, +			    flags & IFF_DHCPRUNNING, B_FALSE, +			    ifp->if_family == AF_INET6); +		} +	} +} + +static struct interface * +get_next_interface(struct interface *ifp) +{ +	return (ifp == NULL ? ifs_head : ifp->if_next); +} + +/* + * Add an interface struct to the interface list.  The list is + * partially ordered; all the wired interfaces appear first, + * followed by all the wireless interfaces.  New interfaces are + * added at the end of the appropriate list section. + */ +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; + +	switch (ifp->if_type) { +	case IF_WIRELESS: +		first_wireless = (ifs_wireless == NULL); +		wpp = &ifs_wireless; +		endp = NULL; +		break; + +	case IF_WIRED: +		first_wired = (ifs_wired == NULL); +		wpp = &ifs_wired; +		endp = ifs_wireless; +		break; + +	default: +		/* don't add to the list */ +		return; +	} + +	/* set list head if this is the first entry */ +	if (ifs_head == NULL) { +		ifs_head = *wpp = ifp; +		ifp->if_next = NULL; +		return; +	} + +	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; +	} +} + +/* + * 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. + */ +struct interface * +add_interface(sa_family_t family, const char *name, uint64_t flags) +{ +	struct interface *i; +	enum interface_type iftype; + +	if (name == NULL) +		return (NULL); + +	dprintf("add_interface: found interface %s", name); +	if (family == AF_INET6) { +		/* +		 * we don't track IPv6 interfaces separately from their +		 * v4 counterparts; a link either has v4 only, or both +		 * v4 and v6, so we only maintain a v4 interface struct. +		 */ +		dprintf("not adding v6 interface for %s", name); +		return (NULL); +	} else if (family != AF_INET) { +		/* +		 * the classic "shouldn't happen"... +		 */ +		dprintf("not adding af %d interface for %s", family, name); +		return (NULL); +	} + +	if ((iftype = find_if_type(name)) == IF_TUN) { +		/* +		 * for now, we're ignoring tunnel interfaces (we expect +		 * them to be entirely manipulated by higher layer profile +		 * activation/deactivation scripts) +		 */ +		dprintf("%s is a tunnel interface; ignoring", name); +		return (NULL); +	} + +	if ((i = malloc(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; +	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; + +	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 "); + +	interface_list_insert(i); + +	return (i); +} + +/* + * Searches for an interface and returns the interface structure if found. + * Returns NULL otherwise.  errno is set upon error exit. + */ +struct interface * +get_interface(const char *name) +{ +	struct interface *i; + +	if (name == NULL) +		return (NULL); + +	for (i = ifs_head; i != NULL; i = i->if_next) { +		if (strcmp(name, i->if_name) == 0) { +			return (i); +		} +	} + +	return (NULL); +} + +/* + * Checks interface flags and, if IFF_DHCPRUNNING and !IFF_UP, does + * an 'ifconfig ifname dhcp drop'. + */ +void +check_drop_dhcp(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); +} + +/* + * For wireless interface, we will try to find out available wireless + * network; for wired, if dhcp should be used, start it now to try to + * avoid delays there. + * + * 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... + */ +static void * +gather_interface_info(void *arg) +{ +	struct interface *i = arg; +	llp_t *llp; + +	assert(i != NULL); + +	dprintf("Start gathering info for %s", i->if_name); + +	switch (i->if_type) { +	case IF_WIRELESS: +		(void) scan_wireless_nets(i); +		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); +		break; +	default: +		/* For other types, do not do anything. */ +		return (NULL); +	} + +	gen_newif_event(i); + +	dprintf("Done gathering info for %s", i->if_name); +	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. + */ +void +walk_interface(void (*walker)(struct interface *, void *), void *arg) +{ +	struct interface *i; + +	for (i = ifs_head; i != NULL; i = i->if_next) +		walker(i, arg); +} + +static void +print_interface_list(void) +{ +	struct interface *wp; + +	dprintf("Walking interface list; starting with wired interfaces"); +	for (wp = ifs_head; wp != NULL; wp = wp->if_next) { +		if (wp == ifs_wireless) +			dprintf("Now wireless interfaces"); +		dprintf("==> %s", wp->if_name); +	} +} + +/* + * Walker function passed to icfg_iterate_if() below - the icfg_if_it * + * argument is guaranteed to be non-NULL by icfg_iterate_if(), + * since the function it uses to generate the list - icfg_get_if_list()) - + * guarantees this. + */ +/* ARGSUSED */ +static int +do_add_interface(icfg_if_t *intf, void *arg) +{ +	uint64_t flags = get_ifflags(intf->if_name, intf->if_protocol); + +	/* We don't touch loopback interface. */ +	if (flags & IFF_LOOPBACK) +		return (ICFG_SUCCESS); + +	/* If adding fails, just ignore that interface... */ +	(void) add_interface(intf->if_protocol, intf->if_name, flags); + +	return (ICFG_SUCCESS); +} + +/* + * Walker function passed to icfg_iterate_if() below - the icfg_if_it * + * argument is guaranteed to be non-NULL by icfg_iterate_if(), + * since the function it uses to generate the list - icfg_get_if_list()) - + * guarantees this. + */ +/* ARGSUSED */ +static int +do_unplumb_if(icfg_if_t *intf, void *arg) +{ +	uint64_t flags = get_ifflags(intf->if_name, intf->if_protocol); + +	/* We don't touch loopback interface. */ +	if (flags & IFF_LOOPBACK) +		return (ICFG_SUCCESS); + +	(void) start_child(IFCONFIG, intf->if_name, +	    intf->if_protocol == AF_INET6 ? "inet6" : "inet", "unplumb", NULL); + +	return (ICFG_SUCCESS); +} + +void +initialize_interfaces(void) +{ +	int times; + +	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. +	 */ +	(void) icfg_iterate_if(AF_INET, ICFG_PLUMBED, NULL, do_unplumb_if); +	(void) icfg_iterate_if(AF_INET6, ICFG_PLUMBED, NULL, do_unplumb_if); + +	/* +	 * In case dhcpagent is running...  If it is running, when +	 * we do another DHCP command on the same interface later, it may +	 * be confused.  Just kill dhcpagent to simplify handling. +	 */ +	dprintf("killing dhcpagent"); +	(void) start_child(PKILL, "-z", zonename, "dhcpagent", NULL); + +	/* +	 * Really we should walk the device tree instead of doing +	 * the 'ifconfig -a plumb'. +	 */ +	for (times = 1; times <= 30; times++) { +		if (start_child(IFCONFIG, "-a", "plumb", NULL) == 0) +			break; + +		/* +		 * Assume the di_init problem is the cause: sleep and try +		 * again, as the DDI has probably not been initialized yet. +		 */ +		(void) sleep(1); +	} +	if (times > 30) +		syslog(LOG_ERR, IFCONFIG "-a plumb failed %d times", times); + +	(void) dladm_init_linkprop(); + +	(void) icfg_iterate_if(AF_INET, ICFG_PLUMBED, NULL, do_add_interface); + +	print_interface_list(); + +} + +/* + * Walker function used to start info gathering of each interface. + */ +/* ARGSUSED */ +void +start_if_info_collect(struct interface *ifp, void *arg) +{ +	pthread_t if_thr; +	pthread_attr_t attr; + +	/* +	 * Only if the cable of the wired interface is +	 * plugged in, start gathering info from it. +	 */ +	if (!is_plugged_in(ifp)) +		return; + +	/* +	 * This is a "fresh start" for the interface; if dhcp +	 * previously failed, the flag can now be cleared. +	 */ +	ifp->if_lflags &= ~IF_DHCPFAILED; + +	(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"); +		exit(EXIT_FAILURE); +	} else { +		dprintf("interface info thread: %d", if_thr); +	} +} + +/* + * Walker function used to check timer for each interface. + * If timer has expired, generate a timer event for the + * interface. + */ +/* ARGSUSED */ +void +check_interface_timer(struct interface *ifp, void *arg) +{ +	uint32_t now = *(uint32_t *)arg; +	struct np_event *ev; + +	if (ifp->if_timer_expire == 0) +		return; + +	if (ifp->if_timer_expire > now) { +		start_timer(now, ifp->if_timer_expire - now); +		return; +	} + +	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); +} + +enum interface_type +find_if_type(const char *name) +{ +	enum interface_type type; + +	if (name == NULL) { +		dprintf("find_if_type: no ifname; returning IF_UNKNOWN"); +		return (IF_UNKNOWN); +	} + +	if (strncmp(name, "ip.tun", 6) == 0) { +		/* +		 * We'll need to update our tunnel detection once +		 * clearview/uv and clearview/tun driver projects +		 * go back; tunnel names won't necessarily be ip.tunN +		 */ +		type = IF_TUN; +	} else { +		/* +		 * We didn't recognize it.  Try the libdladm function +		 * to decide if it is wireless or not; if not, assume +		 * that it's wired. +		 */ +		type = dladm_wlan_is_valid(name) ? IF_WIRELESS : IF_WIRED; +	} + +	return (type); +} + +const char * +if_type_str(enum interface_type type) +{ +	switch (type) { +	case IF_WIRED: +		return ("wired"); +	case IF_WIRELESS: +		return ("wireless"); +	case IF_TUN: +		return ("tunnel"); +	case IF_UNKNOWN: +	default: +		return ("unknown type"); +	} +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/llp.c b/usr/src/cmd/cmd-inet/lib/nwamd/llp.c new file mode 100644 index 0000000000..1ea8be7e1c --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/llp.c @@ -0,0 +1,666 @@ +/* + * 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 2007 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 + * updating of the /etc/nwam/llp file. + * + * The daemon maintains a list of llp_t structures that represent the + * provided configuration information for a link.  After the llp file + * is read, entries are added to the LLP list for any known links + * (identified by checking the interface list, which is based on the + * v4 interfaces present after 'ifconfig -a plumb') which were not + * represented in the llp file.  These entries contain the default + * "automatic" settings: plumb both IPv4 and IPv6, use DHCP on the + * v4 interface, and accept router- and DHCPv6-assigned addresses on + * the v6 interface.  The default entries created by the daemon are + * also added to the llp file. + * + * LLP priority is assigned based on two factors: the order within + * the llp file, with earlier entries having higher priority; and + * a preference for wired interfaces before wireless.  Entries that + * are added to the file by the daemon are added *after* any existing + * entries; within the added block, wired entries are added before + * wireless.  Thus if the llp file is never modified externally, wired + * will generally be ordered before wireless.  However, if the + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <strings.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <syslog.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <atomic.h> +#include <pthread.h> +#include <signal.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +/* 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; + +/* + * 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 void print_llp_list(void); + +char * +llp_prnm(llp_t *llp) +{ +	if (llp == NULL) +		return ("null_llp"); +	else if (llp->llp_lname == NULL) +		return ("null_lname"); +	else +		return (llp->llp_lname); +} + +static void +llp_list_free(llp_t *head) +{ +	llp_t **llpp; +	llp_t *llpfree; + +	if (pthread_mutex_lock(&llp_lock) != 0) { +		/* Something very serious is wrong... */ +		syslog(LOG_ERR, "llp_list_free: cannot lock mutex: %m"); +		return; +	} +	llpp = &head; +	while (*llpp != NULL) { +		llpfree = *llpp; +		*llpp = llpfree->llp_next; +		free(llpfree->llp_ipv4addrstr); +		free(llpfree); +	} +	(void) pthread_mutex_unlock(&llp_lock); +} + +llp_t * +llp_lookup(const char *link) +{ +	llp_t *llp; + +	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) { +		if (strcmp(link, llp->llp_lname) == 0) +			break; +	} +	(void) pthread_mutex_unlock(&llp_lock); +	return (llp); +} + +/* + * Choose the higher priority llp of the two passed in.  If one is + * NULL, the other will be higher priority.  If both are NULL, NULL + * is returned. + * + * Assumes that both are available (i.e. doesn't check IFF_RUNNING + * or IF_DHCPFAILED flag values). + */ +llp_t * +llp_high_pri(llp_t *a, llp_t *b) +{ +	if (a == NULL) +		return (b); +	else if (b == NULL) +		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. + */ +llp_t * +llp_best_avail(void) +{ +	llp_t *p, *rtnllp = NULL; +	struct interface *ifp; + +	/* 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); +	} +	(void) pthread_mutex_unlock(&llp_lock); + +	return (rtnllp); +} + +/* + * Returns B_TRUE if llp is successfully activated; + * B_FALSE if activation fails. + */ +boolean_t +llp_activate(llp_t *llp) +{ +	boolean_t rtn; +	char *host; +	/* +	 * Choosing "dhcp" as a hostname is unsupported right now. +	 * We use hostname="dhcp" as a keyword telling bringupinterface() +	 * to use dhcp on the interface. +	 */ +	char *dhcpstr = "dhcp"; + +	llp_deactivate(); + +	host = (llp->llp_ipv4src == IPV4SRC_DHCP) ? dhcpstr : +	    llp->llp_ipv4addrstr; + +	if (bringupinterface(llp->llp_lname, host, llp->llp_ipv6addrstr, +	    llp->llp_ipv6onlink)) { +		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)); +		link_layer_profile = NULL; +		rtn = B_FALSE; +	} + +	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, +	    link_layer_profile->llp_ipv4src == IPV4SRC_DHCP, 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; +} + +/* + * Replace the currently active link layer profile with the one + * specified.  And since we're changing the lower layer stuff, + * we need to first deactivate the current upper layer profile. + * An upper layer profile will be reactivated later, when we get + * confirmation that the new llp is fully up (has an address + * assigned). + * + * 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. + */ +void +llp_swap(llp_t *newllp) +{ +	char *upifname; + +	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 { +		dprintf("taking down current link layer profile (%s)", +		    llp_prnm(link_layer_profile)); +		llp_deactivate(); +	} +	if (newllp != NULL) { +		dprintf("bringing up new link layer profile (%s)", +		    llp_prnm(newllp)); +		(void) llp_activate(newllp); +	} +} + +/* + * + * 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; +	} +} + +/* + * 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. + */ +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) +{ +	FILE *fp; +	int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + +	/* 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 ((fp = fopen(LLPFILE, "w")) == NULL) { +		syslog(LOG_ERR, "create LLP config file: %m"); +		return; +	} +	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; + +	/* +	 * 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 (pthread_mutex_lock(&llp_lock) != 0) { +		/* Something very serious is wrong... */ +		syslog(LOG_ERR, "llp_list_append: cannot lock mutex: %m"); +		return; +	} + +	while (*wpp != NULL) +		wpp = &(*wpp)->llp_next; +	*wpp = llp; +	llp->llp_next = NULL; + +	(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); +} + +/* + * Walker function to pass to walk_interface() to find out if + * an interface description is missing from LLPFILE.  If it is, + * add it. + * + * Currently, IF_TUN type interfaces are special-cased: they are + * only handled as user-enabled, layered links (which may be created + * 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. + */ +static void +find_and_add_llp(struct interface *ifp, void *arg) +{ +	FILE *fp = (FILE *)arg; + +	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); +		/* If we run out of memory, ignore this interface for now. */ +		create_and_add_llp(ifp->if_name, IPV4SRC_DHCP, NULL, +		    B_TRUE, NULL); +	} +} + +/* + * 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) +		dprintf("==> %s", wp->llp_lname); +} + +/* + * This function parses /etc/nwam/llp which describes the phase 0 link layer + * profile.  The file is line oriented with each line containing tab or space + * 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. + * 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. + * If the second field is dhcp then dhcp will be used on the interface. + * If the second field is ipv6 then an ipv6 interface is plumbed up.  The + *    outcome of this is that if offered by the network in.ndpd and dhcp + *    will conspire to put addresses on additional ipv6 logical interfaces. + *    If the next field is non-null then it is taken to be an IPv6 address + *    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. + */ +void +llp_parse_config(void) +{ +	static const char STATICSTR[] = "static"; +	static const char DHCP[] = "dhcp"; +	static const char IPV6[] = "ipv6"; +	static const char NOIPV6[] = "noipv6"; +	FILE *fp; +	char line[LINE_MAX]; +	char *cp, *lasts, *lstr, *srcstr, *addrstr, *v6addrstr; +	int lnum; +	ipv4src_t ipv4src; +	boolean_t ipv6onlink; + +	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"); +			return; +		} +	} + +	if (llp_head != NULL) +		llp_list_free(llp_head); +	llp_head = NULL; + +	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'; + +		cp = line; +		while (isspace(*cp)) +			cp++; + +		if (*cp == '#' || *cp == '\0') +			continue; + +		dprintf("parsing llp conf file line %d...", lnum); + +		if (((lstr = strtok_r(cp, " \t", &lasts)) == NULL) || +		    ((srcstr = strtok_r(NULL, " \t", &lasts)) == NULL)) { +			syslog(LOG_ERR, "llp:%d: not enough tokens; " +			    "ignoring 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", +				    lnum); +				continue; +			} +			ipv4src = IPV4SRC_STATIC; +		} else if (strcasecmp(srcstr, DHCP) == 0) { +			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); +				} +			} else { +				v6addrstr = NULL; +			} +		} else if (strcasecmp(srcstr, NOIPV6) == 0) { +			ipv6onlink = B_FALSE; +		} else { +			syslog(LOG_ERR, "llp:%d: unrecognized " +			    "field; ignoring entry", lnum); +			continue; +		} + +		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. +	 */ +	add_missing_if_llp(fp); + +	(void) fclose(fp); + +	print_llp_list(); +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/main.c b/usr/src/cmd/cmd-inet/lib/nwamd/main.c new file mode 100644 index 0000000000..1072a8f11c --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/main.c @@ -0,0 +1,392 @@ +/* + * 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 2007 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +/* + * nwamd - NetWork Auto-Magic Daemon + */ + +#include <fcntl.h> +#include <priv.h> +#include <pthread.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <syslog.h> +#include <unistd.h> +#include <locale.h> +#include <libintl.h> +#include <errno.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +#define	TIMESPECGT(x, y)	((x.tv_sec > y.tv_sec) || \ +	    ((x.tv_sec == y.tv_sec) && (x.tv_nsec > y.tv_nsec))) + +const char *OUR_FMRI = "svc:/network/physical:nwam"; +const char *OUR_PG = "nwamd"; + +boolean_t fg = B_FALSE; +boolean_t shutting_down; +sigset_t original_sigmask; +char zonename[ZONENAME_MAX]; + +/* + * nwamd + * + * This is the Network Auto-Magic daemon.  For further high level information + * see the Network Auto-Magic project and the Approachability communities + * on opensolaris.org, and nwamd(1M). + * + * The general structure of the code is as a set of threads collecting + * system events which are fed into a state machine which alters system + * state based on configuration. + * + * signal management + * Due to being threaded, a simple set of signal handlers would not work + * very well for nwamd.  Instead nwamd blocks signals at startup and + * then starts a thread which sits in sigwait(2) waiting for signals. + * When a signal is received the signal handling thread dispatches it. + * It handles: + * - shutting down, done by creating an event which is passed through the + *   system allowing the various subsystems to do any necessary cleanup. + * - SIGALRM for timers. + * - SIGHUP for instance refresh, which tells us to look up various + *   properties from SMF(5). + * + * subprocess management + * nwamd starts several different subprocesses to manage the system.  Some + * of those start other processes (e.g. `ifconfig <if> dhcp` ends up starting + * dhcpagent if necessary).  Due to the way we manage signals if we started + * those up without doing anything special their signal mask would mostly + * block signals.  So we restore the signal mask when we start subprocesses. + * This is especially important with respect to DHCP as later when we exit + * we need to kill the dhcpagent process which we started; for details, see + * the block comment in state_machine.c in its cleanup() function. + */ + +/* + * In this file there are several utility functions which might otherwise + * belong in util.c, but since they are only called from main(), they can + * live here as static functions: + * - syslog set-up + * - daemonizing + * - looking up SMF(5) properties + * - signal handling + * - managing privileges(5) + */ + +static void +start_logging(void) +{ +	openlog("nwamd", LOG_PID | LOG_NDELAY, LOG_DAEMON); +} + +static void +daemonize(void) +{ +	pid_t pid; + +	/* +	 * A little bit of magic here.  By the first fork+setsid, we +	 * disconnect from our current controlling terminal and become +	 * a session group leader.  By forking again without calling +	 * setsid again, we make certain that we are not the session +	 * group leader and can never reacquire a controlling terminal. +	 */ +	if ((pid = fork()) == (pid_t)-1) { +		syslog(LOG_ERR, "fork 1 failed"); +		exit(EXIT_FAILURE); +	} +	if (pid != 0) { +		(void) wait(NULL); +		dprintf("child %d exited, daemonizing", pid); +		_exit(0); +	} +	if (setsid() == (pid_t)-1) { +		syslog(LOG_ERR, "setsid"); +		exit(EXIT_FAILURE); +	} +	if ((pid = fork()) == (pid_t)-1) { +		syslog(LOG_ERR, "fork 2 failed"); +		exit(EXIT_FAILURE); +	} +	if (pid != 0) { +		_exit(0); +	} +	(void) chdir("/"); +	(void) umask(022); +	closelog(); +	(void) closefrom(STDIN_FILENO); +	(void) open("/dev/null", O_RDONLY); +	(void) open("/dev/null", O_WRONLY); +	(void) dup2(STDOUT_FILENO, STDERR_FILENO); +	start_logging(); +} + +/* + * Look up nwamd property values and set daemon variables appropriately. + * This function will be called on startup and via the signal handling + * thread on receiving a HUP (which occurs when the nwam service is + * refreshed). + */ +static void +lookup_daemon_properties(void) +{ +	boolean_t debug_set; +	uint64_t scan_interval; + +	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; +	dprintf("Read daemon configuration properties."); +} + +/* ARGSUSED */ +static void * +sighandler(void *arg) +{ +	struct np_event *ev; +	sigset_t sigset; +	int sig; +	uint32_t now; + +	(void) sigfillset(&sigset); + +	for (;;) { +		sig = sigwait(&sigset); +		dprintf("signal %d caught", sig); +		switch (sig) { +		case SIGALRM: +			/* +			 * We may have multiple interfaces with +			 * scheduled timers; walk the list and +			 * create a timer event for each one. +			 */ +			timer_expire = TIMER_INFINITY; +			now = NSEC_TO_SEC(gethrtime()); +			walk_interface(check_interface_timer, &now); +			break; +		case SIGHUP: +			/* +			 * Refresh action - reread configuration properties. +			 */ +			lookup_daemon_properties(); +			break; +		default: +			syslog(LOG_NOTICE, "%s received, shutting down", +			    strsignal(sig)); +			shutting_down = B_TRUE; +			if ((ev = malloc(sizeof (*ev))) == 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); +	} +} + +static void +init_signalhandling(void) +{ +	pthread_attr_t attr; +	pthread_t sighand; +	int err; +	sigset_t new; + +	(void) sigfillset(&new); +	(void) pthread_sigmask(SIG_BLOCK, &new, &original_sigmask); +	(void) pthread_attr_init(&attr); +	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); +	if (err = pthread_create(&sighand, &attr, sighandler, NULL)) { +		syslog(LOG_ERR, "pthread_create system: %s", strerror(err)); +		exit(EXIT_FAILURE); +	} else { +		dprintf("signal handler thread: %d", sighand); +	} +	(void) pthread_attr_destroy(&attr); +} + +static void +change_user_set_privs(void) +{ +	priv_set_t *priv_set; + +	priv_set = priv_allocset(); +	if (getppriv(PRIV_PERMITTED, priv_set) == -1) { +		dprintf("getppriv %s", strerror(errno)); +	} else { +		char *p; + +		p = priv_set_to_str(priv_set, ',', 0); +		dprintf("started with privs %s", p != NULL ? p : "Unknown"); +		free(p); +	} + +	priv_emptyset(priv_set); +	(void) priv_addset(priv_set, "basic"); +	(void) priv_addset(priv_set, "file_chown_self"); +	(void) priv_addset(priv_set, "file_dac_read"); +	(void) priv_addset(priv_set, "file_dac_write"); +	(void) priv_addset(priv_set, "net_privaddr"); +	(void) priv_addset(priv_set, "net_rawaccess"); +	(void) priv_addset(priv_set, "proc_exec"); +	(void) priv_addset(priv_set, "proc_fork"); +	(void) priv_addset(priv_set, "proc_info"); +	(void) priv_addset(priv_set, "proc_owner"); +	(void) priv_addset(priv_set, "proc_session"); +	(void) priv_addset(priv_set, "proc_setid"); +	(void) priv_addset(priv_set, "sys_ip_config"); +	(void) priv_addset(priv_set, "sys_ipc_config"); +	(void) priv_addset(priv_set, "sys_net_config"); +	(void) priv_addset(priv_set, "sys_res_config"); +	(void) priv_addset(priv_set, "sys_resource"); + +	if (setppriv(PRIV_SET, PRIV_INHERITABLE, priv_set) == -1) { +		syslog(LOG_ERR, "setppriv inheritable: %m"); +		priv_freeset(priv_set); +		exit(EXIT_FAILURE); +	} + +	if (setppriv(PRIV_SET, PRIV_PERMITTED, priv_set) == -1) { +		syslog(LOG_ERR, "setppriv permitted: %m"); +		priv_freeset(priv_set); +		exit(EXIT_FAILURE); +	} + +	if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_set) == -1) { +		syslog(LOG_ERR, "setppriv effective: %m"); +		priv_freeset(priv_set); +		exit(EXIT_FAILURE); +	} + +	priv_freeset(priv_set); +} + +int +main(int argc, char *argv[]) +{ +	int c; +	int scan_lev; +	struct np_event *e; + +	(void) setlocale(LC_ALL, ""); +	(void) textdomain(TEXT_DOMAIN); + +	shutting_down = B_FALSE; +	start_logging(); +	syslog(LOG_INFO, "nwamd pid %d started", getpid()); + +	while ((c = getopt(argc, argv, "fs:")) != -1) { +		switch (c) { +			case 'f': +				fg = B_TRUE; +				break; +			case 's': +				scan_lev = atoi(optarg); +				if (scan_lev >= DLADM_WLAN_STRENGTH_VERY_WEAK && +				    scan_lev <= DLADM_WLAN_STRENGTH_EXCELLENT) { +					wireless_scan_level = scan_lev; +				} else { +					syslog(LOG_ERR, "invalid signal " +					    "strength: %s", optarg); +				} +				break; +			default: +				syslog(LOG_ERR, "unrecognized option %c", +				    optopt); +				break; +		} +	} + +	lookup_daemon_properties(); + +	change_user_set_privs(); + +	if (!fg) +		daemonize(); + +	init_signalhandling(); + +	init_mutexes(); + +	lookup_zonename(zonename, sizeof (zonename)); + +	initialize_interfaces(); + +	llp_parse_config(); + +	(void) start_event_collection(); + +	while ((e = np_queue_get_event()) != NULL) { /* forever */ + +		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; +		} +	} +	return (0); +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c b/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c new file mode 100644 index 0000000000..19cbda649f --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c @@ -0,0 +1,218 @@ +/* + * 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 2007 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. + * + * This functionality is built around state_machine() which consumes an event. + * The events which are input to this function are generated by either a change + * in interface state or a signal.  The events which correspond to signals are + * either external requests to shutdown or a timer event.  The interface events + * indicate if an interface has acquired a new address (via DHCP) or if a new + * interface has appeared on the system.  The latter event is used to detect new + * links. + * + * state_machine() calls high level routines in llp.c and interface.c to act on + * the state of the machine in response to events. + */ + +#include <assert.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" + +void +state_machine(struct np_event *e) +{ +	struct interface *evif; +	llp_t *evllp, *prefllp = NULL; +	uint64_t flags; +	boolean_t dhcp_restored = B_FALSE; + +	dprintf("state_machine(event type: %s, name: %s)", +	    npe_type_str(e->npe_type), STRING(e->npe_name)); +	switch (e->npe_type) { +	case EV_TIMER: +		/* Our timer popped; check our dhcp status. */ +		if ((evif = get_interface(e->npe_name)) == NULL) { +			dprintf("couldn't find waiting interface; " +			    "ignoring EV_TIMER event"); +			break; +		} +		flags = get_ifflags(evif->if_name, evif->if_family); +		if ((flags & IFF_DHCPRUNNING) == 0 || (flags & IFF_UP) != 0) { +			/* +			 * dhcp did come up successfully, or we're no +			 * longer trying to do dhcp in this interface; +			 * so no need to worry about the timer expiring. +			 */ +			dprintf("timer popped for %s, but dhcp state is okay " +			    "(ifflags 0x%llx)", evif->if_name, flags); +			break; +		} +		/* +		 * dhcp has not yet completed; give up on it for +		 * now and, if it is the currently active llp, +		 * switch to the next best. +		 */ +		dprintf("giving up on dhcp on %s (ifflags 0x%llx)", +		    evif->if_name, flags); +		evif->if_lflags |= IF_DHCPFAILED; +		if (interface_is_active(evif)) { +			prefllp = llp_best_avail(); +			llp_swap(prefllp); +		} + +		break; + +	case EV_ROUTING: +		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)); +			break; +		} +		prefllp = llp_best_avail(); +		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); +		} + +		break; + +	case EV_NEWADDR: +		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_NEWADDR event", +			    (void *)evif, (void *)evllp, STRING(e->npe_name)); +			break; +		} +		flags = get_ifflags(evif->if_name, evif->if_family); +		if (!(flags & IFF_DHCPRUNNING) && !(flags & IFF_UP) && +		    evllp->llp_ipv4src == IPV4SRC_DHCP) { +			evif->if_timer_expire = 0; +			if ((evif->if_lflags & IF_DHCPFAILED) != 0) { +				evif->if_lflags &= ~IF_DHCPFAILED; +				dhcp_restored = B_TRUE; +			} +		} +		if (evllp != link_layer_profile) { +			if (dhcp_restored && +			    llp_high_pri(evllp, link_layer_profile) == evllp) { +				dprintf("state_machine: dhcp completed on " +				    "higher priority llp (%s); swapping", +				    llp_prnm(evllp)); +				llp_swap(evllp); +			} 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, +				    evllp->llp_ipv4src == IPV4SRC_DHCP, B_FALSE, +				    evllp->llp_ipv6onlink); +				break; +			} +		} +		/* +		 * An address has been assigned to the current active link. +		 * Notify the user, and activate the upper layer profile. +		 * +		 * Since other changes to the link (netmask change, broadcast +		 * addr change, etc.) can cause a NEWADDR event (XXX would +		 * be good if our event generator could do a better job +		 * filtering!), only do this if there is not currently an +		 * active ulp. +		 */ +		if (!ulp_is_active()) { +			show_if_status(evllp->llp_lname); +			activate_upper_layer_profile(evllp->llp_ipv4src == +			    IPV4SRC_DHCP, evllp->llp_lname); +		} +		break; + +	case EV_SHUTDOWN: +		/* Cleanup not expecting to see any more events after this */ +		cleanup(); +		return; +		/* NOTREACHED */ + +	default: +		dprintf("unknown event"); +		break; +	} +} + +void +cleanup(void) +{ +	if (link_layer_profile != NULL) { +		deactivate_upper_layer_profile(); +		takedowninterface(link_layer_profile->llp_lname, +		    link_layer_profile->llp_ipv4src == IPV4SRC_DHCP, B_FALSE, +		    link_layer_profile->llp_ipv6onlink); +	} +	/* +	 * Since actions taken in nwamd result in dhcpagent being +	 * launched, it's under our contract.  Thus, it needs to be +	 * stopped when our stop method is executed.  But it needs +	 * to stick around long enough for us to release any leases +	 * we might have; thus, we don't want the stop method to +	 * explicitly kill it.  We do it here, when we know we've +	 * finished any dhcp cleanup that needed to be done. +	 */ +	dprintf("killing dhcpagent"); +	(void) start_child(PKILL, "-z", zonename, "dhcpagent", NULL); +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/structures.h b/usr/src/cmd/cmd-inet/lib/nwamd/structures.h new file mode 100644 index 0000000000..d6e89ba3ce --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/structures.h @@ -0,0 +1,183 @@ +/* + * 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 2007 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _STRUCTURES_H +#define	_STRUCTURES_H + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +#include <net/if.h> +#include <libscf.h> +#include <libdlwlan.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 + * right now. + */ +enum np_event_type { +	EV_ROUTING, +	EV_SYS, +	EV_TIMER, +	EV_SHUTDOWN, +	EV_NEWADDR +}; + +enum interface_type { +	IF_UNKNOWN, +	IF_WIRED, +	IF_WIRELESS, +	IF_TUN +}; + +struct np_event { +	enum np_event_type npe_type; +	char *npe_name; +	struct np_event *npe_next; +}; + +/* + * This structure is used to represent the current state of the system.  We + * maintain these for IPv4 as proxies for links in the system.  This is + * differentiated from the LLP which contains the intended configuration of + * the system. + * + * Currently these are stored on ifs_head with the wired interfaces sorted + * together and the wireless ones sorted together with ifs_wired and + * ifs_wireless pointed at these sublists.  Access to these lists is not + * currently MT-safe. + * + * We explicitly do not maintain IPv6 interface structures.  In the current + * state machine, IPv6 interfaces are completely dependent on their IPv4 + * counterparts.  For example, we make a decision to bring up an interface + * based on routing socket messages read from an AF_INET socket; we will + * always bring up v4, and may additionally bring up v6.  Also, when we + * start up, we find all interfaces in the system by doing 'ifconfig -a + * plumb', which will plumb v4 on all links; we always keep the v4 interface + * plumbed, taking it up and down depending on whether it's currently in use + * or not.  v6 interfaces are not plumbed initially; when we decide a link + * should be active (and its llp tells us to do v6), we'll mark the (already + * existing) v4 interface up, and 'plumb up' the v6 interface.  Conversely, + * when a link is no longer active, we 'down unplumb' the v6 interface, but + * only 'down' the v4 interface. + */ +struct interface { +	char *if_name; +	sa_family_t if_family; +	uint64_t if_flags; +	uint32_t if_lflags; +	enum interface_type if_type; +	uint32_t if_timer_expire; +	struct sockaddr *if_ipaddr; +	struct interface *if_next; +}; + +/* + * interface local flag values + */ +/* + * IF_DHCPFAILED: indicates that we timed out a dhcp request.  Will be + * cleared if the request eventually succeeds, or if the IFF_RUNNING flag + * is toggled off/on (i.e. the cable is unplugged/plugged) + */ +#define	IF_DHCPFAILED	0x01 +/* + * IF_DHCPSTARTED: much like IFF_DHCPRUNNING; but means specifically that + * we have inititated dhcp on this interface.  Used to prevent overlapping + * invocations of dhcp. + */ +#define	IF_DHCPSTARTED	0x02 + +/* + * 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. + * + * llp structures are held on the list llp_head.  Access to this list is + * protected by llp_lock. + */ +typedef struct llp { +	struct llp *llp_next; +	char	llp_lname[LIFNAMSIZ]; +	int	llp_pri;		/* lower number => higher priority */ +	enum interface_type llp_type; +	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 */ +} llp_t; + +/* + * These entries are user allocated and should be managed by whomever + * originates the structure. + */ +struct wireless_lan { +	char *essid; +	char *bssid; +	char *signal_strength; +	char *raw_wepkey; +	dladm_wlan_wepkey_t *cooked_wepkey; +	boolean_t need_wepkey; +	char *wl_if_name; +}; + +/* + * A holder for all the resources needed to get a property value + * using libscf. + */ +typedef struct scf_resources { +	scf_handle_t *sr_handle; +	scf_instance_t *sr_inst; +	scf_snapshot_t *sr_snap; +	scf_propertygroup_t *sr_pg; +	scf_property_t *sr_prop; +	scf_value_t *sr_val; +} scf_resources_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 new file mode 100644 index 0000000000..31198df801 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/util.c @@ -0,0 +1,321 @@ +/* + * 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 2007 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 + * - check for an IP interface being marked running + * - 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 + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#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 <spawn.h> +#include <wait.h> +#include <inetcfg.h> +#include <utmpx.h> +#include <pwd.h> +#include <limits.h> +#include <errno.h> +#include <zone.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +extern char **environ; +boolean_t debug = B_FALSE; + +void +dprintf(const char *fmt, ...) +{ +	va_list ap; +	char vbuf[1024]; + +	va_start(ap, fmt); +	if (debug) { +		(void) vsnprintf(vbuf, sizeof (vbuf), fmt, ap); +		syslog(LOG_DEBUG, "%d: %s", pthread_self(), vbuf); +	} +	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) +{ +	icfg_if_t intf; +	icfg_handle_t h; +	uint64_t flags = 0; + +	(void) strlcpy(intf.if_name, name, sizeof (intf.if_name)); +	intf.if_protocol = family; + +	if (icfg_open(&h, &intf) != ICFG_SUCCESS) +		return (0); + +	if (icfg_get_flags(h, &flags) != ICFG_SUCCESS) { +		/* +		 * Interfaces can be ripped out from underneath us (for example +		 * by DHCP).  We don't want to spam the console for those. +		 */ +		if (errno == ENOENT) +			dprintf("get_ifflags: icfg_get_flags failed for '%s'", +			    name); +		else +			syslog(LOG_ERR, "get_ifflags: icfg_get_flags %s af " +			    "%d: %m", name, family); +		/* just to be sure... */ +		flags = 0; +	} +	icfg_close(h); + +	return (flags); +} + +/* + * + * This starts a child process determined by command.  If command contains a + * slash then it is assumed to be a full path; otherwise the path is searched + * for an executable file with the name command.  Command is also used as + * argv[0] of the new process.  The rest of the arguments of the function + * up to the first NULL make up pointers to arguments of the new process. + * + * This function returns child exit status on success and -1 on failure. + * + * NOTE: original_sigmask must be set before this function is called. + */ +int +start_childv(const char *command, char const * const *argv) +{ +	posix_spawnattr_t attr; +	sigset_t fullset; +	int i, rc, status, n; +	pid_t pid; +	char vbuf[1024]; + +	vbuf[0] = 0; +	n = sizeof (vbuf); +	for (i = 1; argv[i] != NULL && n > 2; i++) { +		n -= strlcat(vbuf, " ", n); +		n -= strlcat(vbuf, argv[i], n); +	} +	if (argv[i] != NULL || n < 0) +		syslog(LOG_ERR, "start_childv can't log full arg vector"); + +	if ((rc = posix_spawnattr_init(&attr)) != 0) { +		dprintf("posix_spawnattr_init %d %s\n", rc, strerror(rc)); +		return (-1); +	} +	(void) sigfillset(&fullset); +	if ((rc = posix_spawnattr_setsigdefault(&attr, &fullset)) != 0) { +		dprintf("setsigdefault %d %s\n", rc, strerror(rc)); +		return (-1); +	} +	if ((rc = posix_spawnattr_setsigmask(&attr, &original_sigmask)) != 0) { +		dprintf("setsigmask %d %s\n", rc, strerror(rc)); +		return (-1); +	} +	if ((rc = posix_spawnattr_setflags(&attr, +	    POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_SETSIGMASK)) != 0) { +		dprintf("setflags %d %s\n", rc, strerror(rc)); +		return (-1); +	} + +	if ((rc = posix_spawnp(&pid, command, NULL, &attr, (char * const *)argv, +	    environ)) > 0) { +		dprintf("posix_spawnp failed errno %d", rc); +		return (-1); +	} + +	if ((rc = posix_spawnattr_destroy(&attr)) != 0) { +		dprintf("posix_spawn_attr_destroy %d %s\n", rc, strerror(rc)); +		return (-1); +	} + +	(void) waitpid(pid, &status, 0); +	if (WIFSIGNALED(status) || WIFSTOPPED(status)) { +		i = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status); +		syslog(LOG_ERR, "'%s%s' %s with signal %d (%s)", command, vbuf, +		    (WIFSIGNALED(status) ? "terminated" : "stopped"), i, +		    strsignal(i)); +		return (-2); +	} else { +		syslog(LOG_INFO, "'%s%s' completed normally: %d", command, vbuf, +		    WEXITSTATUS(status)); +		return (WEXITSTATUS(status)); +	} +} + +int +start_child(const char *command, ...) +{ +	const char **argv = NULL; +	int argv_len = 0; +	va_list ap; +	int i = 1, rc; + +	va_start(ap, command); +	do { +		if (i >= argv_len) { +			void *p; + +			argv_len = argv_len != 0 ? argv_len * 2 : 4; +			p = realloc(argv, sizeof (*argv)*argv_len); +			if (p != NULL) { +				argv = p; +			} else { +				syslog(LOG_ERR, "Out of memory in start_child"); +				free(argv); +				return (-1); +			} +		} + +		argv[i] = va_arg(ap, const char *); +	} while (argv[i++] != NULL); +	va_end(ap); +	argv[0] = command; + +	rc = start_childv(command, argv); +	free(argv); + +	return (rc); +} + +uint32_t	timer_expire = TIMER_INFINITY; + +/* + * Schedules a SIGALRM in delay seconds, unless one is already + * scheduled sooner.  If one is already scheduled later than + * delay seconds from now, that one will be replaced. + */ +void +start_timer(uint32_t now, uint32_t delay) +{ +	if (now + delay > timer_expire) +		return; + +	timer_expire = now + 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. +	 */ +	setutxent(); +	while ((utp = getutxent()) != NULL) { +		if ((utp->ut_type == USER_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) +{ +	zoneid_t zoneid = getzoneid(); + +	if (getzonenamebyid(zoneid, zonename, zonesize) >= 0) +		return; +	syslog(LOG_ERR, "could not determine zone name"); +	(void) strlcpy(zonename, GLOBAL_ZONENAME, zonesize); +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/variables.h b/usr/src/cmd/cmd-inet/lib/nwamd/variables.h new file mode 100644 index 0000000000..2a4794f1be --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/variables.h @@ -0,0 +1,60 @@ +/* + * 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 2007 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 queue_mutex; +extern pthread_cond_t queue_cond; +extern pthread_t routing, scan; + +extern llp_t *link_layer_profile; + +extern sigset_t original_sigmask; +extern pid_t ppid; + +extern boolean_t shutting_down; + +extern uint32_t timer_expire; + +extern uint_t wlan_scan_interval; +extern dladm_wlan_strength_t wireless_scan_level; + +extern const char *OUR_FMRI; +extern const char *OUR_PG; + +extern boolean_t debug; + +extern char zonename[ZONENAME_MAX]; + +#endif /* _VARIABLES_H */ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c new file mode 100644 index 0000000000..72e6708f10 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c @@ -0,0 +1,1596 @@ +/* + * 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 2007 Sun Microsystems, Inc.  All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident	"%Z%%M%	%I%	%E% SMI" + +/* + * This file containes all the routines to handle wireless (more + * accurately, 802.11 "WiFi" family only at this moment) operations. + * This is only phase 0 work so the handling is pretty simple. + * + * When the daemon starts up, for each WiFi interface detected, it'll + * spawn a thread doing an access point (AP) scanning.  After the scans + * finish and if one of the WiFi interfaces is chosen to be active, the + * code will pop up a window showing the scan results and wait for the + * user's input on which AP to connect to and then complete the AP + * connection and IP interface set up.  WEP is supported to connect to + * those APs which require it.  The code also maintains a list of known + * WiFi APs in the file KNOWN_WIFI_NETS.  Whenever the code successfully + * 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 one known AP, the code will automatically + * connect to that AP without asking the user.  But if the detected signal + * strength of that AP is weaker than some other AP's, the code will still + * pop up a window asking for user input. + * + * If the AP scan results contain more than one known APs, the code will + * pop up a window listing those known APs only.  If the user does not + * make a choice, the full list of available APs will be shown. + * + * If the AP scan results contain no known AP, the full list of available + * APs is shown and the user is asked which AP to connect to. + * + * Note that not all APs broadcast the Beacon.  And some events may + * happen during the AP scan such that not all available APs are found. + * So the code also allows a user to manually input an AP's data in the + * pop up window.  This allows a user to connect to the aforementioned + * "hidden" APs. + * + * The code also periodically (specified by wlan_scan_interval) checks + * for the health of the AP connection.  If the signal strength of the + * connected AP drops below a threshold (specified by wireless_scan_level), + * the code will try to do another scan to find out other APs available. + * If there is currently no connected AP, a scan will also be done + * periodically to look for available APs.  In both cases, if there are + * new APs, the above AP connection procedure will be performed. + * + * One limitation of the current code is that a user cannot initiate a + * WiFi APs scan manually.  A manual scan can only be done when the code + * shows a pop up window asking for user input.  Suppose there is no + * connected AP and periodic scan is going on.  If a user wants to + * connect to a hidden AP, this is only possible if there is another + * non-hidden AP available such that after a periodic scan, the pop up + * window is shown.  This will be fixed in a later phase. + */ + +#include <unistd.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <strings.h> +#include <syslog.h> +#include <assert.h> +#include <limits.h> +#include <errno.h> +#include <sys/stat.h> +#include <syslog.h> +#include <pthread.h> +#include <sys/wait.h> +#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> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +static pthread_mutex_t wifi_mutex; +static pthread_mutexattr_t wifi_mutex_attr; +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; + +/* + * 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; + +/* 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 */ + +static int wepkey_string_to_secobj_value(char *, uint8_t *, uint_t *); +static int store_wepkey(char *, char *, char *); +static dladm_wlan_wepkey_t *retrieve_wepkey(const char *, const char *); + +static boolean_t add_wlan_entry(struct interface *, char *, char *, char *, +    boolean_t); +static boolean_t already_in_visited_wlan_list(const struct wireless_lan *); +static boolean_t check_wlan(const char *, const char *); +static boolean_t connect_or_autoconf(struct wireless_lan *, const char *); +static return_vals_t connect_to_new_wlan(const struct wireless_lan *, int, +    const char *); +static boolean_t find_wlan_entry(struct interface *, char *, char *); +static void free_wireless_lan(struct wireless_lan *); +static struct wireless_lan *get_specific_lan(void); +static void get_user_wepkey(struct wireless_lan *); +static char *get_zenity_response(const char *); +static boolean_t wlan_autoconf(const char *ifname); +static int zenity_height(int); +static boolean_t get_scan_results(void *, dladm_wlan_attr_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. + */ +uint_t wlan_scan_interval = 120; + +/* + * The variable wireless_scan_level specifies the lowest signal level + * when a periodic wireless scan needs to be done. + */ +dladm_wlan_strength_t wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK; + +void +init_mutexes(void) +{ +	(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); +} + +/* + * wlan is expected to be non-NULL. + */ +static void +get_user_wepkey(struct wireless_lan *wlan) +{ +	char zenity_cmd[1024]; +	char buf[1024]; +	FILE *zcptr; + +	/* +	 * First, test if we have wepkey stored as secobj. If so, +	 * no need to prompt for it. +	 */ +	wlan->cooked_wepkey = retrieve_wepkey(wlan->essid, wlan->bssid); +	if (wlan->cooked_wepkey != NULL) { +		dprintf("get_user_wepkey: retrieve_wepkey() returns non NULL"); +		return; +	} + +	(void) snprintf(zenity_cmd, sizeof (zenity_cmd), +	    "%s --entry --text=\"%s %s\"" +	    " --title=\"%s\" --hide-text", ZENITY, +	    gettext("Enter WEP key for WiFi network"), wlan->essid, +	    gettext("Enter WEP key")); + +	if (!valid_graphical_user(B_TRUE)) +		return; + +	zcptr = popen(zenity_cmd, "r"); +	if (zcptr != NULL) { +		if (fgets(buf, sizeof (buf), zcptr) != NULL) { +			wlan->raw_wepkey = strdup(buf); +			if (wlan->raw_wepkey != NULL) { +				/* Store WEP key persistently */ +				if (store_wepkey(wlan->essid, wlan->bssid, +				    wlan->raw_wepkey) != 0) { +					syslog(LOG_ERR, +					    "get_user_wepkey: failed to store" +					    " user specified WEP key"); +				} +			} else { +				syslog(LOG_ERR, +				    "get_user_wepkey: strdup failed"); +			} +		} +		(void) pclose(zcptr); +	} else { +		syslog(LOG_ERR, "Could not run %s: %m", ZENITY); +	} +} + +static boolean_t +find_wlan_entry(struct interface *intf, char *essid, char *bssid) +{ +	int i; + +	(void) pthread_mutex_lock(&wifi_mutex); +	/* Check if the new entry is already there. */ +	for (i = 0; i < wireless_lan_used; i++) { +		/* +		 * Assume that essid and bssid are already NULL terminated. +		 * Note that we also check for the interface name here. +		 * If there is only one wireless interface, it should not +		 * matter.  But if there are more than 1, then it is safer +		 * to use the interface which finds the AP to connect to +		 * it. +		 */ +		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); +		} +	} +	(void) pthread_mutex_unlock(&wifi_mutex); +	return (B_FALSE); +} + +static void +free_wireless_lan(struct wireless_lan *wlp) +{ +	free(wlp->essid); +	wlp->essid = NULL; +	free(wlp->bssid); +	wlp->bssid = NULL; +	free(wlp->signal_strength); +	wlp->signal_strength = NULL; +	free(wlp->raw_wepkey); +	wlp->raw_wepkey = NULL; +	free(wlp->cooked_wepkey); +	wlp->cooked_wepkey = 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, boolean_t wep) +{ +	int n; + +	(void) pthread_mutex_lock(&wifi_mutex); + +	if (wireless_lan_used == wireless_lan_count) { +		int newcnt; +		struct wireless_lan *r; + +		newcnt = (wireless_lan_count == 0) ? +		    WIRELESS_LAN_INIT_COUNT : wireless_lan_count * 2; +		r = realloc(wlans, newcnt * sizeof (*wlans)); +		if (r == NULL) { +			syslog(LOG_ERR, "add_wlan_entry: realloc failed"); +			(void) pthread_mutex_unlock(&wifi_mutex); +			return (B_FALSE); +		} +		(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].need_wepkey = wep; +	wlans[n].raw_wepkey = NULL; +	wlans[n].cooked_wepkey = NULL; +	if (wlans[n].essid == NULL || wlans[n].bssid == NULL || +	    wlans[n].signal_strength == NULL || wlans[n].wl_if_name == NULL) { +		syslog(LOG_ERR, "add_wlan_entry: strdup failed"); +		free_wireless_lan(&(wlans[n])); +		(void) pthread_mutex_unlock(&wifi_mutex); +		return (B_FALSE); +	} +	wireless_lan_used++; +	(void) pthread_mutex_unlock(&wifi_mutex); +	return (B_TRUE); +} + +static void +clear_lan_entries(void) +{ +	int i; + +	(void) pthread_mutex_lock(&wifi_mutex); +	for (i = 0; i < wireless_lan_used; i++) +		free_wireless_lan(&(wlans[i])); +	wireless_lan_used = 0; +	(void) pthread_mutex_unlock(&wifi_mutex); +} + +/* + * Verify if a WiFi NIC is associated with the given ESSID.  If the given + * ESSID is NULL, and if the NIC is already connected,  return true. + * Otherwise, + * + * 1. If the NIC is associated with the given ESSID, return true. + * 2. If the NIC is not associated with any AP, return false. + * 3. If the NIC is associated with a different AP, tell the driver + *    to disassociate with it and then return false. + */ +static boolean_t +check_wlan(const char *intf, const char *exp_essid) +{ +	dladm_wlan_linkattr_t attr; +	dladm_status_t status; +	char cur_essid[DLADM_STRSIZE]; +	char errmsg[DLADM_STRSIZE]; + +	status = dladm_wlan_get_linkattr(intf, &attr); +	if (status != DLADM_STATUS_OK) { +		dprintf("check_wlan: dladm_wlan_get_linkattr() failed: %s", +		    dladm_status2str(status, errmsg)); +		return (B_FALSE); +	} +	if (attr.la_status == DLADM_WLAN_LINKSTATUS_DISCONNECTED) +		return (B_FALSE); +	if (exp_essid == NULL) +		return (B_TRUE); +	(void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, cur_essid); + +	/* Is the NIC associated with the expected one? */ +	if (strcmp(cur_essid, exp_essid) == 0) +		return (B_TRUE); + +	/* Tell the driver to disassociate with the current AP. */ +	if (dladm_wlan_disconnect(intf) != DLADM_STATUS_OK) +		dprintf("check_wlan: dladm_wlan_disconnect() fails"); +	return (B_FALSE); +} + +/* + * Given a wireless interface, use it to scan for available networks. + */ +boolean_t +scan_wireless_nets(struct interface *intf) +{ +	boolean_t	new_ap = B_FALSE; +	dladm_status_t	status; +	int		num_ap; + +	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. +	 */ +	(void) pthread_mutex_lock(&wifi_init_mutex); +	while (wifi_scan_intf != NULL) { +		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) { +			(void) pthread_mutex_unlock(&wifi_init_mutex); +			return (B_FALSE); +		} +		(void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); +	} +	wifi_scan_intf = intf; +	(void) pthread_mutex_unlock(&wifi_init_mutex); + +	/* +	 * Since only one scan is allowed at any one time, no need to grab +	 * a lock in checking wireless_lan_used. +	 */ +	num_ap = wireless_lan_used; +	status = dladm_wlan_scan(intf->if_name, 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); + +	(void) pthread_mutex_lock(&wifi_init_mutex); +	wifi_scan_intf = NULL; +	(void) pthread_cond_signal(&wifi_init_cond); +	(void) pthread_mutex_unlock(&wifi_init_mutex); + +	return (new_ap); +} + +/* ARGSUSED */ +static void +wireless_scan(struct interface *ifp, void *arg) +{ +	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); +		} +	} +} + +static boolean_t +get_scan_results(void *arg, dladm_wlan_attr_t *attrp) +{ + +	boolean_t 	wep; +	char		essid_name[DLADM_STRSIZE]; +	char		bssid_name[DLADM_STRSIZE]; +	char		strength[DLADM_STRSIZE]; + +	(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); + +	wep = (attrp->wa_secmode == DLADM_WLAN_SECMODE_WEP); + +	if (!find_wlan_entry(arg, essid_name, bssid_name) && +	    add_wlan_entry(arg, essid_name, bssid_name, strength, wep)) { +		return (B_TRUE); +	} +	return (B_FALSE); +} + +/* ARGSUSED */ +void * +periodic_wireless_scan(void *arg) +{ +	/* +	 * No periodic scan if the "-i" option is used to change the +	 * interval to 0. +	 */ +	if (wlan_scan_interval == 0) +		return (NULL); + +	for (;;) { +		int ret; +		dladm_wlan_linkattr_t attr; +		llp_t *cur_llp; +		struct interface *ifp; + +		ret = poll(NULL, 0, wlan_scan_interval * MILLISEC); + +		/* +		 * 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; + +		/* +		 * We do a scan if +		 * +		 * 1. There is no active profile.  Or +		 * 2. We are now disconnected from the AP.  Or +		 * 3. The signal strength falls below a certain specified level. +		 */ +		if (ret == 0) { +			if (cur_llp != NULL) { +				if (cur_llp->llp_type != IF_WIRELESS || +				    dladm_wlan_get_linkattr(cur_llp->llp_lname, +				    &attr) != DLADM_STATUS_OK) { +					continue; +				} +				if (attr.la_status == +				    DLADM_WLAN_LINKSTATUS_CONNECTED && +				    attr.la_wlan_attr.wa_strength > +				    wireless_scan_level) { +					continue; +				} +				/* +				 * Clear the IF_DHCPFAILED and IF_DHCPSTARTED +				 * flags on this interface; this is a "fresh +				 * start" for the interface, so we should +				 * retry dhcp. +				 */ +				ifp = get_interface(cur_llp->llp_lname); +				if (ifp != NULL) { +					ifp->if_lflags &= ~IF_DHCPFAILED; +					ifp->if_lflags &= ~IF_DHCPSTARTED; +				} +				/* +				 * 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); +		} +	} +	/* NOTREACHED */ +	return (NULL); +} + +/* + * Below are functions used to handle storage/retrieval of WEP keys + * for a given WLAN. The keys are stored/retrieved using dladm_set_secobj() + * and dladm_get_secobj(). + */ + +/* + * Convert wepkey hexascii string to raw secobj value. This + * code is very similar to convert_secobj() in dladm.c, it would + * be good to have a libdladm function to convert values. + */ +static int +wepkey_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp) +{ +	size_t buf_len = strlen(buf); + +	dprintf("before: wepkey_string_to_secobj_value: buf_len = %d", buf_len); +	if (buf_len == 0) { +		syslog(LOG_ERR, +		    "wepkey_string_to_secobj_value: empty WEP key"); +		return (-1); +	} + +	if (buf[buf_len - 1] == '\n') +		buf[--buf_len] = '\0'; + +	dprintf("after: wepkey_string_to_secobj_value: buf_len = %d", buf_len); +	switch (buf_len) { +	case 5:		/* ASCII key sizes */ +	case 13: +		(void) memcpy(obj_val, buf, (uint_t)buf_len); +		*obj_lenp = (uint_t)buf_len; +		break; +	case 10: +	case 26:	/* Hex key sizes, not preceded by 0x */ +		if (hexascii_to_octet(buf, (uint_t)buf_len, obj_val, obj_lenp) +		    != 0) { +			syslog(LOG_ERR, +			    "wepkey_string_to_secobj_value: invalid WEP key"); +			return (-1); +		} +		break; +	case 12: +	case 28:	/* Hex key sizes, preceded by 0x */ +		if (strncmp(buf, "0x", 2) != 0 || +		    hexascii_to_octet(buf + 2, (uint_t)buf_len - 2, obj_val, +		    obj_lenp) != 0) { +			syslog(LOG_ERR, +			    "wepkey_string_to_secobj_value: invalid WEP key"); +			return (-1); +		} +		break; +	default: +		syslog(LOG_ERR, +		    "wepkey_string_to_secobj_value: invalid WEP key length"); +		return (-1); +	} +	return (0); +} + +/* + * Print the key format into the appropriate field, then convert any ":" + * characters to ".", as ":[1-4]" is the slot indicator, which otherwise + * would trip us up.  The third parameter is expected to be of size + * DLADM_SECOBJ_NAME_MAX. + */ +static void +set_key_name(const char *essid, const char *bssid, char *name, size_t nsz) +{ +	int i; + +	if (bssid == NULL) +		(void) snprintf(name, nsz, "nwam-%s", essid); +	else +		(void) snprintf(name, nsz, "nwam-%s-%s", essid, bssid); +	for (i = 0; i < strlen(name); i++) +		if (name[i] == ':') +			name[i] = '.'; +} + +static int +store_wepkey(char *essid, char *bssid, char *raw_wepkey) +{ +	uint8_t obj_val[DLADM_SECOBJ_VAL_MAX]; +	uint_t obj_len; +	char obj_name[DLADM_SECOBJ_NAME_MAX]; +	dladm_status_t status; +	char errmsg[DLADM_STRSIZE]; + +	/* +	 * Name wepkey object for this WLAN so it can be later retrieved +	 * (name is unique for each ESSID/BSSID combination). +	 */ +	set_key_name(essid, bssid, obj_name, sizeof (obj_name)); +	dprintf("store_wepkey: obj_name is %s", obj_name); + +	if (wepkey_string_to_secobj_value(raw_wepkey, obj_val, &obj_len) != 0) { +		/* above function logs internally on failure */ +		return (-1); +	} + +	status = dladm_set_secobj(obj_name, DLADM_SECOBJ_CLASS_WEP, +	    obj_val, obj_len, +	    DLADM_OPT_CREATE | DLADM_OPT_PERSIST | DLADM_OPT_TEMP); +	if (status != DLADM_STATUS_OK) { +		syslog(LOG_ERR, "store_wepkey: could not create secure object " +		    "'%s' for wepkey: %s", obj_name, +		    dladm_status2str(status, errmsg)); +		return (-1); +	} +	return (0); +} + +/* + * retrieve_wepkey returns NULL if no wepkey was recovered from dladm + */ +static dladm_wlan_wepkey_t * +retrieve_wepkey(const char *essid, const char *bssid) +{ +	dladm_status_t status; +	char errmsg[DLADM_STRSIZE]; +	dladm_wlan_wepkey_t *cooked_wepkey; +	dladm_secobj_class_t class; + +	/* +	 * Newly-allocated wepkey must be freed by caller, or by +	 * subsequent call to retrieve_wepkey(). +	 */ +	if ((cooked_wepkey = malloc(sizeof (dladm_wlan_wepkey_t))) == NULL) { +		syslog(LOG_ERR, "retrieve_wepkey: malloc failed"); +		return (NULL); +	} + +	/* Set name appropriately to retrieve wepkey for this WLAN */ +	set_key_name(essid, bssid, cooked_wepkey->wk_name, +	    DLADM_SECOBJ_NAME_MAX); +	dprintf("retrieve_wepkey: len = %d, object = %s\n", +	    strlen(cooked_wepkey->wk_name), cooked_wepkey->wk_name); +	cooked_wepkey->wk_len = DLADM_SECOBJ_NAME_MAX; +	cooked_wepkey->wk_idx = 1; + +	/* Try the kernel first, then fall back to persistent storage. */ +	status = dladm_get_secobj(cooked_wepkey->wk_name, &class, +	    cooked_wepkey->wk_val, &cooked_wepkey->wk_len, +	    DLADM_OPT_TEMP); +	if (status != DLADM_STATUS_OK) { +		dprintf("retrieve_wepkey: dladm_get_secobj(TEMP) failed: %s", +		    dladm_status2str(status, errmsg)); +		status = dladm_get_secobj(cooked_wepkey->wk_name, &class, +		    cooked_wepkey->wk_val, &cooked_wepkey->wk_len, +		    DLADM_OPT_PERSIST); +	} + +	switch (status) { +	case DLADM_STATUS_OK: +		dprintf("retrieve_wepkey: dladm_get_secobj succeeded: len %d", +		    cooked_wepkey->wk_len); +		break; +	case DLADM_STATUS_NOTFOUND: +		/* +		 * We do not want an error in the case that the secobj +		 * is not found, since we then prompt for it. +		 */ +		free(cooked_wepkey); +		return (NULL); +	default: +		syslog(LOG_ERR, "retrieve_wepkey: could not get wepkey " +		    "from secure object '%s': %s", cooked_wepkey->wk_name, +		    dladm_status2str(status, errmsg)); +		free(cooked_wepkey); +		return (NULL); +	} + +	return (cooked_wepkey); +} + +/* Create the KNOWN_WIFI_NETS using info from the interface list.  */ +void +create_known_wifi_nets_file(void) +{ +	FILE *fp; +	int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + +	/* 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; +	} +	dprintf("Creating %s", KNOWN_WIFI_NETS); +	(void) fclose(fp); +} + +/* + * Add an entry to known_wifi_nets file given the parameters. + */ +void +update_known_wifi_nets_file(const char *essid, const char *bssid) +{ +	FILE *fp; + +	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 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... +		 */ +		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; +		} +	} +	/* now see if this info is already in the file */ +	if (known_wifi_nets_lookup(essid, bssid) == B_FALSE) { +		/* now add this to the file */ +		(void) fprintf(fp, "%s\t%s\n", essid, +		    bssid == NULL ? "" : bssid); +	} +	(void) fclose(fp); +} + +/* + * Check if the given AP (ESSID, BSSID pair) is on the known AP list. + */ +boolean_t +known_wifi_nets_lookup(const char *new_essid, const char *new_bssid) +{ +	FILE *fp; +	char line[LINE_MAX]; +	char *cp, *lasts, *essid, *bssid; +	int line_num; +	boolean_t found = B_FALSE; + +	/* +	 * For now the file format is: +	 * 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(); +		return (B_FALSE); +	} +	for (line_num = 1; fgets(line, sizeof (line), fp) != NULL; line_num++) { +		if (line[strlen(line) - 1] == '\n') +			line[strlen(line) - 1] = '\0'; + +		cp = line; +		while (isspace(*cp)) +			cp++; + +		if (*cp == '#' || *cp == '\0') +			continue; + +		if ((essid = strtok_r(cp, "\t", &lasts)) == NULL) { +			syslog(LOG_ERR, "%s:%d: not enough tokens; " +			    "ignoring entry", KNOWN_WIFI_NETS, line_num); +			continue; +		} +		bssid = strtok_r(NULL, "\t", &lasts); +		if (strcmp(essid, new_essid) != 0) +			continue; +		if (new_bssid == NULL) { +			/* +			 * no BSSID specified => ESSID match +			 * is good enough. +			 */ +			found = B_TRUE; +		} else if (bssid != NULL && strcmp(bssid, new_bssid) == 0) { +			/* Match on both is always good. */ +			found = B_TRUE; +		} +		if (found) +			break; +	} +	(void) fclose(fp); +	return (found); +} + +/* + * reqlan->essid is required (i.e., cannot be NULL) + * reqlan->bssid is optional (i.e., may be NULL) + */ +boolean_t +connect_chosen_lan(struct wireless_lan *reqlan, const char *ifname) +{ +	uint_t	keycount; +	dladm_wlan_wepkey_t *key; +	dladm_wlan_attr_t attr; +	dladm_status_t status; +	uint_t flags = DLADM_WLAN_CONNECT_NOSCAN; +	int timeout = DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT; +	char errmsg[DLADM_STRSIZE]; + +	(void) memset(&attr, 0, sizeof (attr)); +	/* try to apply essid selected by the user */ +	if (reqlan->essid == NULL) +		return (B_FALSE); +	dprintf("connect_chosen_lan(%s, %s, %s)", reqlan->essid, +	    STRING(reqlan->bssid), ifname); + +	/* If it is already connected to the required AP, just return. */ +	if (check_wlan(ifname, reqlan->essid)) +		return (B_TRUE); + +	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, ifname); +		return (B_FALSE); +	} +	attr.wa_valid = DLADM_WLAN_ATTR_ESSID; +	if (reqlan->bssid != NULL) { +		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, ifname); +			return (B_FALSE); +		} +		attr.wa_valid |= DLADM_WLAN_ATTR_BSSID; +	} + +	/* First check for the wepkey */ +	if (reqlan->need_wepkey) { +		get_user_wepkey(reqlan); +		if (reqlan->cooked_wepkey == NULL) +			return (B_FALSE); +		attr.wa_valid |= DLADM_WLAN_ATTR_SECMODE; +		attr.wa_secmode = DLADM_WLAN_SECMODE_WEP; +		key = reqlan->cooked_wepkey; +		keycount = 1; +		dprintf("connect_chosen_lan: retrieved key"); +	} else { +		key = NULL; +		keycount = 0; +	} + +	/* +	 * Connect; only scan if a bssid was not specified. +	 * If it times out and we were trying with a bssid, +	 * try a second time with just the ESSID. +	 */ + +	status = dladm_wlan_connect(ifname, &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) { +		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(ifname, &attr, timeout, key, +		    keycount, flags); +	} +	if (status != DLADM_STATUS_OK) { +		syslog(LOG_ERR, +		    "connect_chosen_lan: connect to '%s' failed on '%s': %s", +		    reqlan->essid, ifname, dladm_status2str(status, errmsg)); +		return (B_FALSE); +	} +	return (B_TRUE); +} + +/* + * First attempt to connect to the network specified by essid. + * If that fails, attempt to connect using autoconf. + */ +static boolean_t +connect_or_autoconf(struct wireless_lan *reqlan, const char *ifname) +{ +	if (!connect_chosen_lan(reqlan, ifname)) { +		syslog(LOG_WARNING, +		    "Could not connect to chosen WLAN %s, going to auto-conf", +		    reqlan->essid); +		return (wlan_autoconf(ifname)); +	} +	return (B_TRUE); +} + +/* + * The +1 is for the extra "compare" row, the 24 is for the font spacing + * and the 125 if extra for the buttons et al. + */ +static int +zenity_height(int rows) +{ +	return (((rows + 1) * 24) + 125); +} + +static return_vals_t +connect_to_new_wlan(const struct wireless_lan *lanlist, int num, +    const char *ifname) +{ +	int i, rtn, j = 0; +	struct interface *intf; +	struct wireless_lan *reqlan; +	char buf[2048]; +	char *endbuf = buf; +	size_t buflen = 0; +	char zenity_cmd[2048]; +	char *other_str = gettext("Other"); +	char *rescan_str = gettext("Rescan"); +	boolean_t autoconf = B_FALSE; + +	dprintf("connect_to_new_wlan(..., %d, %s)", num, ifname); + +	if (num == 0) { +		display(gettext("No Wifi networks found; continuing in case " +		    "you know of any which do not broadcast.")); +	} + +	if ((intf = get_interface(ifname)) == NULL) { +		dprintf("connect_to_new_wlan: cannot find wireless interface: " +		    "%s", ifname); +		return (FAILURE); +	} + +	/* build list for display */ +	buf[0] = '\0'; +	for (i = 0; i < num; i++) { +		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, ifname) != 0) { +			dprintf("connect_to_new_wlan: wrong interface (%s) for " +			    "%s (should be %s)", ifname, lanlist[i].essid, +			    lanlist[i].wl_if_name); +			continue; +		} + +		j++; +		/* +		 * Zenity uses a space as its delimiter, so put the ESSID in +		 * quotes so it won't get confused if there is a space in the +		 * ESSID name. +		 */ +		if (sizeof (buf) - 1 > buflen) { +			buflen += snprintf(endbuf, sizeof (buf) - buflen, +			    "%d '%s' %s %s '%s' ", j, +			    lanlist[i].essid, lanlist[i].bssid, +			    lanlist[i].need_wepkey ? "WEP" : "none", +			    lanlist[i].signal_strength); +			endbuf = buf + buflen; +		} +	} +	if (sizeof (buf) - 1 > buflen) { +		/* +		 * All columns except the first are empty for the "Other" +		 * and "Rescan" rows. +		 */ +		(void) snprintf(endbuf, sizeof (buf) - buflen, +		    "\"%s\" \"\" \"\" \"\" \"\" \"%s\" \"\" \"\" \"\" \"\"", +		    other_str, rescan_str); +	} + +	(void) snprintf(zenity_cmd, sizeof (zenity_cmd), +	    "%s --list --title=\"%s\"" +	    " --height=%d --width=500 --column=\"#\" --column=\"%s\"" +	    " --column=\"%s\" --column=\"%s\" --column=\"%s\"" +	    " %s ", ZENITY, gettext("Choose WiFi network you wish to activate"), +	    zenity_height(j), "ESSID", "BSSID", gettext("Encryption"), +	    gettext("Signal"), buf); + +	/* present list to user and get selection */ +	rtn = get_user_preference(zenity_cmd, other_str, rescan_str, &reqlan, +	    lanlist); +	switch (rtn) { +	case 1: +		/* user chose "other"; pop-up for specific essid */ +		reqlan = get_specific_lan(); +		break; +	case 2: +		/* user chose "Rescan" */ +		(void) scan_wireless_nets(intf); +		return (TRY_AGAIN); +	case -1: +		reqlan = NULL; +		break; +	default: +		/* common case: reqlan was set in get_user_preference() */ +		break; +	} + +	if ((reqlan == NULL) || (reqlan->essid == NULL)) { +		dprintf("did not get user preference; attempting autoconf"); +		return (wlan_autoconf(ifname) ? SUCCESS : FAILURE); +	} +	dprintf("get_user_preference() returned essid %s, bssid %s, encr %s", +	    reqlan->essid, STRING(reqlan->bssid), +	    reqlan->need_wepkey ? "WEP" : "none"); + +	/* set wepkey before first time connection */ +	if (reqlan->need_wepkey && reqlan->raw_wepkey == NULL && +	    reqlan->cooked_wepkey == NULL) +		get_user_wepkey(reqlan); + +	/* +	 * now attempt to connect to selection, backing +	 * off to autoconf if the connect fails +	 */ +	if (connect_chosen_lan(reqlan, ifname)) { +		/* succeeded, so add entry to known_essid_list_file */ +		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) +		return (SUCCESS); +	return (wlan_autoconf(ifname) ? SUCCESS : FAILURE); +} + +struct wireless_lan * +prompt_for_visited(void) +{ +	char buf[1024]; +	size_t buflen = 0; +	char *endbuf = buf; +	char zenity_cmd[1024]; +	char *select_str = gettext("Select from all available WiFi networks"); +	char *rescan_str = gettext("Rescan"); +	struct wireless_lan *req_conf = NULL, *list; +	int i = 0; + +	/* Build zenity command string */ +	buf[0] = '\0'; +	if (visited_wlan_list->total > 0) { +		struct visited_wlans *vlp; +		struct wireless_lan *wlp; + +		list = calloc(visited_wlan_list->total, +		    sizeof (struct wireless_lan)); +		if (list == NULL) { +			syslog(LOG_ERR, "prompt_for_visited: calloc failed"); +			return (NULL); +		} +		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; +			} +			i++; +			if (sizeof (buf) - 1 > buflen) { +				buflen += snprintf(endbuf, +				    sizeof (buf) - buflen, +				    "%d '%s' %s %s '%s' ", +				    i, wlp->essid, wlp->bssid, +				    wlp->need_wepkey ? "WEP" : gettext("none"), +				    wlp->signal_strength); +				endbuf = buf + buflen; +			} +			list[i-1].essid = wlp->essid; +			list[i-1].bssid = wlp->bssid; +			list[i-1].need_wepkey = wlp->need_wepkey; +			list[i-1].raw_wepkey = wlp->raw_wepkey; +			list[i-1].cooked_wepkey = wlp->cooked_wepkey; +			list[i-1].signal_strength = wlp->signal_strength; +			list[i-1].wl_if_name = wlp->wl_if_name; +		} +	} +	if (sizeof (buf) - 1 > buflen) { +		(void) snprintf(endbuf, sizeof (buf) - buflen, +		    "\"%s\"", select_str); +	} + +	(void) snprintf(zenity_cmd, sizeof (zenity_cmd), +	    "%s --list --title=\"%s\"" +	    " --height=%d --width=670 --column=\"#\" --column=\"%s\"" +	    " --column=\"%s\" --column=\"%s\" --column=\"%s\"" +	    " %s", ZENITY, gettext("Choose from pre-visited WiFi network"), +	    zenity_height(i), "ESSID", "BSSID", gettext("Encryption"), +	    gettext("Signal"), buf); + +	/* +	 * 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 (get_user_preference(zenity_cmd, select_str,	rescan_str, +	    &req_conf, list) != 0) +		req_conf = NULL; + +	free(list); +	return (req_conf); +} + +static boolean_t +wlan_autoconf(const char *ifname) +{ +	dladm_status_t status; +	boolean_t autoconf; + +	if (lookup_boolean_property(OUR_PG, "autoconf", &autoconf) == 0) { +		if (!autoconf) +			return (B_FALSE); +	} + +	/* If the NIC is already associated with something, just return. */ +	if (check_wlan(ifname, NULL)) +		return (B_TRUE); + +	/* +	 * Do autoconf, relying on the heuristics used by dladm_wlan_connect() +	 * to cycle through WLANs detected in priority order, attempting +	 * to connect. +	 */ +	status = dladm_wlan_connect(ifname, 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", +		    ifname, 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(const char *cmd) +{ +	char buf[1024]; +	size_t buf_len; +	char *rtnp; +	FILE *cptr; +	int ret; + +	if (!valid_graphical_user(B_TRUE)) +		return (NULL); + +	cptr = popen(cmd, "r"); +	if (cptr == NULL) { +		syslog(LOG_ERR, "Could not run %s: %m", ZENITY); +		return (NULL); +	} +	if (fgets(buf, sizeof (buf), cptr) != NULL) { +		buf_len = strlen(buf); +		if (buf_len > 0 && buf[buf_len - 1] == '\n') +			buf[buf_len - 1] = '\0'; +		dprintf("get_zenity_resp: zenity returned '%s'", buf); +	} else { +		buf[0] = '\0'; +		dprintf("get_zenity_resp: zenity returned nothing"); +	} +	ret = pclose(cptr); +	/* +	 * We should probably make sure that those ZENITY_* exit +	 * environment variables are not set first... +	 */ +	if (ret == -1 || !WIFEXITED(ret) || (WEXITSTATUS(ret) == 255)) { +		dprintf("get_zenity_resp: %s did not exit normally", ZENITY); +		return (NULL); +	} +	if (WEXITSTATUS(ret) == 1) { +		dprintf("get_zenity_resp: user cancelled"); +		return (NULL); +	} +	if ((rtnp = strdup(buf)) == 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. + * + * 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. + */ +int +get_user_preference(const char *cmd, 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, "malloc failed"); +		return (-1); +	} + +	response = get_zenity_response(cmd); +	if (response == NULL) { +		free(wlp); +		return (-1); +	} +	if (strcmp(response, compare_other) == 0) { +		free(response); +		free(wlp); +		return (1); +	} +	if (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->need_wepkey = sel->need_wepkey; + +	if ((sel->raw_wepkey != NULL) && +	    ((wlp->raw_wepkey = strdup(sel->raw_wepkey)) == NULL)) +		goto dup_error; + +	if (sel->cooked_wepkey != NULL) { +		wlp->cooked_wepkey = malloc(sizeof (dladm_wlan_wepkey_t)); +		if (wlp->cooked_wepkey == NULL) +			goto dup_error; +		*(wlp->cooked_wepkey) = *(sel->cooked_wepkey); +	} + +	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), wlp->need_wepkey ? "WEP" : "none", +	    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) +{ +	char specify_str[1024]; +	char *response; +	struct wireless_lan *wlp; + +	/* +	 * TRANSLATION_NOTE: the token "ESSID" should not be translated +	 * in the phrase below. +	 */ +	(void) snprintf(specify_str, sizeof (specify_str), ZENITY +	    " --entry --title=\"%s\" --text=\"%s\"", +	    gettext("Specify WiFi network"), gettext("Enter ESSID")); + +	response = get_zenity_response(specify_str); +	if (response == NULL) +		return (NULL); + +	wlp = calloc(1, sizeof (struct wireless_lan)); +	if (wlp == NULL) { +		syslog(LOG_ERR, "malloc failed: %m"); +		free(response); +		return (NULL); +	} +	wlp->essid = response; + +	(void) snprintf(specify_str, sizeof (specify_str), ZENITY +	    " --list --title=\"%s\" --text=\"%s\" --column=\"%s\" none wep", +	    gettext("Security"), gettext("Enter security"), +	    gettext("Type")); + +	response = get_zenity_response(specify_str); +	if (response != NULL && strcmp(response, "wep") == 0) +		wlp->need_wepkey = B_TRUE; + +	free(response); +	return (wlp); +} + +/* + * Returns: + * B_TRUE if things go well + * B_FALSE if we were unable to connect to anything + */ +boolean_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; +	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) +		(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; + +	/* +	 * Try to see if any of the wifi nets currently available +	 * has been used previously. If more than one available +	 * nets has been used before, then prompt user with +	 * all the applicable previously wifi nets, and ask which +	 * one to connect to. +	 */ +	for (i = 0; i < num_wlans; i++) { +		struct visited_wlans *new_wlan; + +		/* Find the AP with the highest signal. */ +		if (dladm_wlan_str2strength(cur_wlans[i].signal_strength, +		    &strength) != DLADM_STATUS_OK) { +			continue; +		} +		if (strength > strongest) +			strongest = strength; + +		if (!known_wifi_nets_lookup(cur_wlans[i].essid, +		    cur_wlans[i].bssid)) +			continue; + +		if (already_in_visited_wlan_list(&cur_wlans[i])) { +			/* don't have to add it again */ +			continue; +		} + +		/* add this to the visited_wlan_list */ +		dprintf("adding essid %s, bssid %s to visited list", +		    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_wepkey = NULL; +		new_wlan->wifi_net->cooked_wepkey = NULL; +		new_wlan->wifi_net->need_wepkey = cur_wlans[i].need_wepkey; +		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; +		} + +		new_wlan->next = visited_wlan_list->head; +		visited_wlan_list->head = new_wlan; +		visited_wlan_list->total++; +	} + +	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, ifname); +		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, ifname); +			connect_result = result ? SUCCESS : FAILURE; +		} else { +			/* +			 * The user didn't make a choice; offer the full list. +			 */ +			connect_result = connect_to_new_wlan(cur_wlans, +			    num_wlans, ifname); +			result = (connect_result == SUCCESS); +		} +	} else { +connect_any: +		/* last case, no previously visited wlan found */ +		connect_result = connect_to_new_wlan(cur_wlans, num_wlans, +		    ifname); +		result = (connect_result == SUCCESS); +	} + +all_done: +	/* +	 * We locked down the list above; free it now that we're done. +	 */ +	(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; +			} +			next = vwlp->next; +			free(vwlp); +		} +		free(visited_wlan_list); +		visited_wlan_list = NULL; +	} +	if (req_conf != NULL) { +		free_wireless_lan(req_conf); +		free(req_conf); +		req_conf = NULL; +	} +	if (connect_result == TRY_AGAIN) { +		dprintf("end of handle_wireless_lan() TRY_AGAIN"); +		goto start_over; +	} +	return (result); +} diff --git a/usr/src/cmd/svc/milestone/Makefile b/usr/src/cmd/svc/milestone/Makefile index 0b421f1825..b084a04e44 100644 --- a/usr/src/cmd/svc/milestone/Makefile +++ b/usr/src/cmd/svc/milestone/Makefile @@ -19,7 +19,7 @@  # CDDL HEADER END  #  # -# Copyright 2006 Sun Microsystems, Inc.  All rights reserved. +# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.  # Use is subject to license terms.  #  #ident	"%Z%%M%	%I%	%E% SMI" @@ -103,6 +103,7 @@ SVCMETHOD=\  	manifest-import \  	net-loopback \  	net-init \ +	net-nwam \  	net-physical \  	net-routing-setup \  	net-svc \ diff --git a/usr/src/cmd/svc/milestone/net-loopback b/usr/src/cmd/svc/milestone/net-loopback index 3688a206f5..ea30da7326 100644 --- a/usr/src/cmd/svc/milestone/net-loopback +++ b/usr/src/cmd/svc/milestone/net-loopback @@ -53,31 +53,8 @@ SUNW_NO_MPATHD=; export SUNW_NO_MPATHD  # in the boot process) and set forwarding flags.  # -# ipV4 loopback +# IPv4 loopback  /sbin/ifconfig lo0 plumb 127.0.0.1 up -# Configure the v6 loopback if any IPv6 interfaces are configured. -interface_names="`echo /etc/hostname6.*[0-9] 2>/dev/null`" -if [ "$interface_names" != "/etc/hostname6.*[0-9]" ]; then -        ORIGIFS="$IFS" -        IFS="$IFS." -        set -- $interface_names -        IFS="$ORIGIFS" -        while [ $# -ge 2 ]; do -                shift -                if [ $# -gt 1 -a "$2" != "/etc/hostname6" ]; then -                        while [ $# -gt 1 -a "$1" != "/etc/hostname6" ]; do -                                shift -                        done -                else -                        inet6_list="$inet6_list $1" -                        shift -                fi -        done -fi - -if [ -n "$inet6_list" ]; then -	/sbin/ifconfig lo0 inet6 plumb ::1 up -else -	exit $SMF_EXIT_OK -fi +# IPv6 loopback +/sbin/ifconfig lo0 inet6 plumb ::1 up diff --git a/usr/src/cmd/svc/milestone/net-nwam b/usr/src/cmd/svc/milestone/net-nwam new file mode 100644 index 0000000000..bbe5220b1b --- /dev/null +++ b/usr/src/cmd/svc/milestone/net-nwam @@ -0,0 +1,66 @@ +#!/sbin/sh +# +# 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 2007 Sun Microsystems, Inc.  All rights reserved. +# Use is subject to license terms. +# + +#ident	"%Z%%M%	%I%	%E% SMI" + +. /lib/svc/share/smf_include.sh + +# +# In a shared-IP zone we need this service to be up, but all of the work +# it tries to do is irrelevant (and will actually lead to the service +# failing if we try to do it), so just bail out. +# In the global zone and exclusive-IP zones we proceed. +# +smf_configure_ip || exit $SMF_EXIT_OK + +case "$1" in +'refresh') +	/usr/bin/pkill -HUP -z `smf_zonename` nwamd +	;; + +'start') +	if smf_is_globalzone; then +		# Initialize security objects. +		/sbin/dladm init-secobj +	fi +	# start nwamd in foreground; it will daemonize itself +	if /lib/inet/nwamd ; then +		exit $SMF_EXIT_OK +	else +		exit $SMF_EXIT_ERR_FATAL +	fi +	;; + +'stop') +	/usr/bin/pkill -z `smf_zonename` nwamd +	;; + +*) +	echo "Usage: $0 { start | stop | refresh }" +	exit $SMF_EXIT_ERR_FATAL +	;; +esac +exit $SMF_EXIT_OK diff --git a/usr/src/cmd/svc/milestone/net-svc b/usr/src/cmd/svc/milestone/net-svc index a91d3d09fa..f987e91dc4 100644 --- a/usr/src/cmd/svc/milestone/net-svc +++ b/usr/src/cmd/svc/milestone/net-svc @@ -54,6 +54,7 @@ case "$1" in  	;;  esac +interface=$2  # If boot variables are not set, set variables we use  [ -z "$_INIT_UTS_NODENAME" ] && _INIT_UTS_NODENAME=`/usr/bin/uname -n` @@ -134,72 +135,6 @@ update_nss ()  }  # -# update_hosts_file -# This routine updates the /etc/inet/hosts file with the hostname and IP -# address that was obtained via DHCP. -# -update_hosts_file () -{ -	filename=hosts -	# Delete any old lines added by dhcp. -	/usr/bin/sed -e '/# Added by DHCP$/d' /etc/inet/${filename} \ -	    > /tmp/${filename}_clear.$$ - -	shift $#	# Clear $0-9 first in case grep fails -	set -- `/usr/bin/grep "^[ 	]*$ipaddr[ 	]" \ -	    /tmp/${filename}_clear.$$ 2>/dev/null` - -	if [ $# -gt 0 ]; then -		# -		# IP address is already in the file. Ensure the -		# associated hostname is the same as the Hostname -		# property returned by the DHCP server. -		# -		/usr/bin/sed -e "/^[ 	]*${ipaddr}[ 	]/d" \ -		    /tmp/${filename}_clear.$$ >/tmp/${filename}.$$ -		echo "${ipaddr}\t${hostname}\t# Added by DHCP" \ -		    >>/tmp/${filename}.$$ -	else -		# -		# IP address is missing from the respective file.  Now check -		# to see if the hostname is present with a different IP. -		# -		shift $#	# Clear $0-9 in case grep fails -		set -- `/usr/bin/grep -s -v '^#' /tmp/${filename}_clear.$$ | \ -		    /usr/bin/egrep "[	 ]${hostname}([	 ]|$)"` - -		if [ $# -gt 0 ]; then -			# -			# Hostname is present in the file. Rewrite this line -			# to have the new IP address and the DHCP comment. -			# -			/usr/bin/sed -e "/^[ 	]*${1}[ 	]/d" \ -			    /tmp/${filename}_clear.$$ >/tmp/${filename}.$$ - -			shift	# Shift off $1 (the old IP) - -			echo "$ipaddr $*\c" | /usr/bin/tr ' ' '\t' \ -			    >>/tmp/${filename}.$$ - -			echo "\t# Added by DHCP" >>/tmp/${filename}.$$ -		else -			# -			# Hostname is not present in the named file. -			# Add a new line for the host at the end of -			# the new respective file. -			# -			/usr/bin/mv /tmp/${filename}_clear.$$ \ -			    /tmp/${filename}.$$ -			echo "${ipaddr}\t${hostname}\t# Added by DHCP" \ -			    >>/tmp/${filename}.$$ -		fi -	fi - -	/usr/bin/rm -f /tmp/${filename}_clear.$$ -	mv_file /tmp/${filename}.$$ /etc/inet/${filename} 444 -} - -#  # We now need to reset the netmask and broadcast address for our network  # interfaces.  Since this may result in a name service lookup, we want to  # now wait for NIS to come up if we previously started it. @@ -224,7 +159,11 @@ domain=`/usr/bin/domainname 2>/dev/null`  smf_netstrategy  if [ "$_INIT_NET_STRATEGY" = "dhcp" ]; then -	dnsservers=`/sbin/dhcpinfo DNSserv` +	if [ -n "$interface" ]; then +		dnsservers=`/sbin/dhcpinfo -i $interface DNSserv` +	else +		dnsservers=`/sbin/dhcpinfo DNSserv` +	fi  else  	dnsservers=""  fi @@ -242,7 +181,11 @@ if [ -n "$dnsservers" ]; then  	if [ ! -f /etc/resolv.conf ]; then  		/usr/bin/touch /etc/resolv.conf  	fi -	dnsdomain=`/sbin/dhcpinfo DNSdmain` +	if [ -n "$interface" ]; then +		dnsdomain=`/sbin/dhcpinfo -i $interface DNSdmain` +	else +		dnsdomain=`/sbin/dhcpinfo DNSdmain` +	fi  	export dnsservers dnsdomain  	/usr/bin/nawk </etc/resolv.conf >/tmp/resolv.conf.$$ '  		function writedomain() { @@ -283,22 +226,19 @@ elif /usr/bin/grep '# Added by DHCP$' /etc/nsswitch.conf >/dev/null 2>&1; then  	mv_file /tmp/nsswitch.conf.$$ /etc/nsswitch.conf 644  fi -if [ "$_INIT_NET_STRATEGY" = "dhcp" ]; then - -	hostname=`/usr/bin/uname -n` -	ipaddr=`/sbin/dhcpinfo Yiaddr` -	update_hosts_file - -else -	# We're not using a dhcp strategy, so host entries added by -	# DHCP should be removed from /etc/inet/hosts. +# Clean up any old DHCP-added entries (except loopback) in the hosts file. +if /usr/bin/grep '# Added by DHCP$' /etc/inet/hosts >/dev/null 2>&1; then +	/usr/bin/nawk '{ +		if (index($0, "# Added by DHCP") == 0 || +		    $1 == "127.0.0.1" || $1 == "::1") { +			print $0 +		} +	}' /etc/inet/hosts > /tmp/hosts.$$ +	mv_file /tmp/hosts.$$ /etc/inet/hosts 444 +fi -	if /usr/bin/grep '# Added by DHCP$' /etc/inet/hosts >/dev/null 2>&1; -	    then -		/usr/bin/sed -e '/# Added by DHCP$/d' \ -		    /etc/inet/hosts > /tmp/hosts.$$ -		mv_file /tmp/hosts.$$ /etc/inet/hosts 444 -	fi +if [ -z "$SMF_FMRI" ] || [ "$SMF_FMRI" = "svc:/network/physical:nwam" ]; then +	exit 0  fi  # diff --git a/usr/src/cmd/svc/milestone/network-physical.xml b/usr/src/cmd/svc/milestone/network-physical.xml index c491b04420..0405ddedfc 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 2006 Sun Microsystems, Inc.  All rights reserved. + Copyright 2007 Sun Microsystems, Inc.  All rights reserved.   Use is subject to license terms.   CDDL HEADER START @@ -65,21 +65,77 @@  		<propval name='duration' type='astring' value='transient' />  	</property_group> +	<template> +		<common_name> +			<loctext xml:lang='C'> +				physical network interfaces			 +			</loctext> +		</common_name> +		<documentation> +			<manpage title='ifconfig' section='1M' +				manpath='/usr/share/man' /> +		</documentation> +	</template> +  	</instance> -	<stability value='Unstable' /> +	<instance name='nwam' enabled='false'> + +	<exec_method +		type='method' +		name='start' +		exec='/lib/svc/method/net-nwam start' +		timeout_seconds='600' /> + +	<exec_method +		type='method' +		name='stop' +		exec='/lib/svc/method/net-nwam stop' +		timeout_seconds='60' /> + +	<exec_method +		type='method' +		name='refresh' +		exec='/lib/svc/method/net-nwam refresh' +		timeout_seconds='60' /> + +	<property_group name='general' type='framework'> +		<!-- to start/stop NWAM services --> +		<propval name='action_authorization' type='astring' +			value='solaris.smf.manage.nwam' /> +		<propval name='value_authorization' type='astring' +			value='solaris.smf.manage.nwam' /> +	</property_group> + +	<property_group name='nwamd' type='application'> +		<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='true' /> +		<propval name='dhcp_wait_time' type='count' value='60' /> +		<propval name='scan_interval' type='count' value='120' /> +		<propval name='value_authorization' type='astring' +			value='solaris.smf.value.nwam' /> +	</property_group>  	<template>  		<common_name>  			<loctext xml:lang='C'> -				physical network interfaces +				physical network interface autoconfiguration  			</loctext>  		</common_name>  		<documentation> -			<manpage title='ifconfig' section='1M' +			<manpage title='nwamd' section='1M'  				manpath='/usr/share/man' />  		</documentation>  	</template> + +	</instance> + +	<stability value='Unstable' /> +  </service>  </service_bundle> diff --git a/usr/src/lib/libsecdb/auth_attr.txt b/usr/src/lib/libsecdb/auth_attr.txt index fd6d0219ce..46521fbc2d 100644 --- a/usr/src/lib/libsecdb/auth_attr.txt +++ b/usr/src/lib/libsecdb/auth_attr.txt @@ -18,7 +18,7 @@  #  # CDDL HEADER END  # -# Copyright 2006 Sun Microsystems, Inc.  All rights reserved. +# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.  # Use is subject to license terms.  #  # /etc/security/auth_attr @@ -90,6 +90,7 @@ solaris.smf.manage.bind:::Manage DNS Service States::help=BindStates.html  solaris.smf.manage.cron:::Manage Cron Service States::help=SmfCronStates.html  solaris.smf.manage.hal:::Manage HAL Service States::help=SmfHALStates.html  solaris.smf.manage.name-service-cache:::Manage Name Service Cache Daemon Service States::help=SmfNscdStates.html +solaris.smf.manage.nwam:::Manage Network Auto-Magic Service States::help=SmfNWAMStates.html  solaris.smf.manage.power:::Manage Power Management Service States::help=SmfPowerStates.html  solaris.smf.manage.routing:::Manage Routing Service States::help=SmfRoutingStates.html  solaris.smf.manage.rmvolmgr:::Manage Rmvolmgr Service States::help=SmfRmvolmgrStates.html @@ -98,6 +99,7 @@ solaris.smf.manage.sendmail:::Manage Sendmail Service States::help=SmfSendmailSt  solaris.smf.manage.ssh:::Manage Secure Shell Service States::help=SmfSshStates.html  solaris.smf.manage.system-log:::Manage Syslog Service States::help=SmfSyslogStates.html  solaris.smf.value.:::Change Values of SMF Service Properties::help=SmfValueHeader.html +solaris.smf.value.nwam:::Change Values of SMF Network Auto-Magic Properties::help=SmfValueNWAM.html  solaris.smf.value.routing:::Change Values of SMF Routing Properties::help=SmfValueRouting.html  #  solaris.system.:::Machine Administration::help=SysHeader.html diff --git a/usr/src/lib/libsecdb/help/auths/Makefile b/usr/src/lib/libsecdb/help/auths/Makefile index 3f063bc0d3..a6b50e871d 100644 --- a/usr/src/lib/libsecdb/help/auths/Makefile +++ b/usr/src/lib/libsecdb/help/auths/Makefile @@ -18,7 +18,7 @@  #  # CDDL HEADER END  # -# Copyright 2006 Sun Microsystems, Inc.  All rights reserved. +# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.  # Use is subject to license terms.  #  #ident	"%Z%%M%	%I%	%E% SMI" @@ -75,12 +75,14 @@ HTMLENTS = \  	SmfModifyHeader.html \  	SmfModifyMethod.html \  	SmfNscdStates.html \ +	SmfNWAMStates.html \  	SmfPowerStates.html \  	SmfRoutingStates.html \  	SmfSendmailStates.html \  	SmfSshStates.html \  	SmfSyslogStates.html \  	SmfValueHeader.html \ +	SmfValueNWAM.html \  	SmfValueRouting.html \  	NetworkHeader.html \  	WifiConfig.html \ diff --git a/usr/src/lib/libsecdb/help/auths/SmfNWAMStates.html b/usr/src/lib/libsecdb/help/auths/SmfNWAMStates.html new file mode 100644 index 0000000000..cbfc92d892 --- /dev/null +++ b/usr/src/lib/libsecdb/help/auths/SmfNWAMStates.html @@ -0,0 +1,40 @@ +<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 2007 Sun Microsystems, Inc.  All rights reserved. +Use is subject to license terms. +--> +<!-- SCCS keyword +#ident	"%Z%%M%	%I%	%E% SMI" +--> +<!-- +   <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> +--> +<BODY> +When Manage NWAM Service States is in the Authorizations Include +column, it grants the authorization to enable, disable, or restart +Network Auto-Magic (NWAM) services. +<p> +If Manage NWAM Service States is grayed, then you are not entitled +to Add or Remove this authorization. +<BR>  +</BODY> +</HTML> diff --git a/usr/src/lib/libsecdb/help/auths/SmfValueNWAM.html b/usr/src/lib/libsecdb/help/auths/SmfValueNWAM.html new file mode 100644 index 0000000000..da69dc3353 --- /dev/null +++ b/usr/src/lib/libsecdb/help/auths/SmfValueNWAM.html @@ -0,0 +1,40 @@ +<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 2007 Sun Microsystems, Inc.  All rights reserved. +Use is subject to license terms. +--> +<!-- SCCS keyword +#ident	"%Z%%M%	%I%	%E% SMI" +--> +<!-- +   <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> +--> +<BODY> +When Value NWAM Properties is in the Authorizations Include +column, it grants the the authorization to change Network Auto-Magic +(NWAM) service property values. +<P>  +If Value NWAM Properties is grayed, then you are not entitled to +Add or Remove this authorization. +<BR>  +</BODY> +</HTML> diff --git a/usr/src/lib/libsecdb/prof_attr.txt b/usr/src/lib/libsecdb/prof_attr.txt index 6901a2b673..be1c99f66f 100644 --- a/usr/src/lib/libsecdb/prof_attr.txt +++ b/usr/src/lib/libsecdb/prof_attr.txt @@ -20,7 +20,7 @@  #  # -# Copyright 2006 Sun Microsystems, Inc.  All rights reserved. +# Copyright 2007 Sun Microsystems, Inc.  All rights reserved.  # Use is subject to license terms.  #  # ident	"%Z%%M%	%I%	%E% SMI" @@ -49,7 +49,7 @@ Mail Management:::Manage sendmail & queues:auths=solaris.smf.manage.sendmail;hel  Maintenance and Repair:::Maintain and repair a system:auths=solaris.smf.manage.system-log;help=RtMaintAndRepair.html  Media Backup:::Backup files and file systems:help=RtMediaBkup.html  Media Restore:::Restore files and file systems from backups:help=RtMediaRestore.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;profiles=Network Wifi Management;help=RtNetMngmnt.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;profiles=Network Wifi Management;help=RtNetMngmnt.html  Network Security:::Manage network and host security:auths=solaris.smf.manage.ssh;profiles=Network Wifi Security,Network Link Security;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/lib/libsocket/inet/link_addr.c b/usr/src/lib/libsocket/inet/link_addr.c index 36840521ab..bdb62fbbc7 100644 --- a/usr/src/lib/libsocket/inet/link_addr.c +++ b/usr/src/lib/libsocket/inet/link_addr.c @@ -2,9 +2,8 @@   * CDDL HEADER START   *   * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License").  You may not use this file except in compliance - * with the License. + * 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. @@ -19,8 +18,9 @@   *   * CDDL HEADER END   */ +  /* - * Copyright 2004 Sun Microsystems, Inc.  All rights reserved. + * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.   * Use is subject to license terms.   */ @@ -106,7 +106,7 @@ _link_aton(const char *ascaddr, int *maclen)  		return (NULL);  	} -	if ((netaddr = malloc(numcolons)) == NULL) { +	if ((netaddr = malloc(numcolons + 1)) == NULL) {  		*maclen = 0;  		return (NULL);  	} diff --git a/usr/src/pkgdefs/SUNW0on/prototype_com b/usr/src/pkgdefs/SUNW0on/prototype_com index 247579340a..c4ef0c557c 100644 --- a/usr/src/pkgdefs/SUNW0on/prototype_com +++ b/usr/src/pkgdefs/SUNW0on/prototype_com @@ -234,12 +234,14 @@ f none usr/lib/help/auths/locale/SmfModifyFramework.html 444 root bin  f none usr/lib/help/auths/locale/SmfModifyHeader.html 444 root bin  f none usr/lib/help/auths/locale/SmfModifyMethod.html 444 root bin  f none usr/lib/help/auths/locale/SmfNscdStates.html 444 root bin +f none usr/lib/help/auths/locale/SmfNWAMStates.html 444 root bin  f none usr/lib/help/auths/locale/SmfPowerStates.html 444 root bin  f none usr/lib/help/auths/locale/SmfRoutingStates.html 444 root bin  f none usr/lib/help/auths/locale/SmfSendmailStates.html 444 root bin  f none usr/lib/help/auths/locale/SmfSshStates.html 444 root bin  f none usr/lib/help/auths/locale/SmfSyslogStates.html 444 root bin  f none usr/lib/help/auths/locale/SmfValueHeader.html 444 root bin +f none usr/lib/help/auths/locale/SmfValueNWAM.html 444 root bin  f none usr/lib/help/auths/locale/SmfValueRouting.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 diff --git a/usr/src/pkgdefs/SUNWcsr/prototype_com b/usr/src/pkgdefs/SUNWcsr/prototype_com index 4a56993853..f8eefab542 100644 --- a/usr/src/pkgdefs/SUNWcsr/prototype_com +++ b/usr/src/pkgdefs/SUNWcsr/prototype_com @@ -327,6 +327,8 @@ s none etc/whodo=../usr/sbin/whodo  s none etc/wtmpx=../var/adm/wtmpx  d none etc/rpcsec 755 root sys  d none lib 755 root bin +d none lib/inet 755 root bin +f none lib/inet/nwamd 555 root bin  d none lib/svc 0755 root bin  d none lib/svc/bin 0755 root bin  f none lib/svc/bin/lsvcrun 0555 root sys @@ -353,6 +355,7 @@ f none lib/svc/method/manifest-import 0555 root bin  f none lib/svc/method/mpxio-upgrade 0555 root bin  f none lib/svc/method/net-init 0555 root bin  f none lib/svc/method/net-loopback 0555 root bin +f none lib/svc/method/net-nwam	0555 root bin  f none lib/svc/method/net-physical 0555 root bin  f none lib/svc/method/net-routing-setup 0555 root bin  f none lib/svc/method/net-svc 0555 root bin diff --git a/usr/src/pkgdefs/SUNWcsu/prototype_com b/usr/src/pkgdefs/SUNWcsu/prototype_com index cbbc6a9d0b..f479da7d69 100644 --- a/usr/src/pkgdefs/SUNWcsu/prototype_com +++ b/usr/src/pkgdefs/SUNWcsu/prototype_com @@ -479,12 +479,14 @@ f none usr/lib/help/auths/locale/C/SmfModifyFramework.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfModifyHeader.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfModifyMethod.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfNscdStates.html 444 root bin +f none usr/lib/help/auths/locale/C/SmfNWAMStates.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfPowerStates.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfRoutingStates.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfSendmailStates.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfSshStates.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfSyslogStates.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfValueHeader.html 444 root bin +f none usr/lib/help/auths/locale/C/SmfValueNWAM.html 444 root bin  f none usr/lib/help/auths/locale/C/SmfValueRouting.html 444 root bin  f none usr/lib/help/auths/locale/C/SysDate.html 444 root bin  f none usr/lib/help/auths/locale/C/SysHeader.html 444 root bin | 
