xref: /illumos-gate/usr/src/cmd/svc/shell/net_include.sh (revision 5bbb4db2c3f208d12bf0fd11769728f9e5ba66a2)
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# doDHCPhostname interface
287# Pass to this function the name of an interface.  It will return
288# true if one should enable the use of DHCP client-side host name
289# requests on the interface, and false otherwise.
290#
291doDHCPhostname()
292{
293	if [ -f /etc/dhcp.$1 ] && [ -f /etc/hostname.$1 ]; then
294                set -- `shcat /etc/hostname.$1`
295                [ $# -eq 2 -a "$1" = "inet" ]
296                return $?
297        fi
298        return 1
299}
300
301#
302# inet_process_hostname processor [ args ]
303#
304# Process an inet hostname file.  The contents of the file
305# are taken from standard input. Each line is passed
306# on the command line to the "processor" command.
307# Command line arguments can be passed to the processor.
308#
309# Examples:
310#	inet_process_hostname /sbin/ifconfig hme0 < /etc/hostname.hme0
311#
312#	inet_process_hostname /sbin/ifparse -f < /etc/hostname.hme0
313#
314# If there is only line in an hostname file we assume it contains
315# the old style address which results in the interface being brought up
316# and the netmask and broadcast address being set ($inet_oneline_epilogue).
317#
318# If there are multiple lines we assume the file contains a list of
319# commands to the processor with neither the implied bringing up of the
320# interface nor the setting of the default netmask and broadcast address.
321#
322# Return non-zero if any command fails so that the caller may alert
323# users to errors in the configuration.
324#
325inet_oneline_epilogue="netmask + broadcast + up"
326
327inet_process_hostname()
328{
329	if doDHCPhostname $2; then
330		:
331	else
332		#
333		# Redirecting input from a file results in a sub-shell being
334		# used, hence this outer loop surrounding the "multiple_lines"
335		# and "ifcmds" variables.
336		#
337		while :; do
338			multiple_lines=false
339			ifcmds=""
340			retval=0
341
342			while read one rest; do
343				if [ -n "$ifcmds" ]; then
344					#
345					# This handles the first N-1
346					# lines of a N-line hostname file.
347					#
348					$* $ifcmds || retval=$?
349					multiple_lines=true
350				fi
351
352				#
353				# Strip out the "ipmp" keyword if it's the
354				# first token, since it's used to control
355				# interface creation, not configuration.
356				#
357				[ "$one" = ipmp ] && one=
358				ifcmds="$one $rest"
359			done
360
361			#
362			# If the hostname file is empty or consists of only
363			# blank lines, break out of the outer loop without
364			# configuring the newly plumbed interface.
365			#
366			[ -z "$ifcmds" ] && return $retval
367			if [ $multiple_lines = false ]; then
368				# The traditional one-line hostname file.
369				ifcmds="$ifcmds $inet_oneline_epilogue"
370			fi
371
372			#
373			# This handles either the single-line case or
374			# the last line of the N-line case.
375			#
376			$* $ifcmds || return $?
377			return $retval
378		done
379	fi
380}
381
382#
383# inet6_process_hostname processor [ args ]
384#
385# Process an inet6 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#	inet6_process_hostname /sbin/ifconfig hme0 inet6 < /etc/hostname6.hme0
392#
393#	inet6_process_hostname /sbin/ifparse -f inet6 < /etc/hostname6.hme0
394#
395# Return non-zero if any of the commands fail so that the caller may alert
396# users to errors in the configuration.
397#
398inet6_process_hostname()
399{
400    	retval=0
401	while read one rest; do
402		#
403	    	# See comment in inet_process_hostname for details.
404	        #
405		[ "$one" = ipmp ] && one=
406		ifcmds="$one $rest"
407
408		if [ -n "$ifcmds" ]; then
409			$* $ifcmds || retval=$?
410		fi
411	done
412	return $retval
413}
414
415#
416# Process interfaces that failed to plumb.  Find the IPMP meta-interface
417# that should host the addresses.  For IPv6, only static addresses defined
418# in hostname6 files are moved, autoconfigured addresses are not moved.
419#
420# Example:
421#	move_addresses inet6
422#
423move_addresses()
424{
425	type="$1"
426	eval "failed=\"\$${type}_failed\""
427	eval "list=\"\$${type}_list\""
428	process_func="${type}_process_hostname"
429	processed=""
430
431	if [ "$type" = inet ]; then
432	        typedesc="IPv4"
433		zaddr="0.0.0.0"
434		hostpfx="/etc/hostname"
435	else
436	        typedesc="IPv6"
437		zaddr="::"
438		hostpfx="/etc/hostname6"
439	fi
440
441	echo "Moving addresses from missing ${typedesc} interface(s):\c" \
442	    >/dev/msglog
443
444	for ifname in $failed; do
445		in_list if_comp $ifname $processed && continue
446
447		group=`get_group $ifname`
448		if [ -z "$group" ]; then
449			in_list physical_comp $ifname $processed || {
450				echo " $ifname (not moved -- not" \
451				    "in an IPMP group)\c" >/dev/msglog
452				processed="$processed $ifname"
453			}
454			continue
455		fi
456
457		#
458		# Lookup the IPMP meta-interface name.  If one doesn't exist,
459		# create it.
460		#
461		grifname=`get_groupifname $group`
462		[ -z "$grifname" ] && grifname=`create_groupifname $group $type`
463
464		#
465		# The hostname files are processed twice.  In the first
466		# pass, we are looking for all commands that apply to the
467		# non-additional interface address.  These may be
468		# scattered over several files.  We won't know whether the
469		# address represents a failover address or not until we've
470		# read all the files associated with the interface.
471		#
472		# In the first pass through the hostname files, all
473		# additional logical interface commands are removed.  The
474		# remaining commands are concatenated together and passed
475		# to ifparse to determine whether the non-additional
476		# logical interface address is a failover address.  If it
477		# as a failover address, the address may not be the first
478		# item on the line, so we can't just substitute "addif"
479		# for "set".  We prepend an "addif $zaddr" command, and
480		# let the embedded "set" command set the address later.
481		#
482		/sbin/ifparse -f $type `
483			for item in $list; do
484				if_comp $ifname $item && $process_func \
485				    /sbin/ifparse $type < $hostpfx.$item
486			done | while read three four; do
487				[ "$three" != addif ] && echo "$three $four \c"
488			done` | while read one two; do
489				[ -z "$one" ] && continue
490				[ "$one $two" = "$inet_oneline_epilogue" ] && \
491				    continue
492				line="addif $zaddr $one $two"
493				/sbin/ifconfig $grifname $type $line >/dev/null
494			done
495
496		#
497		# In the second pass, look for the the "addif" commands
498		# that configure additional failover addresses.  Addif
499		# commands are not valid in logical interface hostname
500		# files.
501		#
502		if [ "$ifname" = "`get_physical $ifname`" ]; then
503			$process_func /sbin/ifparse -f $type < $hostpfx.$ifname \
504			| while read one two; do
505				[ "$one" = addif ] && \
506					/sbin/ifconfig $grifname $type \
507				    	    addif $two >/dev/null
508			done
509		fi
510
511		in_list physical_comp $ifname $processed || {
512			processed="$processed $ifname"
513			echo " $ifname (moved to $grifname)\c" > /dev/msglog
514		}
515	done
516	echo "." >/dev/msglog
517}
518
519#
520# if_configure type class interface_list
521#
522# Configure all of the interfaces of type `type' (e.g., "inet6") in
523# `interface_list' according to their /etc/hostname[6].* files.  `class'
524# describes the class of interface (e.g., "IPMP"), as a diagnostic aid.
525# For inet6 interfaces, the interface is also brought up.
526#
527if_configure()
528{
529	fail=
530	type=$1
531	class=$2
532	process_func=${type}_process_hostname
533	shift 2
534
535	if [ "$type" = inet ]; then
536	        desc="IPv4"
537		hostpfx="/etc/hostname"
538	else
539	        desc="IPv6"
540		hostpfx="/etc/hostname6"
541	fi
542	[ -n "$class" ] && desc="$class $desc"
543
544	echo "configuring $desc interfaces:\c"
545	while [ $# -gt 0 ]; do
546		$process_func /sbin/ifconfig $1 $type < $hostpfx.$1 >/dev/null
547		if [ $? != 0 ]; then
548			fail="$fail $1"
549		elif [ "$type" = inet6 ]; then
550		    	/sbin/ifconfig $1 inet6 up || fail="$fail $1"
551		fi
552		echo " $1\c"
553		shift
554	done
555	echo "."
556
557	[ -n "$fail" ] && warn_failed_ifs "configure $desc" "$fail"
558}
559
560#
561# net_reconfigure is called from the network/physical service (by the
562# net-physical and net-nwam method scripts) to perform tasks that only
563# need to be done during a reconfigure boot.  This needs to be
564# isolated in a function since network/physical has two instances
565# (default and nwam) that have distinct method scripts that each need
566# to do these things.
567#
568net_reconfigure ()
569{
570	#
571	# Is this a reconfigure boot?  If not, then there's nothing
572	# for us to do.
573	#
574	reconfig=`svcprop -c -p system/reconfigure \
575	    system/svc/restarter:default 2>/dev/null`
576	if [ $? -ne 0 -o "$reconfig" = false ]; then
577		return 0
578	fi
579
580	#
581	# Ensure that the datalink-management service is running since
582	# manifest-import has not yet run for a first boot after
583	# upgrade.  We wouldn't need to do that if manifest-import ran
584	# earlier in boot, since there is an explicit dependency
585	# between datalink-management and network/physical.
586	#
587	svcadm enable -ts network/datalink-management:default
588
589	#
590	# There is a bug in SMF which causes the svcadm command above
591	# to exit prematurely (with an error code of 3) before having
592	# waited for the service to come online after having enabled
593	# it.  Until that bug is fixed, we need to have the following
594	# loop to explicitly wait for the service to come online.
595	#
596	i=0
597	while [ $i -lt 30 ]; do
598		i=`expr $i + 1`
599		sleep 1
600		state=`svcprop -p restarter/state \
601		    network/datalink-management:default 2>/dev/null`
602		if [ $? -ne 0 ]; then
603			continue
604		elif [ "$state" = "online" ]; then
605			break
606		fi
607	done
608	if [ "$state" != "online" ]; then
609		echo "The network/datalink-management service \c"
610		echo "did not come online."
611		return 1
612	fi
613
614	#
615	# Initialize the set of physical links, and validate and
616	# remove all the physical links which were removed during the
617	# system shutdown.
618	#
619	/sbin/dladm init-phys
620	return 0
621}
622
623#
624# Check for use of the default "Port VLAN Identifier" (PVID) -- VLAN 1.
625# If there is one for a given interface, then warn the user and force the
626# PVID to zero (if it's not already set).  We do this by generating a list
627# of interfaces with VLAN 1 in use first, and then parsing out the
628# corresponding base datalink entries to check for ones without a
629# "default_tag" property.
630#
631update_pvid()
632{
633	datalink=/etc/dladm/datalink.conf
634
635	(
636		# Find datalinks using VLAN 1 explicitly
637		# configured by dladm
638		/usr/bin/nawk '
639			/^#/ || NF < 2 { next }
640			{ linkdata[$1]=$2; }
641			/;vid=int,1;/ {
642				sub(/.*;linkover=int,/, "", $2);
643				sub(/;.*/, "", $2);
644				link=linkdata[$2];
645				sub(/name=string,/, "", link);
646				sub(/;.*/, "", link);
647				print link;
648			}' $datalink
649	) | ( /usr/bin/sort -u; echo END; cat $datalink ) | /usr/bin/nawk '
650	    /^END$/ { state=1; }
651	    state == 0 { usingpvid[++nusingpvid]=$1; next; }
652	    /^#/ || NF < 2 { next; }
653	    {
654		# If it is already present and has a tag set,
655		# then believe it.
656		if (!match($2, /;default_tag=/))
657			next;
658		sub(/name=string,/, "", $2);
659		sub(/;.*/, "", $2);
660		for (i = 1; i <= nusingpvid; i++) {
661			if (usingpvid[i] == $2)
662				usingpvid[i]="";
663		}
664	    }
665	    END {
666		for (i = 1; i <= nusingpvid; i++) {
667			if (usingpvid[i] != "") {
668				printf("Warning: default VLAN tag set to 0" \
669				    " on %s\n", usingpvid[i]);
670				cmd=sprintf("dladm set-linkprop -p " \
671				    "default_tag=0 %s\n", usingpvid[i]);
672				system(cmd);
673			}
674		}
675	    }'
676}
677