1#!/bin/sh -
2#
3#	$OpenBSD: netstart,v 1.234 2022/12/18 15:52:52 kn Exp $
4
5# Turn off Strict Bourne shell mode.
6set +o sh
7
8# Show usage of the netstart script and exit.
9usage() {
10	print -u2 "usage: sh $0 [-n] [interface ...]"
11	exit 1
12}
13
14# Test the first argument against the remaining ones, return success on a match.
15isin() {
16	local _a=$1 _b
17
18	shift
19	for _b; do
20		[[ $_a == "$_b" ]] && return 0
21	done
22	return 1
23}
24
25# Echo file $1 to stdout. Skip comment lines. Strip leading and trailing
26# whitespace if IFS is set.
27# Usage: stripcom /path/to/file
28stripcom() {
29	local _file=$1 _line
30
31	[[ -f $_file ]] || return
32
33	while read _line; do
34		[[ -n ${_line%%#*} ]] && print -r -- "$_line"
35	done <$_file
36}
37
38# Parse and "unpack" a hostname.if(5) line given as positional parameters.
39# Fill the _cmds array with the resulting interface configuration commands.
40parse_hn_line() {
41	local _af=0 _name=1 _mask=2 _bc=3 _prefix=2 _c _cmd _prev _daddr _dhcp _i
42	set -A _c -- "$@"
43	set -o noglob
44
45	case ${_c[_af]} in
46	''|*([[:blank:]])'#'*)
47		return
48		;;
49	inet)	((${#_c[*]} > 1)) || return
50		if [[ ${_c[_name]} == autoconf ]]; then
51			_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
52			V4_AUTOCONF=true
53			return
54		fi
55		[[ ${_c[_name]} == alias ]] && _mask=3 _bc=4
56		[[ -n ${_c[_mask]} ]] && _c[_mask]="netmask ${_c[_mask]}"
57		if [[ -n ${_c[_bc]} ]]; then
58			_c[_bc]="broadcast ${_c[_bc]}"
59			[[ ${_c[_bc]} == *NONE ]] && _c[_bc]=
60		fi
61		_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
62		;;
63	inet6)	((${#_c[*]} > 1)) || return
64		if [[ ${_c[_name]} == autoconf ]]; then
65			_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
66			V6_AUTOCONF=true
67			return
68		fi
69		[[ ${_c[_name]} == alias ]] && _prefix=3
70		[[ -n ${_c[_prefix]} ]] && _c[_prefix]="prefixlen ${_c[_prefix]}"
71		_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
72		;;
73	dest)	((${#_c[*]} == 2)) && _daddr=${_c[1]} || return
74		_prev=$((${#_cmds[*]} - 1))
75		((_prev >= 0)) || return
76		set -A _c -- ${_cmds[_prev]}
77		_name=3
78		[[ ${_c[_name]} == alias ]] && _name=4
79		_c[_name]="${_c[_name]} $_daddr"
80		_cmds[$_prev]="${_c[@]}"
81		;;
82	dhcp)	_cmds[${#_cmds[*]}]="ifconfig $_if inet autoconf"
83		V4_AUTOCONF=true
84		;;
85	'!'*)	_cmd=$(print -- "${_c[@]}" | sed 's/\$if/'$_if'/g')
86		_cmds[${#_cmds[*]}]="${_cmd#!}"
87		;;
88	*)	_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
89		;;
90	esac
91	unset _c
92	set +o noglob
93}
94
95# Create interface $1 if it does not yet exist.
96# Usage: ifcreate if1
97ifcreate() {
98	local _if=$1
99
100	if $PRINT_ONLY; then
101		print -r -- "{ ifconfig $_if || ifconfig $_if create; }"
102	else
103		{ ifconfig $_if || ifconfig $_if create; } >/dev/null 2>&1
104	fi
105}
106
107# Create interfaces for network pseudo-devices referred to by hostname.if files.
108# Optionally, limit creation to given interfaces only.
109# Usage: vifscreate [if ...]
110vifscreate() {
111	local _vif _hn _if
112
113	for _vif in $(ifconfig -C); do
114		for _hn in /etc/hostname.${_vif}+([[:digit:]]); do
115			[[ -f $_hn ]] || continue
116			_if=${_hn#/etc/hostname.}
117
118			# loopback for routing domain is created by kernel
119			[[ -n ${_if##lo[1-9]*} ]] || continue
120
121			if (($# > 0)) && ! isin $_if "$@"; then
122				continue
123			fi
124
125			if ! ifcreate $_if; then
126				print -u2 "${0##*/}: create for '$_if' failed."
127			fi
128		done
129	done
130}
131
132# Start a single interface.
133# Usage: ifstart if1
134ifstart() {
135	local _if=$1 _lladdr _hn=/etc/hostname.$1 _cmds _i=0 _line _stat
136	set -A _cmds
137
138	# Interface names must be alphanumeric only.  We check to avoid
139	# configuring backup or temp files, and to catch the "*" case.
140	if [[ $_if == +([[:alpha:]])+([[:digit:]]) ]]; then
141		_lladdr=$(ifconfig $_if 2>/dev/null |
142		    sed -n 's/^[[:space:]]*lladdr[[:space:]]//p')
143		if [[ -n $_lladdr && -f /etc/hostname.$_lladdr &&
144		    -n $(ifconfig -M "$_lladdr") ]]; then
145			print -u2 "${0##*/}: $_hn: /etc/hostname.$_lladdr overrides"
146			return
147		fi
148
149	# We also support hostname.lladdr, but it must resolve to be valid
150	elif [[ $_if == ??:??:??:??:??:?? ]]; then
151		_lladdr=$_if
152		_if=$(ifconfig -M $_lladdr)
153		if (($? != 0)); then
154			print -u2 "${0##*/}: $_lladdr is not unique."
155			return
156		fi
157
158		[[ -z $_if ]] && return
159	else
160		return
161	fi
162
163	if [[ ! -f $_hn ]]; then
164		print -u2 "${0##*/}: $_hn: No such file or directory."
165		return
166	fi
167
168	# Not using stat(1), we can't rely on having /usr yet.
169	set -A _stat -- $(ls -nL $_hn)
170	if [[ "${_stat[0]}${_stat[2]}${_stat[3]}" != *---00 ]]; then
171		print -u2 "WARNING: $_hn is insecure, fixing permissions."
172		chmod -LR o-rwx $_hn
173		chown -LR root:wheel $_hn
174	fi
175
176	# Check for ifconfig'able interface, except if -n option is specified.
177	ifcreate $_if || return
178
179	# Parse the hostname.if(5) file and fill _cmds array with interface
180	# configuration commands.
181	set -o noglob
182	while IFS= read -- _line; do
183		parse_hn_line $_line
184	done <$_hn
185
186	# Apply the interface configuration commands stored in _cmds array.
187	while ((_i < ${#_cmds[*]})); do
188		if $PRINT_ONLY; then
189			print -r -- "${_cmds[_i]}"
190		else
191			eval "${_cmds[_i]}"
192		fi
193		((_i++))
194	done
195	unset _cmds
196	set +o noglob
197}
198
199# Start multiple interfaces by driver name.
200# Usage: ifmstart "em iwm" "trunk vlan"
201#   Start "$1" interfaces in order or all interfaces if empty.
202#   Don't start "$2" interfaces. "$2" is optional.
203ifmstart() {
204	local _sifs=$1 _xifs=$2 _hn _if _sif _xif
205
206	for _sif in ${_sifs:-ALL}; do
207		for _hn in /etc/hostname.@(+([[:alpha:]])+([[:digit:]])|??:??:??:??:??:??); do
208			[[ -f $_hn ]] || continue
209			_if=${_hn#/etc/hostname.}
210
211			if [[ $_if == +([[:alpha:]])+([[:digit:]]) ]]; then
212				# Skip unwanted ifs.
213				for _xif in $_xifs; do
214					[[ $_xif == ${_if%%[0-9]*} ]] && continue 2
215				done
216			fi
217
218			# Start wanted ifs.
219			[[ $_sif == @(ALL|${_if%%[0-9]*}) ]] && ifstart $_if
220		done
221	done
222}
223
224# Parse /etc/mygate and add default routes for IPv4 and IPv6.
225# Usage: defaultroute
226defaultroute() {
227	local _cmd _v4set=false _v6set=false;
228	set -o noglob
229
230	stripcom /etc/mygate |
231	while read gw; do
232		case $gw in
233		'!'*)
234			_cmd=$(print -- "$gw")
235			_cmd="${_cmd#!}"
236			;;
237		!(*:*))
238			($_v4set || $V4_AUTOCONF) && continue
239			_cmd="route -qn add -host default $gw"
240			_v4set=true
241			;;
242		*)
243			($_v6set || $V6_AUTOCONF) && continue
244			_cmd="route -qn add -host -inet6 default $gw"
245			_v6set=true
246			;;
247		esac
248		if $PRINT_ONLY; then
249			print -r -- "$_cmd"
250		else
251			$_cmd
252		fi
253	done
254	set +o noglob
255}
256
257# add all the routes needed for IPv6
258ip6routes() {
259	local _i=0
260	set -A _cmds
261
262	# Disallow link-local unicast dest without outgoing scope identifiers.
263	_cmds[_i++]="route -qn add -inet6 fe80:: -prefixlen 10 ::1 -reject"
264
265	# Disallow site-local unicast dest without outgoing scope identifiers.
266	# If you configure site-locals without scope id (it is permissible
267	# config for routers that are not on scope boundary), you may want
268	# to comment the line out.
269	_cmds[_i++]="route -qn add -inet6 fec0:: -prefixlen 10 ::1 -reject"
270
271	# Disallow "internal" addresses to appear on the wire.
272	_cmds[_i++]="route -qn add -inet6 ::ffff:0.0.0.0 -prefixlen 96 ::1 -reject"
273
274	# Disallow packets to malicious 6to4 prefix.
275	_cmds[_i++]="route -qn add -inet6 2002:e000:: -prefixlen 20 ::1 -reject"
276	_cmds[_i++]="route -qn add -inet6 2002:7f00:: -prefixlen 24 ::1 -reject"
277	_cmds[_i++]="route -qn add -inet6 2002:0000:: -prefixlen 24 ::1 -reject"
278	_cmds[_i++]="route -qn add -inet6 2002:ff00:: -prefixlen 24 ::1 -reject"
279
280	# Disallow packets without scope identifier.
281	_cmds[_i++]="route -qn add -inet6 ff01:: -prefixlen 16 ::1 -reject"
282	_cmds[_i++]="route -qn add -inet6 ff02:: -prefixlen 16 ::1 -reject"
283
284	# Completely disallow packets to IPv4 compatible prefix.
285	#
286	# This may conflict with RFC1933 under following circumstances:
287	# (1) An IPv6-only KAME node tries to originate packets to IPv4
288	#     compatible destination.  The KAME node has no IPv4 compatible
289	#     support.  Under RFC1933, it should transmit native IPv6
290	#     packets toward IPv4 compatible destination, hoping it would
291	#     reach a router that forwards the packet toward auto-tunnel
292	#     interface.
293	# (2) An IPv6-only node originates a packet to an IPv4 compatible
294	#     destination.  A KAME node is acting as an IPv6 router, and
295	#     asked to forward it.
296	#
297	# Due to rare use of IPv4 compatible addresses, and security issues
298	# with it, we disable it by default.
299	_cmds[_i++]="route -qn add -inet6 ::0.0.0.0 -prefixlen 96 ::1 -reject"
300
301	# Apply the interface configuration commands stored in _cmds array.
302	_i=0
303	while ((_i < ${#_cmds[*]})); do
304		if $PRINT_ONLY; then
305			print -r -- "${_cmds[_i]}"
306		else
307			eval "${_cmds[_i]}"
308		fi
309		((_i++))
310	done
311	unset _cmds
312}
313
314# wait for autoconf interfaces
315wait_autoconf_default() {
316	local _count=0
317
318	if ifconfig | grep -q ': flags=.*<.*AUTOCONF.*>'; then
319		while ((_count++ < 20)); do
320			route -n show | grep -q ^default && break
321			sleep .5
322		done
323	fi
324}
325
326# Ensure IPv6 Duplicate Address Detection (DAD) is completed.
327wait_dad() {
328	local _count=0
329
330	while ((_count++ < 10 && $(sysctl -n net.inet6.ip6.dad_pending) != 0)); do
331		sleep 1
332	done
333}
334
335# Make sure the invoking user has the right privileges.  Check for presence of
336# id(1) to avoid problems with diskless setups.
337if [[ -x /usr/bin/id ]] && (($(id -u) != 0)); then
338	print -u2 "${0##*/}: need root privileges"
339	exit 1
340fi
341
342# Get network related vars from rc.conf using the parsing routine from rc.subr.
343FUNCS_ONLY=1 . /etc/rc.d/rc.subr
344_rc_parse_conf
345
346PRINT_ONLY=false
347V4_AUTOCONF=false
348V6_AUTOCONF=false
349IP6KERNEL=false
350
351while getopts ":n" opt; do
352	case $opt in
353	n)	PRINT_ONLY=true;;
354	*)	usage;;
355	esac
356done
357shift $((OPTIND-1))
358
359if ifconfig lo0 inet6 >/dev/null 2>&1; then
360	IP6KERNEL=true
361fi
362
363# Load key material for the generation of IPv6 Semantically Opaque Interface
364# Identifiers (SOII) used for SLAAC addresses.
365if $IP6KERNEL && ! $PRINT_ONLY; then
366	[[ -f /etc/soii.key ]] &&
367		sysctl -q "net.inet6.ip6.soiikey=$(</etc/soii.key)"
368fi
369
370# If we were invoked with a list of interface names, just reconfigure these
371# interfaces (or bridges), add default routes and return.
372# Create virtual interfaces upfront to make ifconfig commands depending on
373# other interfaces, e.g. "patch", work regardless of in which order interface
374# names were specified.
375if (($# > 0)); then
376	vifscreate "$@"
377	for _if; do ifstart $_if; done
378	defaultroute
379	return
380fi
381
382# Otherwise, process with the complete network initialization.
383
384# Set the address for the loopback interface.  Bringing the interface up,
385# automatically invokes the IPv6 address ::1.
386if $PRINT_ONLY; then
387	print -r -- "ifconfig lo0 inet 127.0.0.1/8"
388else
389	ifconfig lo0 inet 127.0.0.1/8
390fi
391
392if $IP6KERNEL && ! $PRINT_ONLY; then
393	ip6routes
394fi
395
396# Create all the pseudo interfaces up front.
397vifscreate
398
399# Configure all the non-loopback interfaces which we know about, but
400# do not start interfaces which must be delayed. Refer to hostname.if(5)
401ifmstart "" "aggr trunk svlan vlan carp pppoe tun tap gif etherip gre egre nvgre eoip vxlan pflow wg"
402
403# The aggr and trunk interfaces need to come up first in this list.
404# The (s)vlan interfaces need to come up after trunk.
405# Configure all the carp interfaces which we know about before default route.
406ifmstart "aggr trunk svlan vlan carp pppoe"
407
408# Set default routes for IPv4 and IPv6.
409defaultroute
410
411# Multicast routing.
412if [[ $multicast != YES ]]; then
413	if $PRINT_ONLY; then
414		print -r -- "route -qn delete 224.0.0.0/4"
415		print -r -- "route -qn add -net 224.0.0.0/4 -interface 127.0.0.1 -reject"
416	else
417		route -qn delete 224.0.0.0/4
418		route -qn add -net 224.0.0.0/4 -interface 127.0.0.1 -reject
419	fi
420fi
421
422# Reject 127/8 other than 127.0.0.1.
423if $PRINT_ONLY; then
424	print -r -- "route -qn add -net 127 127.0.0.1 -reject"
425else
426	route -qn add -net 127 127.0.0.1 -reject
427fi
428
429# If interface autoconf exists, pause a little for at least one default route
430$PRINT_ONLY || wait_autoconf_default
431
432# Configure interfaces that rely on routing
433ifmstart "tun tap gif etherip gre egre nvgre eoip vxlan pflow wg"
434
435if $IP6KERNEL && ! $PRINT_ONLY; then
436	wait_dad
437fi
438