1#!/bin/sh
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22#
23# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24# Use is subject to license terms.
25#
26# Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T.
27# All rights reserved.
28#
29
30# Print warnings to console
31warn_failed_ifs() {
32	echo "Failed to $1 interface(s):$2" >/dev/msglog
33}
34
35#
36# shcat file
37#   Simulates cat in sh so it doesn't need to be on the root filesystem.
38#
39shcat() {
40        while [ $# -ge 1 ]; do
41                while read i; do
42                        echo "$i"
43                done < $1
44                shift
45        done
46}
47
48#
49# inet_list	list of IPv4 interfaces.
50# inet6_list	list of IPv6 interfaces.
51# ipmp_list	list of IPMP IPv4 interfaces.
52# ipmp6_list	list of IPMP IPv6 interfaces.
53# inet_plumbed	list of plumbed IPv4 interfaces.
54# inet6_plumbed list of plumbed IPv6 interfaces.
55# ipmp_created 	list of created IPMP IPv4 interfaces.
56# ipmp6_created	list of created IPMP IPv6 interfaces.
57# inet_failed	list of IPv4 interfaces that failed to plumb.
58# inet6_failed	list of IPv6 interfaces that failed to plumb.
59# ipmp_failed 	list of IPMP IPv4 interfaces that failed to be created.
60# ipmp6_failed	list of IPMP IPv6 interfaces that failed to be created.
61#
62unset inet_list inet_plumbed inet_failed \
63	inet6_list inet6_plumbed inet6_failed \
64	ipmp_list ipmp_created ipmp_failed \
65	ipmp6_list ipmp6_created ipmp6_failed
66
67#
68# get_physical interface
69#
70# Return physical interface corresponding to the given interface.
71#
72get_physical()
73{
74	ORIGIFS="$IFS"
75	IFS="${IFS}:"
76	set -- $1
77	IFS="$ORIGIFS"
78
79	echo $1
80}
81
82#
83# get_logical interface
84#
85# Return logical interface number.  Zero will be returned
86# if there is no explicit logical number.
87#
88get_logical()
89{
90	ORIGIFS="$IFS"
91	IFS="${IFS}:"
92	set -- $1
93	IFS="$ORIGIFS"
94
95	if [ -z "$2" ]; then
96		echo 0
97	else
98		echo $2
99	fi
100}
101
102#
103# if_comp if1 if2
104#
105# Compare interfaces.  Do the physical interface names and logical interface
106# numbers match?
107#
108if_comp()
109{
110	physical_comp $1 $2 && [ `get_logical $1` -eq `get_logical $2` ]
111}
112
113#
114# physical_comp if1 if2
115#
116# Do the two interfaces share a physical interface?
117#
118physical_comp()
119{
120	[ "`get_physical $1`" = "`get_physical $2`" ]
121}
122
123#
124# in_list op item list
125#
126# Is "item" in the given list?  Use "op" to do the test, applying it to
127# "item" and each member of the list in turn until it returns success.
128#
129in_list()
130{
131	op=$1
132	item=$2
133	shift 2
134
135	while [ $# -gt 0 ]; do
136		$op $item $1 && return 0
137		shift
138	done
139
140	return 1
141}
142
143#
144# get_groupifname groupname
145#
146# Return the IPMP meta-interface name for the group, if it exists.
147#
148get_groupifname()
149{
150	/sbin/ipmpstat -gP -o groupname,group | while IFS=: read name ifname; do
151		if [ "$name" = "$1" ]; then
152			echo "$ifname"
153			return
154		fi
155	done
156}
157
158#
159# create_ipmp ifname groupname type
160#
161# Helper function for create_groupifname() that returns zero if it's able
162# to create an IPMP interface of the specified type and place it in the
163# specified group, or non-zero otherwise.
164#
165create_ipmp()
166{
167	/sbin/ifconfig $1 >/dev/null 2>&1 && return 1
168	/sbin/ifconfig $1 inet6 >/dev/null 2>&1 && return 1
169	/sbin/ifconfig $1 $3 ipmp group $2 2>/dev/null
170}
171
172#
173# create_groupifname groupname type
174#
175# Create an IPMP meta-interface name for the group.  We only use this
176# function if all of the interfaces in the group failed at boot and there
177# were no /etc/hostname[6].<if> files for the IPMP meta-interface.
178#
179create_groupifname()
180{
181	#
182	# This is a horrible way to count from 0 to 999, but in sh and
183	# without necessarily having /usr mounted, what else can we do?
184	#
185	for a in "" 1 2 3 4 5 6 7 8 9; do
186		for b in 0 1 2 3 4 5 6 7 8 9; do
187			for c in 0 1 2 3 4 5 6 7 8 9; do
188				# strip leading zeroes
189				[ "$a" = "" ] && [ "$b" = 0 ] && b=""
190				if create_ipmp ipmp$a$b$c $1 $2; then
191					echo ipmp$a$b$c
192					return
193				fi
194			done
195		done
196	done
197}
198
199#
200# get_hostname_ipmpinfo interface type
201#
202# Return all requested IPMP keywords from hostname file for a given interface.
203#
204# Example:
205#	get_hostname_ipmpinfo hme0 inet keyword [ keyword ... ]
206#
207get_hostname_ipmpinfo()
208{
209	case "$2" in
210		inet)	file=/etc/hostname.$1
211			;;
212		inet6)	file=/etc/hostname6.$1
213			;;
214		*)
215			return
216			;;
217	esac
218
219	[ -r "$file" ] || return
220
221	type=$2
222	shift 2
223
224	#
225	# Read through the hostname file looking for the specified
226	# keywords.  Since there may be several keywords that cancel
227	# each other out, the caller must post-process as appropriate.
228	#
229	while read line; do
230		[ -z "$line" ] && continue
231		/sbin/ifparse -s "$type" $line
232	done < "$file" | while read one two; do
233		for keyword in "$@"; do
234			[ "$one" = "$keyword" ] && echo "$one $two"
235		done
236	done
237}
238
239#
240# get_group_for_type interface type list
241#
242# Look through the set of hostname files associated with the same physical
243# interface as "interface", and determine which group they would configure.
244# Only hostname files associated with the physical interface or logical
245# interface zero are allowed to set the group.
246#
247get_group_for_type()
248{
249	physical=`get_physical $1`
250	type=$2
251	group=""
252
253	#
254	# The last setting of the group is the one that counts, which is
255	# the reason for the second while loop.
256	#
257	shift 2
258	for ifname in "$@"; do
259		if if_comp "$physical" $ifname; then
260			get_hostname_ipmpinfo $ifname $type group
261		fi
262	done | while :; do
263		read keyword grname || {
264			echo "$group"
265			break
266		}
267		group="$grname"
268	done
269}
270
271#
272# get_group interface
273#
274# If there is both an inet and inet6 version of an interface, the group
275# could be set in either set of hostname files.  Since inet6 is configured
276# after inet, if there's a setting in both files, inet6 wins.
277#
278get_group()
279{
280	group=`get_group_for_type $1 inet6 $inet6_list`
281	[ -z "$group" ] && group=`get_group_for_type $1 inet $inet_list`
282	echo $group
283}
284
285#
286# Given the interface name and the address family (inet or inet6), determine
287# whether this is a VRRP VNIC.
288#
289# This is used to determine whether to bring the interface up
290#
291not_vrrp_interface() {
292	macaddrtype=`/sbin/dladm show-vnic $1 -o MACADDRTYPE -p 2>/dev/null`
293
294	case "$macaddrtype" in
295	'vrrp'*''$2'')	vrrp=1
296			;;
297        *)		vrrp=0
298			;;
299	esac
300	return $vrrp
301}
302
303# doDHCPhostname interface
304# Pass to this function the name of an interface.  It will return
305# true if one should enable the use of DHCP client-side host name
306# requests on the interface, and false otherwise.
307#
308doDHCPhostname()
309{
310	if [ -f /etc/dhcp.$1 ] && [ -f /etc/hostname.$1 ]; then
311                set -- `shcat /etc/hostname.$1`
312                [ $# -eq 2 -a "$1" = "inet" ]
313                return $?
314        fi
315        return 1
316}
317
318#
319# inet_process_hostname processor [ args ]
320#
321# Process an inet hostname file.  The contents of the file
322# are taken from standard input. Each line is passed
323# on the command line to the "processor" command.
324# Command line arguments can be passed to the processor.
325#
326# Examples:
327#	inet_process_hostname /sbin/ifconfig hme0 < /etc/hostname.hme0
328#
329#	inet_process_hostname /sbin/ifparse -f < /etc/hostname.hme0
330#
331# If there is only line in an hostname file we assume it contains
332# the old style address which results in the interface being brought up
333# and the netmask and broadcast address being set ($inet_oneline_epilogue).
334#
335# Note that if the interface is a VRRP interface, do not bring the address
336# up ($inet_oneline_epilogue_no_up).
337#
338# If there are multiple lines we assume the file contains a list of
339# commands to the processor with neither the implied bringing up of the
340# interface nor the setting of the default netmask and broadcast address.
341#
342# Return non-zero if any command fails so that the caller may alert
343# users to errors in the configuration.
344#
345inet_oneline_epilogue_no_up="netmask + broadcast +"
346inet_oneline_epilogue="netmask + broadcast + up"
347
348inet_process_hostname()
349{
350	if doDHCPhostname $2; then
351		:
352	else
353		#
354		# Redirecting input from a file results in a sub-shell being
355		# used, hence this outer loop surrounding the "multiple_lines"
356		# and "ifcmds" variables.
357		#
358		while :; do
359			multiple_lines=false
360			ifcmds=""
361			retval=0
362
363			while read one rest; do
364				if [ -n "$ifcmds" ]; then
365					#
366					# This handles the first N-1
367					# lines of a N-line hostname file.
368					#
369					$* $ifcmds || retval=$?
370					multiple_lines=true
371				fi
372
373				#
374				# Strip out the "ipmp" keyword if it's the
375				# first token, since it's used to control
376				# interface creation, not configuration.
377				#
378				[ "$one" = ipmp ] && one=
379				ifcmds="$one $rest"
380			done
381
382			#
383			# If the hostname file is empty or consists of only
384			# blank lines, break out of the outer loop without
385			# configuring the newly plumbed interface.
386			#
387			[ -z "$ifcmds" ] && return $retval
388			if [ $multiple_lines = false ]; then
389				#
390				# The traditional one-line hostname file.
391				# Note that we only bring it up if the
392				# interface is not a VRRP VNIC.
393				#
394				if not_vrrp_interface $2 $3; then
395					estr="$inet_oneline_epilogue"
396				else
397					estr="$inet_oneline_epilogue_no_up"
398				fi
399				ifcmds="$ifcmds $estr"
400			fi
401
402			#
403			# This handles either the single-line case or
404			# the last line of the N-line case.
405			#
406			$* $ifcmds || return $?
407			return $retval
408		done
409	fi
410}
411
412#
413# inet6_process_hostname processor [ args ]
414#
415# Process an inet6 hostname file.  The contents of the file
416# are taken from standard input. Each line is passed
417# on the command line to the "processor" command.
418# Command line arguments can be passed to the processor.
419#
420# Examples:
421#	inet6_process_hostname /sbin/ifconfig hme0 inet6 < /etc/hostname6.hme0
422#
423#	inet6_process_hostname /sbin/ifparse -f inet6 < /etc/hostname6.hme0
424#
425# Return non-zero if any of the commands fail so that the caller may alert
426# users to errors in the configuration.
427#
428inet6_process_hostname()
429{
430    	retval=0
431	while read one rest; do
432		#
433	    	# See comment in inet_process_hostname for details.
434	        #
435		[ "$one" = ipmp ] && one=
436		ifcmds="$one $rest"
437
438		if [ -n "$ifcmds" ]; then
439			$* $ifcmds || retval=$?
440		fi
441	done
442	return $retval
443}
444
445#
446# Process interfaces that failed to plumb.  Find the IPMP meta-interface
447# that should host the addresses.  For IPv6, only static addresses defined
448# in hostname6 files are moved, autoconfigured addresses are not moved.
449#
450# Example:
451#	move_addresses inet6
452#
453move_addresses()
454{
455	type="$1"
456	eval "failed=\"\$${type}_failed\""
457	eval "list=\"\$${type}_list\""
458	process_func="${type}_process_hostname"
459	processed=""
460
461	if [ "$type" = inet ]; then
462	        typedesc="IPv4"
463		zaddr="0.0.0.0"
464		hostpfx="/etc/hostname"
465	else
466	        typedesc="IPv6"
467		zaddr="::"
468		hostpfx="/etc/hostname6"
469	fi
470
471	echo "Moving addresses from missing ${typedesc} interface(s):\c" \
472	    >/dev/msglog
473
474	for ifname in $failed; do
475		in_list if_comp $ifname $processed && continue
476
477		group=`get_group $ifname`
478		if [ -z "$group" ]; then
479			in_list physical_comp $ifname $processed || {
480				echo " $ifname (not moved -- not" \
481				    "in an IPMP group)\c" >/dev/msglog
482				processed="$processed $ifname"
483			}
484			continue
485		fi
486
487		#
488		# Lookup the IPMP meta-interface name.  If one doesn't exist,
489		# create it.
490		#
491		grifname=`get_groupifname $group`
492		[ -z "$grifname" ] && grifname=`create_groupifname $group $type`
493
494		#
495		# The hostname files are processed twice.  In the first
496		# pass, we are looking for all commands that apply to the
497		# non-additional interface address.  These may be
498		# scattered over several files.  We won't know whether the
499		# address represents a failover address or not until we've
500		# read all the files associated with the interface.
501		#
502		# In the first pass through the hostname files, all
503		# additional logical interface commands are removed.  The
504		# remaining commands are concatenated together and passed
505		# to ifparse to determine whether the non-additional
506		# logical interface address is a failover address.  If it
507		# as a failover address, the address may not be the first
508		# item on the line, so we can't just substitute "addif"
509		# for "set".  We prepend an "addif $zaddr" command, and
510		# let the embedded "set" command set the address later.
511		#
512		/sbin/ifparse -f $type `
513			for item in $list; do
514				if_comp $ifname $item && $process_func \
515				    /sbin/ifparse $type < $hostpfx.$item
516			done | while read three four; do
517				[ "$three" != addif ] && echo "$three $four \c"
518			done` | while read one two; do
519				[ -z "$one" ] && continue
520				[ "$one $two" = "$inet_oneline_epilogue" ] && \
521				    continue
522				line="addif $zaddr $one $two"
523				/sbin/ifconfig $grifname $type $line >/dev/null
524			done
525
526		#
527		# In the second pass, look for the the "addif" commands
528		# that configure additional failover addresses.  Addif
529		# commands are not valid in logical interface hostname
530		# files.
531		#
532		if [ "$ifname" = "`get_physical $ifname`" ]; then
533			$process_func /sbin/ifparse -f $type < $hostpfx.$ifname \
534			| while read one two; do
535				[ "$one" = addif ] && \
536					/sbin/ifconfig $grifname $type \
537				    	    addif $two >/dev/null
538			done
539		fi
540
541		in_list physical_comp $ifname $processed || {
542			processed="$processed $ifname"
543			echo " $ifname (moved to $grifname)\c" > /dev/msglog
544		}
545	done
546	echo "." >/dev/msglog
547}
548
549#
550# if_configure type class interface_list
551#
552# Configure all of the interfaces of type `type' (e.g., "inet6") in
553# `interface_list' according to their /etc/hostname[6].* files.  `class'
554# describes the class of interface (e.g., "IPMP"), as a diagnostic aid.
555# For inet6 interfaces, the interface is also brought up.
556#
557if_configure()
558{
559	fail=
560	type=$1
561	class=$2
562	process_func=${type}_process_hostname
563	shift 2
564
565	if [ "$type" = inet ]; then
566	        desc="IPv4"
567		hostpfx="/etc/hostname"
568	else
569	        desc="IPv6"
570		hostpfx="/etc/hostname6"
571	fi
572	[ -n "$class" ] && desc="$class $desc"
573
574	echo "configuring $desc interfaces:\c"
575	while [ $# -gt 0 ]; do
576		$process_func /sbin/ifconfig $1 $type < $hostpfx.$1 >/dev/null
577		if [ $? != 0 ]; then
578			fail="$fail $1"
579		elif [ "$type" = inet6 ]; then
580			#
581			# only bring the interface up if it is not a
582			# VRRP VNIC
583			#
584			if not_vrrp_interface $1 $type; then
585			    	/sbin/ifconfig $1 inet6 up || fail="$fail $1"
586			fi
587		fi
588		echo " $1\c"
589		shift
590	done
591	echo "."
592
593	[ -n "$fail" ] && warn_failed_ifs "configure $desc" "$fail"
594}
595
596#
597# net_reconfigure is called from the network/physical service (by the
598# net-physical and net-nwam method scripts) to perform tasks that only
599# need to be done during a reconfigure boot.  This needs to be
600# isolated in a function since network/physical has two instances
601# (default and nwam) that have distinct method scripts that each need
602# to do these things.
603#
604net_reconfigure ()
605{
606	#
607	# Is this a reconfigure boot?  If not, then there's nothing
608	# for us to do.
609	#
610	reconfig=`svcprop -c -p system/reconfigure \
611	    system/svc/restarter:default 2>/dev/null`
612	if [ $? -ne 0 -o "$reconfig" = false ]; then
613		return 0
614	fi
615
616	#
617	# Ensure that the datalink-management service is running since
618	# manifest-import has not yet run for a first boot after
619	# upgrade.  We wouldn't need to do that if manifest-import ran
620	# earlier in boot, since there is an explicit dependency
621	# between datalink-management and network/physical.
622	#
623	svcadm enable -ts network/datalink-management:default
624
625	#
626	# There is a bug in SMF which causes the svcadm command above
627	# to exit prematurely (with an error code of 3) before having
628	# waited for the service to come online after having enabled
629	# it.  Until that bug is fixed, we need to have the following
630	# loop to explicitly wait for the service to come online.
631	#
632	i=0
633	while [ $i -lt 30 ]; do
634		i=`expr $i + 1`
635		sleep 1
636		state=`svcprop -p restarter/state \
637		    network/datalink-management:default 2>/dev/null`
638		if [ $? -ne 0 ]; then
639			continue
640		elif [ "$state" = "online" ]; then
641			break
642		fi
643	done
644	if [ "$state" != "online" ]; then
645		echo "The network/datalink-management service \c"
646		echo "did not come online."
647		return 1
648	fi
649
650	#
651	# Initialize the set of physical links, and validate and
652	# remove all the physical links which were removed during the
653	# system shutdown.
654	#
655	/sbin/dladm init-phys
656	return 0
657}
658
659#
660# Check for use of the default "Port VLAN Identifier" (PVID) -- VLAN 1.
661# If there is one for a given interface, then warn the user and force the
662# PVID to zero (if it's not already set).  We do this by generating a list
663# of interfaces with VLAN 1 in use first, and then parsing out the
664# corresponding base datalink entries to check for ones without a
665# "default_tag" property.
666#
667update_pvid()
668{
669	datalink=/etc/dladm/datalink.conf
670
671	(
672		# Find datalinks using VLAN 1 explicitly
673		# configured by dladm
674		/usr/bin/nawk '
675			/^#/ || NF < 2 { next }
676			{ linkdata[$1]=$2; }
677			/;vid=int,1;/ {
678				sub(/.*;linkover=int,/, "", $2);
679				sub(/;.*/, "", $2);
680				link=linkdata[$2];
681				sub(/name=string,/, "", link);
682				sub(/;.*/, "", link);
683				print link;
684			}' $datalink
685	) | ( /usr/bin/sort -u; echo END; cat $datalink ) | /usr/bin/nawk '
686	    /^END$/ { state=1; }
687	    state == 0 { usingpvid[++nusingpvid]=$1; next; }
688	    /^#/ || NF < 2 { next; }
689	    {
690		# If it is already present and has a tag set,
691		# then believe it.
692		if (!match($2, /;default_tag=/))
693			next;
694		sub(/name=string,/, "", $2);
695		sub(/;.*/, "", $2);
696		for (i = 1; i <= nusingpvid; i++) {
697			if (usingpvid[i] == $2)
698				usingpvid[i]="";
699		}
700	    }
701	    END {
702		for (i = 1; i <= nusingpvid; i++) {
703			if (usingpvid[i] != "") {
704				printf("Warning: default VLAN tag set to 0" \
705				    " on %s\n", usingpvid[i]);
706				cmd=sprintf("dladm set-linkprop -p " \
707				    "default_tag=0 %s\n", usingpvid[i]);
708				system(cmd);
709			}
710		}
711	    }'
712}
713