#!/sbin/openrc-run # shellcheck shell=sh disable=SC1008 # Copyright (c) 2007-2009 Roy Marples # Copyright (c) 2010-2016 Gentoo Foundation # Released under the 2-clause BSD license. SHDIR="/lib/netifrc/sh" MODULESDIR="/lib/netifrc/net" _config_vars="config metric routes" [ -z "${IN_BACKGROUND}" ] && IN_BACKGROUND="NO" # shellcheck disable=SC2034 description="Configures network interfaces." # Handy var so we don't have to embed new lines everywhere for array splitting __IFS=" " # Set the INIT to be openrc if this file is called directly : "${INIT:=openrc}" if [ -f "$SHDIR/functions.sh" ]; then # shellcheck disable=SC1090 . "$SHDIR/functions.sh" else echo "$SHDIR/functions.sh missing. Exiting" exit 1 fi # Create per-interface nettree ordering, avoids race conditions and allows # per-interface custom modules. MODULESLIST="${RC_SVCDIR}/nettree$(get_interface)" depend() { local IFACE IFVAR IFACE=$(get_interface) IFVAR=$(shell_var "${IFACE}") if [ "$RC_UNAME" = Linux ] && [ "$IFACE" != lo ]; then need sysfs after modules fi after bootmisc keyword -jail -prefix -vserver case "${IFACE}" in lo|lo0) ;; *) after net.lo net.lo0 dbus need localmount provide net ;; esac if [ "$(command -v "depend_${IFVAR}")" = "depend_${IFVAR}" ]; then "depend_${IFVAR}" fi local dep prov for dep in need use before after provide keyword; do eval prov=\$rc_${dep}_${IFVAR} if [ -n "${prov}" ]; then "${dep}" "${prov}" ewarn "rc_${dep}_${IFVAR} is deprecated." ewarn "Please use rc_net_${IFVAR}_${dep} instead." fi done } # Support bash arrays - sigh _array_helper() { local _a= eval _a=\$$1 _a=$(echo "${_a}" | sed -e 's:^[[:space:]]*::' -e 's:[[:space:]]*$::' -e '/^$/d' -e 's:[[:space:]]\{1,\}: :g') [ -n "${_a}" ] && printf "%s\n" "${_a}" } _get_array() { local _a='' if [ -n "${BASH}" ]; then # shellcheck disable=SC2039 case "$(declare -p "$1" 2>/dev/null)" in "declare -a "*) ewarn "You are using a bash array for $1." ewarn "This feature will be removed in the future." ewarn "Please see net.example for the correct format for $1." eval "set -- \"\${$1[@]}\"" for _a; do printf "%s\n" "${_a}" done return 0 ;; esac fi _array_helper "$1" } # Flatten bash arrays to simple strings _flatten_array() { if [ -n "${BASH}" ]; then # shellcheck disable=SC2039 case "$(declare -p "$1" 2>/dev/null)" in "declare -a "*) ewarn "You are using a bash array for $1." ewarn "This feature will be removed in the future." ewarn "Please see net.example for the correct format for $1." eval "set -- \"\${$1[@]}\"" for x; do # shellcheck disable=SC2059 printf "'%s' " "$(printf "$x" | sed "s:':'\\\'':g")" done return 0 ;; esac fi _array_helper "$1" } _wait_for_presence() { local timeout= local rc= _exists && return 0 eval timeout=\$presence_timeout_${IFVAR} timeout=${timeout:-${presence_timeout:-0}} [ ${timeout} -le 0 ] && return 1 einfon "Waiting for ${IFACE} to show up (${timeout} seconds)" while [ ${timeout} -gt 0 ]; do _exists rc=$? [ $rc -eq 0 ] && break sleep 1 printf "." : $(( timeout -= 1 )) done if [ ${timeout} -le 0 ]; then _exists rc=$? fi echo eend $rc } _wait_for_carrier() { local timeout= _has_carrier && return 0 eval timeout=\$carrier_timeout_${IFVAR} timeout=${timeout:-${carrier_timeout:-0}} # Incase users don't want this nice feature ... [ ${timeout} -le 0 ] && return 0 einfon "Waiting for carrier (${timeout} seconds) " while [ ${timeout} -gt 0 ]; do if _has_carrier; then echo eend 0 return 0 fi sleep 1 : $(( timeout -= 1 )) printf "." done echo eend 1 return 1 } _netmask2cidr() { # Some shells cannot handle hex arithmetic, so we massage it slightly # Buggy shells include FreeBSD sh, dash and busybox. # bash and NetBSD sh don't need this. case $1 in 0x*) local hex=${1#0x*} quad= while [ -n "${hex}" ]; do local lastbut2=${hex#??*} quad=${quad}${quad:+.}0x${hex%${lastbut2}*} hex=${lastbut2} done # shellcheck disable=SC2086 set -- ${quad} ;; esac local i='' len='' local IFS=. for i in $1; do case $i in 0x*) i=$((i)) ;; esac while [ ${i} -ne 0 ]; do : $(( len += i % 2 )) : $(( i >>= 1 )) done done echo "${len}" } _configure_variables() { local var='' v='' t='' for var in ${_config_vars}; do local v= for t; do eval v="\"\$${var}_${t}\"" if [ -n "${v}" ]; then eval "${var}_${IFVAR}=\"\$${var}_${t}\"" continue 2 fi done done } _which() { local i OIFS # Empty [ -z "$1" ] && return # check paths OIFS="$IFS" IFS=: for i in $PATH ; do [ -x "$i/$1" ] && echo "$i/$1" && break done IFS=$OIFS } # Like _which, but also consider shell builtins, and multiple alternatives _program_available() { [ -z "$1" ] && return 0 local x= for x; do case "${x}" in /*) [ -x "${x}" ] && break;; *) type "${x}" >/dev/null 2>&1 && break;; esac x= done [ -n "${x}" ] && echo $x && return 0 return 1 } _show_address() { einfo "received address $(_get_inet_address "${IFACE}")" } # Allow custom error handling behavior to be set by the user. # Known used combinations, with defaults # errh_IFVAR_address_EEXIST=warn # errh_IFVAR_route_EEXIST=warn _get_errorhandler_behavior() { IFVAR="$1" object="$2" error="$3" fallback="$4" value= for key in \ "errh_${IFVAR}_${object}_${error}" \ "errh_${IFVAR}_${object}_DEFAULT" \ "errh_${IFVAR}_DEFAULT_${error}" \ "errh_${IFVAR}_DEFAULT_DEFAULT" \ "errh_DEFAULT_${object}_${error}" \ "errh_DEFAULT_${object}_DEFAULT" \ "errh_DEFAULT_DEFAULT_${error}" \ "errh_DEFAULT_DEFAULT_DEFAULT" \ "errh" \ "fallback" ; do eval value="\${${key}}" if [ -n "$value" ]; then echo "$value" && break fi done } _show_address6() { einfo "received address $(_get_inet6_address "${IFACE}")" } # Basically sorts our modules into order and saves the list _gen_module_list() { local x='' f='' force="$1" if ! ${force} ; then if [ -s "${MODULESLIST}" ] && [ "${MODULESLIST}" -nt /proc/$$/status ]; then ewarn "Discarding cached module list ($MODULESLIST) as it's newer current time!" elif [ -s "${MODULESLIST}" ] && [ "${MODULESLIST}" -nt "${MODULESDIR}" ]; then local update=false for x in "${MODULESDIR}"/*.sh; do [ -e "${x}" ] || continue if [ "${x}" -nt "${MODULESLIST}" ]; then update=true break fi done ${update} || return 0 fi fi einfo "Caching network module dependencies" # Run in a subshell to protect the main script ( after() { eval ${MODULE}_after="\"\${${MODULE}_after}\${${MODULE}_after:+ }$*\"" } before() { local mod=${MODULE} local MODULE= for MODULE; do after "${mod}" done } program() { if [ "$1" = "start" ] || [ "$1" = "stop" ]; then local s="$1" shift eval ${MODULE}_program_${s}="\"\${${MODULE}_program_${s}}\${${MODULE}_program_${s}:+ }$*\"" else eval ${MODULE}_program="\"\${${MODULE}_program}\${${MODULE}_program:+ }$*\"" fi } provide() { eval ${MODULE}_provide="\"\${${MODULE}_provide}\${${MODULE}_provide:+ }$*\"" local x for x in "$@"; do eval ${x}_providedby="\"\${${MODULE}_providedby}\${${MODULE}_providedby:+ }${MODULE}\"" done } for MODULE in "${MODULESDIR}"/*.sh; do sh -n "${MODULE}" || continue # shellcheck disable=SC1090 . "${MODULE}" || continue MODULE=${MODULE#${MODULESDIR}/} MODULE=${MODULE%.sh} eval "${MODULE}_depend" MODULES="${MODULES} ${MODULE}" done VISITED= SORTED= visit() { case " ${VISITED} " in *" $1 "*) return;; esac VISITED="${VISITED} $1" eval AFTER=\$${1}_after for MODULE1 in ${AFTER}; do eval PROVIDEDBY=\$${MODULE1}_providedby if [ -n "${PROVIDEDBY}" ]; then for MODULE2 in ${PROVIDEDBY}; do visit "${MODULE2}" done else visit "${MODULE1}" fi done eval PROVIDE=\$${1}_provide for MODULE in ${PROVIDE}; do visit "${MODULE}" done eval PROVIDEDBY=\$${1}_providedby [ -z "${PROVIDEDBY}" ] && SORTED="${SORTED} $1" } for MODULE in ${MODULES}; do visit "${MODULE}" done # Create atomically TMPMODULESLIST=${MODULESLIST}.$$ printf "" > "${TMPMODULESLIST}" i=0 for MODULE in ${SORTED}; do eval PROGRAM=\$${MODULE}_program eval PROGRAM_START=\$${MODULE}_program_start eval PROGRAM_STOP=\$${MODULE}_program_stop eval PROVIDE=\$${MODULE}_provide echo "module_${i}='${MODULE}'" echo "module_${i}_program='${PROGRAM}'" echo "module_${i}_program_start='${PROGRAM_START}'" echo "module_${i}_program_stop='${PROGRAM_STOP}'" echo "module_${i}_provide='${PROVIDE}'" : $(( i += 1 )) done >> "${TMPMODULESLIST}" echo "module_${i}=" >> "${TMPMODULESLIST}" mv -f "${TMPMODULESLIST}" "${MODULESLIST}" ) return 0 } _load_modules() { local starting=$1 mymods= # Ensure our list is up to date _gen_module_list false # shellcheck disable=SC1090 if ! . "${MODULESLIST}"; then _gen_module_list true # shellcheck disable=SC1090 . "${MODULESLIST}" fi MODULES= if [ "${IFACE}" != "lo" ] && [ "${IFACE}" != "lo0" ]; then eval mymods=\$modules_${IFVAR} # shellcheck disable=SC2154 [ -z "${mymods}" ] && mymods=${modules} fi local i=-1 x='' mod='' f='' provides='' while true; do : $(( i += 1 )) eval mod=\$module_${i} [ -z "${mod}" ] && break [ -e "${MODULESDIR}/${mod}.sh" ] || continue eval set -- \$module_${i}_program if [ -n "$1" ]; then if ! _program_available "$@" >/dev/null; then vewarn "Skipping module $mod due to missing program: $*" continue fi fi if ${starting}; then eval set -- \$module_${i}_program_start else eval set -- \$module_${i}_program_stop fi if [ -n "$1" ]; then if ! _program_available "$@" >/dev/null; then vewarn "Skipping module $mod due to missing program: $*" continue fi fi eval provides=\$module_${i}_provide if ${starting}; then case " ${mymods} " in *" !${mod} "*) continue;; *" !${provides} "*) [ -n "${provides}" ] && continue;; esac fi MODULES="${MODULES}${MODULES:+ }${mod}" # Now load and wrap our functions # shellcheck disable=SC1090 if ! . "${MODULESDIR}/${mod}.sh"; then eend 1 "${RC_SVCNAME}: error loading module \`${mod}'" exit 1 fi [ -z "${provides}" ] && continue # Wrap our provides local f= for f in pre_start start post_start; do inner=$(command -v "${mod}_${f}") eval "${provides}_${f}() { [ '${inner}' = '${mod}_${f}' ] || return 0; ${mod}_${f} \"\$@\"; }" done eval module_${mod}_provides="${provides}" eval module_${provides}_providedby="${mod}" done # Wrap our preferred modules for mod in ${mymods}; do case " ${MODULES} " in *" ${mod} "*) eval x=\$module_${mod}_provides [ -z "${x}" ] && continue for f in pre_start start post_start; do inner=$(command -v "${mod}_${f}") eval "${x}_${f}() { [ '${inner}' = '${mod}_${f}' ] || return 0; ${mod}_${f} \"\$@\"; }" done eval module_${x}_providedby="${mod}" ;; esac done # Finally remove any duplicated provides from our list if we're starting # Otherwise reverse the list local LIST="${MODULES}" p= MODULES= if ${starting}; then for mod in ${LIST}; do eval x=\$module_${mod}_provides if [ -n "${x}" ]; then eval p=\$module_${x}_providedby [ "${mod}" != "${p}" ] && continue fi MODULES="${MODULES}${MODULES:+ }${mod}" done else for mod in ${LIST}; do MODULES="${mod}${MODULES:+ }${MODULES}" done fi veinfo "Loaded modules: ${MODULES}" } _load_config() { local config='' fallback='' config="$(_get_array "config_${IFVAR}")" fallback="$(_get_array fallback_${IFVAR})" config_index=0 local IFS="$__IFS" set -- ${config} # We should support a space separated array for cidr configs # But only as long as they do not contain other parameters for the address if [ $# = 1 ]; then unset IFS set -- ${config} # Of course, we may have a single address added old style. # If the NEXT argument is a v4 or v6 address, it's the next config. # Otherwise, it's arguments to the first config... if [ "${2#*.*}" = "${2}" ] && [ "${2#*:*}" = "${2}" ]; then # Not an IPv4/IPv6 local IFS="$__IFS" set -- ${config} fi fi # Ensure that loopback has the correct address if [ "${IFACE}" = "lo" ] || [ "${IFACE}" = "lo0" ]; then if [ "$1" != "null" ]; then config_0="127.0.0.1/8" config_index=1 fi else if [ -z "$1" ]; then ewarn "config_${IFVAR} not specified; defaulting to DHCP" config_0="dhcp" config_index=1 fi fi # We store our config in an array like vars # so modules can influence it for cmd; do eval config_${config_index}="'${cmd}'" : $(( config_index += 1 )) done # Terminate the list eval config_${config_index}= config_index=0 for cmd in ${fallback}; do eval fallback_${config_index}="'${cmd}'" : $(( config_index += 1 )) done # Terminate the list eval fallback_${config_index}= # Don't set to zero, so any net modules don't have to do anything extra config_index=-1 } # Support functions _run_if() { local cmd=$1 iface=$2 ifr=${IFACE} ifv=${IFVAR} # Ensure that we don't stamp on real values local IFACE='' IFVAR='' shift if [ -n "${iface}" ]; then IFACE="${iface}" [ "${iface}" != "${ifr}" ] && IFVAR=$(shell_var "${IFACE}") else IFACE=${ifr} IFVAR=${ifv} fi ${cmd} } interface_exists() { _run_if _exists "$@" } interface_up() { _run_if _up "$@" } interface_down() { _run_if _down "$@" } set_interface_type() { service_set_value iface_type "$@" } get_interface_type() { ( RC_SVCNAME="net.$IFACE" service_get_value iface_type ) } is_interface_type() { [ "$(get_interface_type)" = "$1" ] } start() { local IFACE='' local IFVAR='' local oneworked=false local fallback=false local module='' local cmd='' local our_metric='' local metric=0 local _up_before_preup='' IFACE=$(get_interface) IFVAR=$(shell_var "${IFACE}") eval _up_before_preup="\$up_before_preup_${IFVAR}" # shellcheck disable=SC2154 [ -z "${_up_before_preup}" ] && _up_before_preup=$up_before_preup einfo "Bringing up interface ${IFACE}" eindent if [ -z "${MODULES}" ]; then local MODULES= _load_modules true fi for module in ${MODULES}; do if [ "$(command -v "${module}_pre_up")" = "${module}_pre_up" ]; then ${module}_pre_up || exit $? fi done # We up the iface twice if we have a preup to ensure it's up if # available in preup and afterwards incase the user inadvertently # brings it down if [ "$(command -v preup)" = "preup" ]; then yesno "${_up_before_preup:-yes}" && _up 2>/dev/null ebegin "Running preup" eindent preup || return 1 eoutdent fi _up 2>/dev/null for module in ${MODULES}; do if [ "$(command -v "${module}_pre_start")" = "${module}_pre_start" ]; then ${module}_pre_start || exit $? fi done if ! _wait_for_presence; then eerror "ERROR: interface ${IFACE} does not exist" eerror "Ensure that you have loaded the correct kernel module for your hardware" return 1 fi if ! _wait_for_carrier; then if service_started devd; then ewarn "no carrier, but devd will start us when we have one" mark_service_inactive "${RC_SVCNAME}" else eerror "no carrier" fi return 1 fi local config='' config_index='' _load_config config_index=0 eval our_metric=\$metric_${IFVAR} if [ -n "${our_metric}" ]; then metric=${our_metric} elif [ "${IFACE}" != "lo" ] && [ "${IFACE}" != "lo0" ]; then : $(( metric += $(_ifindex) )) fi while true; do eval config=\$config_${config_index} [ -z "${config}" ] && break set -- ${config} if [ "$1" != "null" ] && [ "$1" != "noop" ]; then ebegin "$1" fi eindent case "$1" in noop) if [ -n "$(_get_inet_address)" ]; then oneworked=true break fi ;; null) :;; [0-9]*|*:*) _add_address ${config};; *) if [ "$(command -v "${config}_start")" = "${config}_start" ]; then "${config}"_start else eerror "nothing provides \`${config}'" fi ;; esac if eend $?; then oneworked=true else eval config=\$fallback_${config_index} if [ -n "${config}" ]; then fallback=true eoutdent ewarn "Trying fallback configuration ${config}" eindent eval config_${config_index}=\$config unset fallback_${config_index} : $(( config_index -= 1 )) fi fi eoutdent : $(( config_index += 1 )) done if ! ${oneworked}; then if [ "$(command -v failup)" = "failup" ]; then ebegin "Running failup" eindent failup eoutdent fi return 1 fi local first=true routes= if ${fallback}; then routes="$(_get_array "fallback_routes_${IFVAR}")" fi if [ -z "${routes}" ]; then routes="$(_get_array "routes_${IFVAR}")" fi if [ "${IFACE}" = "lo" ] || [ "${IFACE}" = "lo0" ]; then if [ "${config_0}" != "null" ]; then routes="127.0.0.0/8 via 127.0.0.1 ${routes}" fi fi local OIFS="${IFS}" SIFS="${IFS-y}" local IFS="$__IFS" local fam for cmd in ${routes}; do unset IFS if ${first}; then first=false einfo "Adding routes" fi case ${cmd} in -6" "*) fam="-6"; cmd=${cmd#-6 };; -4" "*) fam="-4"; cmd=${cmd#-4 };; esac eindent ebegin ${cmd} # Work out if we're a host or a net if not told case ${cmd} in -net\ *|-host\ *);; *\ netmask\ *) cmd="-net ${cmd}";; *.*.*.*/32*) cmd="-host ${cmd}";; *.*.*.*/*|0.0.0.0|0.0.0.0\ *) cmd="-net ${cmd}";; default|default\ *) cmd="-net ${cmd}";; *:*/128*) cmd="-host ${cmd}";; *:*/*) cmd="-net ${cmd}";; *) cmd="-host ${cmd}";; esac _add_route ${fam} ${cmd} eend $? eoutdent done if [ "${SIFS}" = "y" ]; then unset IFS else IFS="${OIFS}" fi for module in ${MODULES}; do if [ "$(command -v "${module}_post_start")" = "${module}_post_start" ]; then ${module}_post_start || exit $? fi done if [ "$(command -v postup)" = "postup" ]; then ebegin "Running postup" eindent postup eoutdent fi return 0 } stop() { # Don't stop the network at shutdown. # We don't use the noshutdown keyword so that we are started again # correctly if we go back to multiuser. yesno ${keep_network:-YES} && yesno $RC_GOINGDOWN && return 0 local IFACE='' module='' local IFVAR='' IFACE=$(get_interface) IFVAR=$(shell_var "${IFACE}") einfo "Bringing down interface ${IFACE}" eindent if [ -z "${MODULES}" ]; then local MODULES='' _load_modules false fi if [ "$(command -v predown)" = "predown" ]; then ebegin "Running predown" eindent predown || return 1 eoutdent else if is_net_fs /; then eerror "root filesystem is network mounted -- can't stop ${IFACE}" return 1 fi fi for module in ${MODULES}; do if [ "$(command -v "${module}_pre_stop")" = "${module}_pre_stop" ]; then ${module}_pre_stop || exit $? fi done for module in ${MODULES}; do if [ "$(command -v "${module}_stop")" = "${module}_stop" ]; then ${module}_stop fi done # Only delete addresses for interfaces that exist if _exists; then # PPP can manage it's own addresses when IN_BACKGROUND # Important in case "demand" set on the ppp link if ! (yesno ${IN_BACKGROUND} && is_ppp) ; then _delete_addresses "${IFACE}" fi fi for module in ${MODULES}; do if [ "$(command -v "${module}_post_stop")" = "${module}_post_stop" ]; then ${module}_post_stop fi done # If not in background, and not loopback then bring the interface down # unless overridden. if ! yesno ${IN_BACKGROUND} && \ [ "${IFACE}" != "lo" ] && [ "${IFACE}" != "lo0" ]; then eval module=\$ifdown_${IFVAR} module=${module:-${ifdown:-YES}} yesno ${module} && _down 2>/dev/null fi type resolvconf >/dev/null 2>&1 && resolvconf -d "${IFACE}" 2>/dev/null if [ "$(command -v "postdown")" = "postdown" ]; then ebegin "Running postdown" eindent postdown eoutdent fi return 0 } # vim:filetype=gentoo-init-d: