xref: /illumos-gate/usr/src/cmd/svc/shell/net_include.sh (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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_inactive_ifname groupname
145#
146# Return the name of an inactive interface in `groupname', if one exists.
147#
148get_inactive_ifname()
149{
150	ORIGIFS="$IFS"
151	/sbin/ipmpstat -gP -o groupname,interfaces |
152	while IFS=: read groupname ifnames; do
153		#
154		# Skip other IPMP groups.
155	        #
156		[ "$groupname" != "$1" ] && continue
157
158		#
159		# Standby interfaces are always enclosed in ()'s, so look
160		# for the first interface name starting with a "(", and
161		# strip those off.
162		#
163		IFS=" "
164		for ifname in $ifnames; do
165			case "$ifname" in
166			'('*)	IFS="()"
167				echo $ifname
168				IFS="$ORIGIFS"
169				return
170				;;
171			*)	;;
172			esac
173		done
174	done
175	IFS="$ORIGIFS"
176}
177
178#
179# get_groupifname groupname
180#
181# Return the IPMP meta-interface name for the group, if it exists.
182#
183get_groupifname()
184{
185	/sbin/ipmpstat -gP -o groupname,group | while IFS=: read name ifname; do
186		if [ "$name" = "$1" ]; then
187			echo "$ifname"
188			return
189		fi
190	done
191}
192
193#
194# create_ipmp ifname groupname type
195#
196# Helper function for create_groupifname() that returns zero if it's able
197# to create an IPMP interface of the specified type and place it in the
198# specified group, or non-zero otherwise.
199#
200create_ipmp()
201{
202	/sbin/ifconfig $1 >/dev/null 2>&1 && return 1
203	/sbin/ifconfig $1 inet6 >/dev/null 2>&1 && return 1
204	/sbin/ifconfig $1 $3 ipmp group $2 2>/dev/null
205}
206
207#
208# create_groupifname groupname type
209#
210# Create an IPMP meta-interface name for the group.  We only use this
211# function if all of the interfaces in the group failed at boot and there
212# were no /etc/hostname[6].<if> files for the IPMP meta-interface.
213#
214create_groupifname()
215{
216	#
217	# This is a horrible way to count from 0 to 999, but in sh and
218	# without necessarily having /usr mounted, what else can we do?
219	#
220	for a in "" 1 2 3 4 5 6 7 8 9; do
221		for b in 0 1 2 3 4 5 6 7 8 9; do
222			for c in 0 1 2 3 4 5 6 7 8 9; do
223				# strip leading zeroes
224				[ "$a" = "" ] && [ "$b" = 0 ] && b=""
225				if create_ipmp ipmp$a$b$c $1 $2; then
226					echo ipmp$a$b$c
227					return
228				fi
229			done
230		done
231	done
232}
233
234#
235# get_hostname_ipmpinfo interface type
236#
237# Return all requested IPMP keywords from hostname file for a given interface.
238#
239# Example:
240#	get_hostname_ipmpinfo hme0 inet keyword [ keyword ... ]
241#
242get_hostname_ipmpinfo()
243{
244	case "$2" in
245		inet)	file=/etc/hostname.$1
246			;;
247		inet6)	file=/etc/hostname6.$1
248			;;
249		*)
250			return
251			;;
252	esac
253
254	[ -r "$file" ] || return
255
256	type=$2
257	shift 2
258
259	#
260	# Read through the hostname file looking for the specified
261	# keywords.  Since there may be several keywords that cancel
262	# each other out, the caller must post-process as appropriate.
263	#
264	while read line; do
265		[ -z "$line" ] && continue
266		/sbin/ifparse -s "$type" $line
267	done < "$file" | while read one two; do
268		for keyword in "$@"; do
269			[ "$one" = "$keyword" ] && echo "$one $two"
270		done
271	done
272}
273
274#
275# get_group_for_type interface type list
276#
277# Look through the set of hostname files associated with the same physical
278# interface as "interface", and determine which group they would configure.
279# Only hostname files associated with the physical interface or logical
280# interface zero are allowed to set the group.
281#
282get_group_for_type()
283{
284	physical=`get_physical $1`
285	type=$2
286	group=""
287
288	#
289	# The last setting of the group is the one that counts, which is
290	# the reason for the second while loop.
291	#
292	shift 2
293	for ifname in "$@"; do
294		if if_comp "$physical" $ifname; then
295			get_hostname_ipmpinfo $ifname $type group
296		fi
297	done | while :; do
298		read keyword grname || {
299			echo "$group"
300			break
301		}
302		group="$grname"
303	done
304}
305
306#
307# get_standby_for_type interface type list
308#
309# Look through the set of hostname files associated with the same physical
310# interface as "interface", and print the standby value ("standby",
311# "-standby", or nothing).  Only hostname files associated with the
312# physical interface or logical interface zero can set this flag.
313#
314get_standby_for_type()
315{
316	physical=`get_physical $1`
317	type=$2
318
319	#
320	# The last setting of "standby" or "-standby" is the one that
321	# counts, which is the reason for the second while loop.
322	#
323	shift 2
324	for ifname in "$@"; do
325		if if_comp "$physical" $ifname; then
326			get_hostname_ipmpinfo $ifname $type standby -standby
327		fi
328	done | while :; do
329		read keyword || {
330		    	echo "$iftype"
331			break
332		}
333		iftype="$keyword"
334	done
335}
336
337#
338# get_group interface
339#
340# If there is both an inet and inet6 version of an interface, the group
341# could be set in either set of hostname files.  Since inet6 is configured
342# after inet, if there's a setting in both files, inet6 wins.
343#
344get_group()
345{
346	group=`get_group_for_type $1 inet6 $inet6_list`
347	[ -z "$group" ] && group=`get_group_for_type $1 inet $inet_list`
348	echo $group
349}
350
351#
352# is_standby interface
353#
354# If there is both an inet and inet6 version of an interface, the
355# "standby" or "-standby" flag could be set in either set of hostname
356# files.  Since inet6 is configured after inet, if there's a setting in
357# both files, inet6 wins.
358#
359is_standby()
360{
361	standby=`get_standby_for_type $1 inet6 $inet6_list`
362	[ -z "$standby" ] && standby=`get_standby_for_type $1 inet $inet_list`
363	[ "$standby" = "standby" ]
364}
365
366#
367# doDHCPhostname interface
368# Pass to this function the name of an interface.  It will return
369# true if one should enable the use of DHCP client-side host name
370# requests on the interface, and false otherwise.
371#
372doDHCPhostname()
373{
374	if [ -f /etc/dhcp.$1 ] && [ -f /etc/hostname.$1 ]; then
375                set -- `shcat /etc/hostname.$1`
376                [ $# -eq 2 -a "$1" = "inet" ]
377                return $?
378        fi
379        return 1
380}
381
382#
383# inet_process_hostname processor [ args ]
384#
385# Process an inet hostname file.  The contents of the file
386# are taken from standard input. Each line is passed
387# on the command line to the "processor" command.
388# Command line arguments can be passed to the processor.
389#
390# Examples:
391#	inet_process_hostname /sbin/ifconfig hme0 < /etc/hostname.hme0
392#
393#	inet_process_hostname /sbin/ifparse -f < /etc/hostname.hme0
394#
395# If there is only line in an hostname file we assume it contains
396# the old style address which results in the interface being brought up
397# and the netmask and broadcast address being set ($inet_oneline_epilogue).
398#
399# If there are multiple lines we assume the file contains a list of
400# commands to the processor with neither the implied bringing up of the
401# interface nor the setting of the default netmask and broadcast address.
402#
403# Return non-zero if any command fails so that the caller may alert
404# users to errors in the configuration.
405#
406inet_oneline_epilogue="netmask + broadcast + up"
407
408inet_process_hostname()
409{
410	if doDHCPhostname $2; then
411		:
412	else
413		#
414		# Redirecting input from a file results in a sub-shell being
415		# used, hence this outer loop surrounding the "multiple_lines"
416		# and "ifcmds" variables.
417		#
418		while :; do
419			multiple_lines=false
420			ifcmds=""
421			retval=0
422
423			while read one rest; do
424				if [ -n "$ifcmds" ]; then
425					#
426					# This handles the first N-1
427					# lines of a N-line hostname file.
428					#
429					$* $ifcmds || retval=$?
430					multiple_lines=true
431				fi
432
433				#
434				# Strip out the "ipmp" keyword if it's the
435				# first token, since it's used to control
436				# interface creation, not configuration.
437				#
438				[ "$one" = ipmp ] && one=
439				ifcmds="$one $rest"
440			done
441
442			#
443			# If the hostname file is empty or consists of only
444			# blank lines, break out of the outer loop without
445			# configuring the newly plumbed interface.
446			#
447			[ -z "$ifcmds" ] && return $retval
448			if [ $multiple_lines = false ]; then
449				# The traditional one-line hostname file.
450				ifcmds="$ifcmds $inet_oneline_epilogue"
451			fi
452
453			#
454			# This handles either the single-line case or
455			# the last line of the N-line case.
456			#
457			$* $ifcmds || return $?
458			return $retval
459		done
460	fi
461}
462
463#
464# inet6_process_hostname processor [ args ]
465#
466# Process an inet6 hostname file.  The contents of the file
467# are taken from standard input. Each line is passed
468# on the command line to the "processor" command.
469# Command line arguments can be passed to the processor.
470#
471# Examples:
472#	inet6_process_hostname /sbin/ifconfig hme0 inet6 < /etc/hostname6.hme0
473#
474#	inet6_process_hostname /sbin/ifparse -f inet6 < /etc/hostname6.hme0
475#
476# Return non-zero if any of the commands fail so that the caller may alert
477# users to errors in the configuration.
478#
479inet6_process_hostname()
480{
481    	retval=0
482	while read one rest; do
483		#
484	    	# See comment in inet_process_hostname for details.
485	        #
486		[ "$one" = ipmp ] && one=
487		ifcmds="$one $rest"
488
489		if [ -n "$ifcmds" ]; then
490			$* $ifcmds || retval=$?
491		fi
492	done
493	return $retval
494}
495
496#
497# Process interfaces that failed to plumb.  Find the IPMP meta-interface
498# that should host the addresses.  For IPv6, only static addresses defined
499# in hostname6 files are moved, autoconfigured addresses are not moved.
500#
501# Example:
502#	move_addresses inet6
503#
504move_addresses()
505{
506	type="$1"
507	eval "failed=\"\$${type}_failed\""
508	eval "list=\"\$${type}_list\""
509	process_func="${type}_process_hostname"
510	processed=""
511
512	if [ "$type" = inet ]; then
513	        typedesc="IPv4"
514		zaddr="0.0.0.0"
515		hostpfx="/etc/hostname"
516	else
517	        typedesc="IPv6"
518		zaddr="::"
519		hostpfx="/etc/hostname6"
520	fi
521
522	echo "Moving addresses from missing ${typedesc} interface(s):\c" \
523	    >/dev/msglog
524
525	for ifname in $failed; do
526		in_list if_comp $ifname $processed && continue
527
528		group=`get_group $ifname`
529		if [ -z "$group" ]; then
530			in_list physical_comp $ifname $processed || {
531				echo " $ifname (not moved -- not" \
532				    "in an IPMP group)\c" >/dev/msglog
533				processed="$processed $ifname"
534			}
535			continue
536		fi
537
538		#
539		# Lookup the IPMP meta-interface name.  If one doesn't exist,
540		# create it.
541		#
542		grifname=`get_groupifname $group`
543		[ -z "$grifname" ] && grifname=`create_groupifname $group $type`
544
545		#
546		# The hostname files are processed twice.  In the first
547		# pass, we are looking for all commands that apply
548		# to the non-additional interface address.  These may be
549		# scattered over several files.  We won't know
550		# whether the address represents a failover address
551		# or not until we've read all the files associated with the
552		# interface.
553		#
554		# In the first pass through the hostname files, all
555		# additional logical interface commands are removed.
556		# The remaining commands are concatenated together and
557		# passed to ifparse to determine whether the
558		# non-additional logical interface address is a failover
559		# address.  If it as a failover address, the
560		# address may not be the first item on the line,
561		# so we can't just substitute "addif" for "set".
562		# We prepend an "addif $zaddr" command, and let
563		# the embedded "set" command set the address later.
564		#
565		/sbin/ifparse -f $type `
566			for item in $list; do
567				if_comp $ifname $item && $process_func \
568				    /sbin/ifparse $type < $hostpfx.$item
569			done | while read three four; do
570				[ "$three" != addif ] && echo "$three $four \c"
571			done` | while read one two; do
572				[ -z "$one" ] && continue
573				[ "$one $two" = "$inet_oneline_epilogue" ] && \
574				    continue
575				line="addif $zaddr $one $two"
576				/sbin/ifconfig $grifname $type $line >/dev/null
577			done
578
579		#
580		# In the second pass, look for the the "addif" commands
581		# that configure additional failover addresses.  Addif
582		# commands are not valid in logical interface hostname
583		# files.
584		#
585		if [ "$ifname" = "`get_physical $ifname`" ]; then
586			$process_func /sbin/ifparse -f $type < $hostpfx.$ifname \
587			| while read one two; do
588				[ "$one" = addif ] && \
589					/sbin/ifconfig $grifname $type \
590				    	    addif $two >/dev/null
591			done
592		fi
593
594		#
595		# Check if this was an active interface in the group.  If so,
596		# activate another IP interface (if possible)
597		#
598		is_standby $ifname || inactive=`get_inactive_ifname $group`
599		[ -n "$inactive" ] && /sbin/ifconfig $inactive $type -standby
600
601		in_list physical_comp $ifname $processed || {
602			processed="$processed $ifname"
603			echo " $ifname (moved to $grifname\c"	   > /dev/msglog
604			if [ -n "$inactive" ]; then
605				echo " and cleared 'standby' on\c" > /dev/msglog
606				echo " $inactive to compensate\c"  > /dev/msglog
607			fi
608			echo ")\c"				   > /dev/msglog
609		}
610		inactive=""
611	done
612	echo "." >/dev/msglog
613}
614
615#
616# if_configure type class interface_list
617#
618# Configure all of the interfaces of type `type' (e.g., "inet6") in
619# `interface_list' according to their /etc/hostname[6].* files.  `class'
620# describes the class of interface (e.g., "IPMP"), as a diagnostic aid.
621# For inet6 interfaces, the interface is also brought up.
622#
623if_configure()
624{
625	fail=
626	type=$1
627	class=$2
628	process_func=${type}_process_hostname
629	shift 2
630
631	if [ "$type" = inet ]; then
632	        desc="IPv4"
633		hostpfx="/etc/hostname"
634	else
635	        desc="IPv6"
636		hostpfx="/etc/hostname6"
637	fi
638	[ -n "$class" ] && desc="$class $desc"
639
640	echo "configuring $desc interfaces:\c"
641	while [ $# -gt 0 ]; do
642		$process_func /sbin/ifconfig $1 $type < $hostpfx.$1 >/dev/null
643		if [ $? != 0 ]; then
644			fail="$fail $1"
645		elif [ "$type" = inet6 ]; then
646		    	/sbin/ifconfig $1 inet6 up || fail="$fail $1"
647		fi
648		echo " $1\c"
649		shift
650	done
651	echo "."
652
653	[ -n "$fail" ] && warn_failed_ifs "configure $desc" "$fail"
654}
655
656#
657# net_reconfigure is called from the network/physical service (by the
658# net-physical and net-nwam method scripts) to perform tasks that only
659# need to be done during a reconfigure boot.  This needs to be
660# isolated in a function since network/physical has two instances
661# (default and nwam) that have distinct method scripts that each need
662# to do these things.
663#
664net_reconfigure ()
665{
666	#
667	# Is this a reconfigure boot?  If not, then there's nothing
668	# for us to do.
669	#
670	reconfig=`svcprop -c -p system/reconfigure \
671	    system/svc/restarter:default 2>/dev/null`
672	if [ $? -ne 0 -o "$reconfig" = false ]; then
673		return 0
674	fi
675
676	#
677	# Ensure that the datalink-management service is running since
678	# manifest-import has not yet run for a first boot after
679	# upgrade.  We wouldn't need to do that if manifest-import ran
680	# earlier in boot, since there is an explicit dependency
681	# between datalink-management and network/physical.
682	#
683	svcadm enable -ts network/datalink-management:default
684
685	#
686	# There is a bug in SMF which causes the svcadm command above
687	# to exit prematurely (with an error code of 3) before having
688	# waited for the service to come online after having enabled
689	# it.  Until that bug is fixed, we need to have the following
690	# loop to explicitly wait for the service to come online.
691	#
692	i=0
693	while [ $i -lt 30 ]; do
694		i=`expr $i + 1`
695		sleep 1
696		state=`svcprop -p restarter/state \
697		    network/datalink-management:default 2>/dev/null`
698		if [ $? -ne 0 ]; then
699			continue
700		elif [ "$state" = "online" ]; then
701			break
702		fi
703	done
704	if [ "$state" != "online" ]; then
705		echo "The network/datalink-management service \c"
706		echo "did not come online."
707		return 1
708	fi
709
710	#
711	# Initialize the set of physical links, and validate and
712	# remove all the physical links which were removed during the
713	# system shutdown.
714	#
715	/sbin/dladm init-phys
716	return 0
717}
718