summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cmd-inet/lib/nwamd/interface.c
blob: e8c27373bfbb1f84dace5535e89f80afa20d89d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * 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 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).
 *
 * When an interface is taken down, we unplumb the IPv6 link-local interface
 * completely, so that dhcpagent and in.ndpd will remove any addresses they've
 * added.  Events are watched on the IPv4 interface alone, which is always
 * present for this version of NWAM.
 *
 * 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.
 *
 * The ifs_head and associated list pointers are protected by ifs_lock.  Only
 * the main thread may modify the list (single writer), and it does so with the
 * lock held.  As a consequence, the main thread alone may read the list (and
 * examine pointers) without holding any locks.  All other threads must hold
 * ifs_lock for the duration of any examination of the data structures, and
 * must not deal directly in interface pointers.  (A thread may also hold
 * machine_lock to block the main thread entirely in order to manipulate the
 * data; such use is isolated to the door interface.)
 *
 * Functions in this file have comments noting where the main thread alone is
 * the caller.  These functions do not need to acquire the lock.
 *
 * If you hold both ifs_lock and llp_lock, you must take ifs_lock first.
 */

#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <syslog.h>
#include <unistd.h>
#include <libscf.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <inetcfg.h>
#include <signal.h>
#include <stdarg.h>
#include <sys/sysmacros.h>
#include <sys/wait.h>
#include <libdllink.h>
#include <zone.h>

#include "defines.h"
#include "structures.h"
#include "functions.h"
#include "variables.h"

static pthread_mutex_t ifs_lock = PTHREAD_MUTEX_INITIALIZER;

static struct interface *ifs_head;
static struct interface *ifs_wired, *ifs_wired_last;
static struct interface *ifs_wireless, *ifs_wireless_last;

static char upper_layer_profile[MAXHOSTNAMELEN];

#define	LOOPBACK_IF	"lo0"

void
show_if_status(const char *ifname)
{
	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);
	report_interface_up(ifname, sin.sin_addr, prefixlen);
}

/*
 * If this interface matches the currently active llp, return B_TRUE.
 * Otherwise, return B_FALSE.
 * Called only from main thread.
 */
boolean_t
interface_is_active(const struct interface *ifp)
{
	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) {
		dprintf("start_dhcp: already started; returning");
		return;
	}
	ifp->if_lflags |= IF_DHCPSTARTED;

	/*
	 * If we need to use DHCP and DHCP is already controlling the
	 * interface, we don't need to do anything.  Otherwise, start it now.
	 */
	if (!(ifp->if_flags & IFF_DHCPRUNNING)) {
		dprintf("launching DHCP on %s", ifp->if_name);
		(void) start_child(IFCONFIG, ifp->if_name, "dhcp", "wait", "0",
		    NULL);
	} else {
		dprintf("DHCP already running on %s; resetting timer",
		    ifp->if_name);
	}
	ifp->if_lflags &= ~IF_DHCPFAILED;

	/* start dhcp timer */
	res = lookup_count_property(OUR_PG, "dhcp_wait_time", &timer_s);
	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 (state == NULL) {
			syslog(LOG_ERR, "smf_get_state(%s) returned \"%s\"",
			    fmri, scf_strerror(scf_error()));
		} else {
			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], *cp;
	size_t buflen;
	size_t offset;
	const char bringup[] = "/bringup";
	boolean_t should;
	int res;

	/*
	 * exec the net-svc script to update local config with
	 * any DNS information learned from the DHCP server.
	 */
	if (do_dhcp) {
		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)) {
			(void) start_child(NET_SVC_METHOD, "start", ifname,
			    NULL);
		}
	}
	f = popen(ULP_DIR "/check-conditions", "r");
	if (f == NULL) {
		/* note that this doesn't happen if the file is missing */
		syslog(LOG_ERR, "popen: check-conditions: %m");
		return;
	}
	/*
	 * We want to build a path to the user's upper layer profile script
	 * that looks like ULP_DIR "/<string we read here>/bringup".  If we
	 * leave some space at the beginning of this buffer for ULP_DIR "/"
	 * that saves us some shuffling later.
	 */
	offset = strlcpy(buffer, ULP_DIR "/", sizeof (buffer));
	cp = fgets(buffer + offset,
	    MIN(sizeof (upper_layer_profile), sizeof (buffer) - offset),
	    f);
	buflen = strlen(buffer);
	if (buffer[buflen - 1] == '\n')
		buffer[--buflen] = '\0';

	/* Need to check for script error before interpreting result */
	res = pclose(f);
	if (res == -1) {
		syslog(LOG_ERR, "check-conditions: pclose: %m");
		return;
	}
	if (WIFEXITED(res)) {
		if (WEXITSTATUS(res) == 0) {
			if (cp == NULL || *cp == '\0') {
				syslog(LOG_DEBUG,
				    "check-conditions returned no information");
			} else {
				(void) strlcpy(upper_layer_profile,
				    buffer + offset,
				    sizeof (upper_layer_profile));
				(void) strlcpy(buffer + buflen, bringup,
				    sizeof (buffer) - buflen);
				(void) start_child(PFEXEC, "-P", "basic",
				    buffer, NULL);
				syslog(LOG_NOTICE,
				    "upper layer profile %s activated",
				    upper_layer_profile);
				report_ulp_activated(upper_layer_profile);
			}
		} else if (access(ULP_DIR "/check-conditions", X_OK) == 0) {
			syslog(LOG_ERR,
			    "check-conditions exited with status %d",
			    WEXITSTATUS(res));
		} else if (errno == ENOENT) {
			syslog(LOG_DEBUG, "check-conditions not present");
		} else {
			syslog(LOG_ERR, "check-conditions: %m");
		}
	} else if (WIFSIGNALED(res)) {
		syslog(LOG_ERR, "check-conditions exit on SIG%s",
		    strsignal(WTERMSIG(res)));
	} else {
		syslog(LOG_ERR,
		    "check-conditions terminated in unknown manner");
	}
}

void
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);

	report_ulp_deactivated(upper_layer_profile);

	upper_layer_profile[0] = '\0';
}

/*
 * Returns SUCCESS if the interface is successfully brought up,
 * FAILURE if bringup fails, or WAITING if we'll need to wait on the GUI to run.
 * Called only in the main thread or a thread holding machine_lock.
 */
return_vals_t
bringupinterface(const char *ifname, const char *host, const char *ipv6addr,
    boolean_t ipv6onlink)
{
	struct interface *intf;

	intf = get_interface(ifname);
	if (intf == NULL) {
		syslog(LOG_ERR, "could not bring up interface %s: not in list",
		    ifname);
		return (FAILURE);
	}

	/* check current state; no point going on if flags are 0 */
	if ((intf->if_flags = get_ifflags(ifname, intf->if_family)) == 0) {
		dprintf("bringupinterface(%s): get_ifflags() returned 0",
		    ifname);
		return (FAILURE);
	}

	if (intf->if_type == IF_WIRELESS) {
		switch (handle_wireless_lan(ifname)) {
		case WAITING:
			intf->if_up_attempted = B_TRUE;
			return (WAITING);
		case FAILURE:
			syslog(LOG_INFO, "Could not connect to any WLAN, not "
			    "bringing %s up", ifname);
			return (FAILURE);
		}
	}
	intf->if_up_attempted = B_TRUE;

	/* physical level must now be up; bail out if not */
	intf->if_flags = get_ifflags(ifname, intf->if_family);
	if (!(intf->if_flags & IFF_RUNNING)) {
		dprintf("bringupinterface(%s): physical layer down", ifname);
		return (FAILURE);
	}

	/*
	 * 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);
		}
	}
	intf->if_v6onlink = ipv6onlink;

	if (strcmp(host, "dhcp") == 0) {
		start_dhcp(intf);
	} else {
		(void) start_child(IFCONFIG, ifname, host, NULL);
		(void) start_child(IFCONFIG, ifname, "up", NULL);
	}

	syslog(LOG_DEBUG, "brought up %s", ifname);

	return (SUCCESS);
}

/* Called only in the main thread */
void
takedowninterface(const char *ifname, libnwam_diag_cause_t cause)
{
	uint64_t flags;
	struct interface *ifp;

	dprintf("takedowninterface(%s, %d)", ifname, (int)cause);

	if ((ifp = get_interface(ifname)) == NULL) {
		dprintf("takedowninterface: can't find interface struct for %s",
		    ifname);
	}

	flags = get_ifflags(ifname, AF_INET);
	if (flags & IFF_DHCPRUNNING) {
		/*
		 * 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 without a lease to release, so we have to drop in
		 * that case.  So try release first, then fall back to drop.
		 */
		if (start_child(IFCONFIG, ifname, "dhcp", "wait", "2",
		    "release", NULL) != 0) {
			(void) start_child(IFCONFIG, ifname, "dhcp", "wait",
			    "2", "drop", NULL);
		}
	} else {
		if (flags & IFF_UP)
			(void) start_child(IFCONFIG, ifname, "down", NULL);
		/* need to unset a statically configured addr */
		(void) start_child(IFCONFIG, ifname, "0.0.0.0", "netmask",
		    "0", "broadcast", "0.0.0.0", NULL);
	}

	if (ifp == NULL || ifp->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 (ifp == NULL || ifp->if_up_attempted)
		report_interface_down(ifname, cause);

	if (ifp != NULL) {
		/* We're no longer expecting the interface to be up */
		ifp->if_flags = flags & ~IFF_UP;
		if (ifp->if_type == IF_WIRELESS) {
			/* and if it's wireless, it's not running, either */
			ifp->if_flags &= ~IFF_RUNNING;
			disconnect_wlan(ifp->if_name);
		}
		dprintf("takedown interface, zero cached ip address");
		ifp->if_lflags &= ~IF_DHCPSTARTED & ~IF_DHCPACQUIRED;
		ifp->if_ipv4addr = INADDR_ANY;
		ifp->if_up_attempted = B_FALSE;
	}
}

/*
 * Called only in the main thread
 *
 * For IPv6, unplumbing the link local interface causes dhcp and ndpd to remove
 * other addresses they have added.  We watch for routing socket events on the
 * IPv4 interface, which is always enabled, so no need to keep IPv6 around on a
 * switch.
 */
void
clear_cached_address(const char *ifname)
{
	struct interface *ifp;
	uint64_t ifflags;

	if ((ifp = get_interface(ifname)) == NULL) {
		dprintf("clear_cached_address: can't find interface struct "
		    "for %s", ifname);
		(void) start_child(IFCONFIG, ifname, "inet6", "unplumb", NULL);
		return;
	}
	if (ifp->if_v6onlink)
		(void) start_child(IFCONFIG, ifname, "inet6", "unplumb", NULL);
	ifflags = get_ifflags(ifname, AF_INET);
	if ((ifflags & IFF_UP) && !(ifflags & IFF_RUNNING))
		zero_out_v4addr(ifname);
	ifp->if_ipv4addr = INADDR_ANY;
	ifp->if_lflags &= ~IF_DHCPFLAGS;
}

/*
 * 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 **headpp, **lastpp;
	struct interface *pchain, *nextp;

	if (pthread_mutex_lock(&ifs_lock) != 0)
		return;

	switch (ifp->if_type) {
	case IF_WIRELESS:
		/*
		 * Wireless entries are in the wireless list, and are chained
		 * after the wired entries.  If there are no wired entries, then
		 * chain on main list.
		 */
		headpp = &ifs_wireless;
		lastpp = &ifs_wireless_last;
		pchain = ifs_wired_last;
		nextp = NULL;
		break;

	case IF_WIRED:
		/*
		 * Wired entries are on the wired list, and are chained before
		 * the wireless entries.
		 */
		headpp = &ifs_wired;
		lastpp = &ifs_wired_last;
		pchain = NULL;
		nextp = ifs_wireless;
		break;

	default:
		/* don't add to the list */
		(void) pthread_mutex_unlock(&ifs_lock);
		return;
	}

	/* Connect into the correct list */
	if (*lastpp == NULL) {
		/*
		 * If there's a previous list, then wire to the end of
		 * that, as we're the new head here.
		 */
		if (pchain != NULL)
			pchain->if_next = ifp;
		*headpp = ifp;
	} else {
		(*lastpp)->if_next = ifp;
	}
	*lastpp = ifp;

	ifp->if_next = nextp;

	/* Fix up the main list; it's always wired-first */
	ifs_head = ifs_wired == NULL ? ifs_wireless : ifs_wired;

	(void) pthread_mutex_unlock(&ifs_lock);
}

/*
 * Returns the interface structure upon success.  Returns NULL and sets
 * errno upon error.
 */
struct interface *
add_interface(sa_family_t family, const char *name, uint64_t flags)
{
	struct interface *i;
	libnwam_interface_type_t 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 = calloc(1, sizeof (*i))) == NULL) {
		dprintf("add_interface: malloc failed");
		return (NULL);
	}

	(void) strlcpy(i->if_name, name, sizeof (i->if_name));
	i->if_family = family;
	i->if_type = iftype;
	i->if_flags = flags == 0 ? get_ifflags(name, family) : flags;

	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_flags & IFF_RUNNING) ? "" : "not ");

	interface_list_insert(i);

	if (iftype == IF_WIRELESS)
		add_wireless_if(name);

	return (i);
}

/*
 * This is called only by the main thread.
 */
void
remove_interface(const char *ifname)
{
	struct interface *ifp, *prevp = NULL;

	if (pthread_mutex_lock(&ifs_lock) != 0)
		return;
	for (ifp = ifs_head; ifp != NULL; ifp = ifp->if_next) {
		if (strcmp(ifname, ifp->if_name) == 0) {
			if (prevp == NULL)
				ifs_head = ifp->if_next;
			else
				prevp->if_next = ifp->if_next;
			if (ifp == ifs_wired_last) {
				if ((ifs_wired_last = prevp) == NULL)
					ifs_wired = NULL;
			} else if (ifp == ifs_wired) {
				ifs_wired = ifp->if_next;
			}
			if (ifp == ifs_wireless_last) {
				if (prevp != NULL &&
				    prevp->if_type != IF_WIRELESS)
					prevp = NULL;
				if ((ifs_wireless_last = prevp) == NULL)
					ifs_wireless = NULL;
			} else if (ifp == ifs_wireless) {
				ifs_wireless = ifp->if_next;
			}
			break;
		}
		prevp = ifp;
	}
	(void) pthread_mutex_unlock(&ifs_lock);

	remove_wireless_if(ifname);

	if (ifp != NULL && ifp->if_thr != 0) {
		(void) pthread_cancel(ifp->if_thr);
		(void) pthread_join(ifp->if_thr, NULL);
	}
	free(ifp);
}

/*
 * Searches for an interface and returns the interface structure if found.
 * Returns NULL otherwise.  The caller must either be holding ifs_lock, or be
 * in the main thread.
 */
struct interface *
get_interface(const char *name)
{
	struct interface *ifp;

	if (name == NULL)
		return (NULL);

	for (ifp = ifs_head; ifp != NULL; ifp = ifp->if_next) {
		if (strcmp(name, ifp->if_name) == 0)
			break;
	}
	return (ifp);
}

/*
 * Check to see whether the interface could be started.  If the IFF_RUNNING
 * flag is set, then we're in good shape.  Otherwise, wireless interfaces are
 * special: we'll attempt to connect to an Access Point as part of the start-up
 * procedure, and IFF_RUNNING won't be present until that's done, so assume
 * that all wireless interfaces are good to go.  This is just an optimization;
 * we could start everything.
 */
static boolean_t
is_startable(struct interface *ifp)
{
	ifp->if_flags = get_ifflags(ifp->if_name, ifp->if_family);
	if (ifp->if_flags & IFF_RUNNING)
		return (B_TRUE);
	return (ifp->if_type == IF_WIRELESS);
}

/*
 * 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.
 *
 * ifs_lock is not held on entry.  The caller will cancel this thread and wait
 * for it to exit if the interface is to be deleted.
 */
static void *
gather_interface_info(void *arg)
{
	struct interface *i = arg;
	int retv;

	dprintf("Start gathering info for %s", i->if_name);

	switch (i->if_type) {
	case IF_WIRELESS:
		/* This generates EV_NEWAP when successful */
		retv = launch_wireless_scan(i->if_name);
		if (retv != 0)
			dprintf("didn't launch wireless scan: %s",
			    strerror(retv));
		break;
	case IF_WIRED:
		if (llp_get_ipv4src(i->if_name) == IPV4SRC_DHCP) {
			/*
			 * The following is to avoid locking up the state
			 * machine as it is currently the choke point.  We
			 * start dhcp with a wait time of 0; later, if we see
			 * the link go down (IFF_RUNNING is cleared), we will
			 * drop the attempt.
			 */
			if (is_startable(i))
				start_dhcp(i);
		}
		(void) np_queue_add_event(EV_LINKUP, i->if_name);
		break;
	}

	dprintf("Done gathering info for %s", i->if_name);
	i->if_thr = 0;
	return (NULL);
}

/*
 * 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, and with the ifs_lock held.
 */
void
walk_interface(void (*walker)(struct interface *, void *), void *arg)
{
	struct interface *ifp;

	if (pthread_mutex_lock(&ifs_lock) != 0)
		return;
	for (ifp = ifs_head; ifp != NULL; ifp = ifp->if_next)
		walker(ifp, arg);
	(void) pthread_mutex_unlock(&ifs_lock);
}

static void
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);
	}
}

static boolean_t
link_belongs_to_this_zone(const char *linkname)
{
	zoneid_t zoneid;
	char zonename[ZONENAME_MAX];
	int ret;

	zoneid = getzoneid();
	if (zoneid == GLOBAL_ZONEID) {
		datalink_id_t linkid;
		dladm_status_t status;
		char errstr[DLADM_STRSIZE];

		if ((status = dladm_name2info(dld_handle, linkname, &linkid,
		    NULL, NULL, NULL)) != DLADM_STATUS_OK) {
			dprintf("link_belongs_to_this_zone: could not get "
			    "linkid for %s: %s", linkname,
			    dladm_status2str(status, errstr));
			return (B_FALSE);
		}
		zoneid = ALL_ZONES;
		ret = zone_check_datalink(&zoneid, linkid);
		if (ret == 0) {
			(void) getzonenamebyid(zoneid, zonename, ZONENAME_MAX);
			dprintf("link_belongs_to_this_zone: %s is used by "
			    "non-global zone: %s", linkname, zonename);
			return (B_FALSE);
		}
	}
	return (B_TRUE);
}

/*
 * Walker function passed to dladm_walk() below - the linkname is
 * passed as the first argument, and is guaranteed to be non-NULL.
 */
/* ARGSUSED */
static int
do_add_interface(const char *name, void *arg)
{
	uint64_t flags;

	/* Do not add links belonging to other zones */
	if (!link_belongs_to_this_zone(name))
		return (DLADM_WALK_CONTINUE);

	(void) start_child(IFCONFIG, name, "plumb", NULL);
	flags = get_ifflags(name, AF_INET);

	/* If adding fails, just ignore that interface... */
	(void) add_interface(AF_INET, name, flags);

	return (DLADM_WALK_CONTINUE);
}

/*
 * 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)
{
	/*
	 * 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);

	/*
	 * Though our dependence on svc:/network/datalink-management
	 * and use of dladm_walk() assures us of being able to find
	 * all available links, we still need to be sure that
	 * svc:/system/filesystem/root is up, as that ensures that
	 * permissions are set up properly on the things we need to
	 * open to plumb ip on links.
	 */
	if (!check_svc_up(DEV_FS_ROOT_FMRI, 60))
		syslog(LOG_ERR, DEV_FS_ROOT_FMRI " never came up");

	/*
	 * Since network/physical depends on network/datalink-management,
	 * we know that service is up and can provide us with a complete
	 * list of links by the time nwamd is started.  Use dladm_walk()
	 * to walk that complete list of links, filtering for just the
	 * physical links (we're explicitly avoiding management of virtual
	 * links such as vnics/vlans/aggrs at this point).  We continue
	 * to plumb each link (in do_add_interface() now) to preserve the
	 * old 'ifconfig -a plumb' behavior.
	 */
	(void) dladm_walk(do_add_interface, dld_handle, NULL,
	    DATALINK_CLASS_PHYS, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);

	print_interface_list();

}

/*
 * Walker function used to start info gathering of each interface.  Caller
 * holds ifs_lock.
 */
void
start_if_info_collect(struct interface *ifp, void *arg)
{
	int retv;
	pthread_attr_t attr;

	/*
	 * In certain cases we need to refresh the cached flags value as
	 * it may be stale.  Notably, we can miss a DL_NOTE_LINK_DOWN
	 * event after we initialize interfaces before the routing thread
	 * is launched.
	 */
	if (arg != NULL)
		ifp->if_flags = get_ifflags(ifp->if_name, ifp->if_family);

	/*
	 * Only if the cable of the wired interface is
	 * plugged in, start gathering info from it.
	 */
	if (!is_startable(ifp)) {
		dprintf("not gathering info on %s; not running", ifp->if_name);
		return;
	}

	/*
	 * This is a "fresh start" for the interface, so clear old DHCP flags.
	 */
	ifp->if_lflags &= ~IF_DHCPFLAGS;

	(void) pthread_attr_init(&attr);
	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	if ((retv = pthread_create(&ifp->if_thr, &attr, gather_interface_info,
	    ifp)) != 0) {
		syslog(LOG_ERR, "create interface gathering thread: %s",
		    strerror(retv));
		exit(EXIT_FAILURE);
	} else {
		dprintf("interface info thread for %s: %d", ifp->if_name,
		    ifp->if_thr);
	}
}

/*
 * Walker function used to check timer for each interface.
 * If timer has expired, generate a timer event for the
 * interface.
 */
static void
iftimer(struct interface *ifp, void *arg)
{
	uint32_t now = (uint32_t)(uintptr_t)arg;

	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;

	(void) np_queue_add_event(EV_TIMER, ifp->if_name);
}

void
check_interface_timers(uint32_t now)
{
	walk_interface(iftimer, (void *)(uint32_t)now);
}

libnwam_interface_type_t
find_if_type(const char *name)
{
	uint32_t media;
	libnwam_interface_type_t type;

	if (name == NULL) {
		dprintf("find_if_type: no ifname; returning IF_UNKNOWN");
		return (IF_UNKNOWN);
	}

	type = IF_WIRED;
	if (dladm_name2info(dld_handle, name, NULL, NULL, NULL, &media) !=
	    DLADM_STATUS_OK) {
		if (strncmp(name, "ip.tun", 6) == 0 ||
		    strncmp(name, "ip6.tun", 7) == 0 ||
		    strncmp(name, "ip.6to4tun", 10) == 0)
			/*
			 * We'll need to update our tunnel detection once
			 * the clearview/tun project is integrated; tunnel
			 * names won't necessarily be ip.tunN.
			 */
			type = IF_TUN;
	} else if (media == DL_WIFI) {
		type = IF_WIRELESS;
	}

	return (type);
}

const char *
if_type_str(libnwam_interface_type_t 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");
	}
}

/*
 * This is called by the routing socket thread to update the IPv4 address on an
 * interface.  The routing socket thread cannot touch the interface structures
 * without holding the global lock, because interface structures can be
 * deleted.
 */
void
update_interface_v4_address(const char *ifname, in_addr_t addr)
{
	struct in_addr in;
	struct interface *ifp;

	if (pthread_mutex_lock(&ifs_lock) == 0) {
		if ((ifp = get_interface(ifname)) == NULL) {
			dprintf("no interface struct for %s; ignoring message",
			    ifname);
		} else if (ifp->if_ipv4addr != addr) {
			ifp->if_ipv4addr = addr;
			in.s_addr = addr;
			dprintf("cached new address %s for link %s",
			    inet_ntoa(in), ifname);
			(void) np_queue_add_event(EV_NEWADDR, ifname);
		} else {
			dprintf("same address on %s; no event", ifname);
		}
		(void) pthread_mutex_unlock(&ifs_lock);
	}
}

/*
 * This is called by the routing socket thread to update the flags on a given
 * IPv4 interface.  If the interface has changed state, then we launch an event
 * or a thread as appropriate.
 */
void
update_interface_flags(const char *ifname, int newflags)
{
	struct interface *ifp;
	int oldflags;

	if (pthread_mutex_lock(&ifs_lock) == 0) {
		if ((ifp = get_interface(ifname)) == NULL) {
			dprintf("no interface data for %s; ignoring message",
			    ifname);
		} else {
			/*
			 * Check for toggling of the IFF_RUNNING flag.
			 *
			 * On any change in the flag value, we turn off the
			 * DHCP flags; the change in the RUNNING state
			 * indicates a "fresh start" for the interface, so we
			 * should try dhcp again.
			 *
			 * If the interface was not plugged in and now it is,
			 * start info collection.
			 *
			 * If it was plugged in and now it is unplugged,
			 * generate an event.
			 */
			oldflags = ifp->if_flags;
			if ((oldflags & IFF_RUNNING) !=
			    (newflags & IFF_RUNNING)) {
				ifp->if_lflags &= ~IF_DHCPFLAGS;
			}
			if (!(newflags & IFF_DHCPRUNNING))
				ifp->if_lflags &= ~IF_DHCPFLAGS;
			ifp->if_flags = newflags;
			if (!(oldflags & IFF_RUNNING) &&
			    (newflags & IFF_RUNNING)) {
				start_if_info_collect(ifp, NULL);
			} else if ((oldflags & IFF_RUNNING) &&
			    !(newflags & IFF_RUNNING)) {
				(void) np_queue_add_event(EV_LINKDROP, ifname);
			} else {
				dprintf("no-event flag change on %s: %x -> %x",
				    ifp->if_name, oldflags, newflags);
			}
		}
		(void) pthread_mutex_unlock(&ifs_lock);
	}
}

/*
 * Called only in main thread.  Note that wireless interfaces are considered
 * "ok" even if the IFF_RUNNING bit isn't set.  This is because AP attach
 * occurs as part of the LLP selection process.
 */
boolean_t
is_interface_ok(const char *ifname)
{
	boolean_t is_ok = B_FALSE;
	struct interface *ifp;

	if ((ifp = get_interface(ifname)) != NULL &&
	    !(ifp->if_lflags & IF_DHCPFAILED) && is_startable(ifp))
		is_ok = B_TRUE;
	return (is_ok);
}

/*
 * Return the interface type for a given interface name.
 */
libnwam_interface_type_t
get_if_type(const char *ifname)
{
	libnwam_interface_type_t ift = IF_UNKNOWN;
	struct interface *ifp;

	if (pthread_mutex_lock(&ifs_lock) == 0) {
		if ((ifp = get_interface(ifname)) != NULL)
			ift = ifp->if_type;
		(void) pthread_mutex_unlock(&ifs_lock);
	}
	return (ift);
}

/*
 * Get the interface state for storing in llp_t.  This is used only with the
 * doors interface to return status flags.
 */
void
get_interface_state(const char *ifname, boolean_t *dhcp_failed,
    boolean_t *link_up)
{
	struct interface *ifp;

	*dhcp_failed = *link_up = B_FALSE;
	if (pthread_mutex_lock(&ifs_lock) == 0) {
		if ((ifp = get_interface(ifname)) != NULL) {
			if (ifp->if_lflags & IF_DHCPFAILED)
				*dhcp_failed = B_TRUE;
			if (ifp->if_flags & IFF_UP)
				*link_up = B_TRUE;
		}
		(void) pthread_mutex_unlock(&ifs_lock);
	}
}

/*
 * Dump out the interface state via debug messages.
 */
void
print_interface_status(void)
{
	struct interface *ifp;
	struct in_addr ina;

	if (pthread_mutex_lock(&ifs_lock) == 0) {
		if (upper_layer_profile[0] != '\0')
			dprintf("upper layer profile %s active",
			    upper_layer_profile);
		else
			dprintf("no upper layer profile active");
		for (ifp = ifs_head; ifp != NULL; ifp = ifp->if_next) {
			ina.s_addr = ifp->if_ipv4addr;
			dprintf("I/F %s af %d flags %llX lflags %X type %d "
			    "expire %u v6 %son-link up %sattempted addr %s",
			    ifp->if_name, ifp->if_family, ifp->if_flags,
			    ifp->if_lflags, ifp->if_type, ifp->if_timer_expire,
			    ifp->if_v6onlink ? "" : "not ",
			    ifp->if_up_attempted ? "" : "not ",
			    inet_ntoa(ina));
		}
		(void) pthread_mutex_unlock(&ifs_lock);
	}
}