#!/sbin/runscript # Copyright (c) 2007-2009 Roy Marples # Released under the 2-clause BSD license. MODULESDIR="/lib/netifrc/net" MODULESLIST="${RC_SVCDIR}/nettree" _config_vars="config routes" [ -z "${IN_BACKGROUND}" ] && IN_BACKGROUND="NO" description="Configures network interfaces." # Handy var so we don't have to embed new lines everywhere for array splitting __IFS=" " depend() { local IFACE=${RC_SVCNAME#*.} local IFVAR=$(shell_var "${IFACE}") need localmount if [ "$RC_UNAME" = Linux -a "$IFACE" != lo ]; then need sysfs fi after bootmisc keyword -jail -prefix -vserver case "${IFACE}" in lo|lo0) ;; *) after net.lo net.lo0 dbus 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 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 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 printf "'%s' " "$(printf "$x" | sed "s:':'\\\'':g")" done return 0 ;; esac fi _array_helper $1 } _wait_for_carrier() { local timeout= efunc=einfon _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 yesno ${RC_PARALLEL} && efunc=einfo ${efunc} "Waiting for carrier (${timeout} seconds) " while [ ${timeout} -gt 0 ]; do if _has_carrier; then [ "${efunc}" = "einfon" ] && echo eend 0 return 0 fi sleep 1 : $(( timeout -= 1 )) [ "${efunc}" = "einfon" ] && printf "." done [ "${efunc}" = "einfon" ] && 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 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 unset x done [ -n "${x}" ] && echo $x && return 0 return 1 } _show_address() { einfo "received address $(_get_inet_address "${IFACE}")" } # Basically sorts our modules into order and saves the list _gen_module_list() { local x= f= force=$1 if ! ${force} && [ -s "${MODULESLIST}" -a "${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 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" -o "$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 . "${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 MODULE in ${AFTER}; do eval PROVIDEDBY=\$${MODULE}_providedby if [ -n "${PROVIDEDBY}" ]; then for MODULE in ${PROVIDEDBY}; do visit "${MODULE}" done else visit "${MODULE}" 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 printf "" > "${MODULESLIST}" 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}'" >> "${MODULESLIST}" echo "module_${i}_program='${PROGRAM}'" >> "${MODULESLIST}" echo "module_${i}_program_start='${PROGRAM_START}'" >> "${MODULESLIST}" echo "module_${i}_program_stop='${PROGRAM_STOP}'" >> "${MODULESLIST}" echo "module_${i}_provide='${PROVIDE}'" >> "${MODULESLIST}" : $(( i += 1 )) done echo "module_${i}=" >> "${MODULESLIST}" ) return 0 } _load_modules() { local starting=$1 mymods= # Ensure our list is up to date _gen_module_list false if ! . "${MODULESLIST}"; then _gen_module_list true . "${MODULESLIST}" fi MODULES= if [ "${IFACE}" != "lo" -a "${IFACE}" != "lo0" ]; then eval mymods=\$modules_${IFVAR} [ -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 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 eval "${provides}_${f}() { [ "$(command -v "${mod}_${f}")" = "${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 eval "${x}_${f}() { [ "$(command -v "${mod}_${f}")" = "${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="$(_get_array "config_${IFVAR}")" local 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}" -a "${2#*:*}" = "${2}" ]; then # Not an IPv4/IPv6 local IFS="$__IFS" set -- ${config} fi fi # Ensure that loopback has the correct address if [ "${IFACE}" = "lo" -o "${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 "$@" } start() { local IFACE=${RC_SVCNAME#*.} oneworked=false fallback=false module= local IFVAR=$(shell_var "${IFACE}") cmd= our_metric= local metric=0 _up_before_preup eval _up_before_preup="\$up_before_preup_${IFVAR}" [ -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 # 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 ! _exists; 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" -a "${IFACE}" != "lo0" ]; then : $(( metric += $(_ifindex) )) fi while true; do eval config=\$config_${config_index} [ -z "${config}" ] && break set -- ${config} if [ "$1" != "null" -a "$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 hideFirstroute=false 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" -o "${IFACE}" = "lo0" ]; then if [ "${config_0}" != "null" ]; then routes="127.0.0.0/8 via 127.0.0.1 ${routes}" hideFirstroute=true 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 if ${hideFirstroute}; then _add_route ${fam} ${cmd} >/dev/null 2>&1 hideFirstroute=false else _add_route ${fam} ${cmd} >/dev/null fi 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=${RC_SVCNAME#*.} module= local IFVAR=$(shell_var "${IFACE}") opts= 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" -a "${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 }