diff options
Diffstat (limited to 'src/lib/network')
-rw-r--r-- | src/lib/network | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/src/lib/network b/src/lib/network new file mode 100644 index 0000000..1aa384c --- /dev/null +++ b/src/lib/network @@ -0,0 +1,368 @@ +### Globals +. /usr/lib/network/globals +# will load hooks + +## Loads a profile. +# $1: profile name +load_profile() +{ + unset ROUTES + [[ -z "$1" ]] && return 1 + if [[ ! -f "$PROFILE_DIR/$1" ]]; then + report_err "Profile \"$1\" does not exist" + return 1 + fi + report_debug "Loading profile $1" + INTERFACE=$(. "$PROFILE_DIR/$1"; echo "$INTERFACE") + report_debug "Configuring interface $INTERFACE" + if [[ -z "$INTERFACE" ]]; then + report_err "Profile missing an interface to configure" + return 1 + fi + if [[ -f "$IFACE_DIR/$INTERFACE" ]]; then + report_debug "Interface level configuration enabled: $IFACE_DIR/$INTERFACE" + . "$IFACE_DIR/$INTERFACE" + fi + . "$PROFILE_DIR/$1" # we want profile settings to override, so need to source profile again + if [[ ! -f "$CONN_DIR/$CONNECTION" ]]; then + report_err "$CONNECTION is not a valid connection, check spelling or look at examples" + return 1 + fi +} + +################## +# Profile up/down +################## + +## Take all registered profiles down. +all_down() +{ + find "$STATE_DIR/profiles/" -maxdepth 1 -type f -printf '%f\n' \ + | while read prof; do + profile_down "$prof" + done +} + +## Create the state dir and sub directories if they don't already exist. +check_make_state_dir() +{ + [[ ! -d "$STATE_DIR" ]] && mkdir -p "$STATE_DIR"/{interfaces,profiles} + for d in interfaces profiles suspend; do + [[ ! -d "$STATE_DIR/$d" ]] && mkdir "$STATE_DIR/$d" + done +} + +## Save the list of running profiles and take them down if needed +# $1: interface name or "all" +# $2: take associated profiles down (optional, default="yes") +interface_suspend() +{ + report_debug interface_suspend "$@" + + check_make_state_dir + find "$STATE_DIR/profiles/" -maxdepth 1 -type f -printf '%f\n' \ + | while read prof; do + # the pipe to "while read" will create a subshell + INTERFACE=$(. "$STATE_DIR/profiles/$prof"; echo "$INTERFACE") + if [[ "$1" == all || "$1" == "$INTERFACE" ]]; then + report_notice "suspending interface $INTERFACE with profile $prof" + cp "$STATE_DIR/profiles/$prof" "$STATE_DIR/suspend/" + if checkyesno "${2:-yes}"; then + profile_down "$prof" + fi + fi + done +} + +## Save the list of all running profiles and take them down +all_suspend() { + interface_suspend all +} + +## Restore saved profiles (for resume purposes). +# $@: a list of interfaces not to resume (e.g., because they're disabled) +all_resume() +{ + report_debug all_resume "$@" + find "$STATE_DIR/suspend/" -maxdepth 1 -type f -printf '%f\n' \ + | while read prof; do + # the pipe to "while read" will create a subshell + INTERFACE=$(. "$STATE_DIR/suspend/$prof"; echo "$INTERFACE") + if [[ $# -eq 0 || ! " $* " =~ " $INTERFACE " ]]; then + report_notice "resuming interface $INTERFACE with profile $prof" + profile_up "$prof" + rm -f "$STATE_DIR/suspend/$prof" # if profile_up succeeds, it will have already removed this + fi + done +} + +## Puts up a profile. +# $1: the profile name +profile_up() +{ + ( + # Keep inside subshell so that options from one profile don't cross to others + # exit 1 used in a subshell is effectively exiting a new process + check_make_state_dir + + local status PROFILE="$1" # save PROFILE in a variable so that it's available to PRE_UP/POST_DOWN etc hooks + + load_profile "$PROFILE" || exit 1 + + if check_profile "$PROFILE"; then + report_err "$PROFILE already connected" + exit 1 + fi + + # EXCLUSIVE, network.d/profile: Individual profile is mutually exclusive + if checkyesno "$EXCLUSIVE"; then + all_down + fi + + report_try "$PROFILE up" + + status=$(check_iface "$INTERFACE") + report_debug "status reported to profile_up as: $status" + case "$status" in + external) + report_fail "Interface $INTERFACE externally controlled" + exit 1 + ;; + disabled) + report_fail "Interface $INTERFACE is disabled" + exit 1 + ;; + "") + ;; + *) + if checkyesno "$CHECK"; then + report_fail "Interface $INTERFACE already in use" + exit 1 + + # not necessary to sandbox this call or reload PROFILE afterwards + elif ! interface_down "$INTERFACE"; then + report_fail + exit 1 + fi + ;; + esac + + if ! ( eval $PRE_UP ); then # JP: sandbox the eval so variables don't bleed into current function + report_debug profile_up "PRE_UP failed" + report_fail + exit 1 + fi + + if ! "$CONN_DIR/$CONNECTION" up "$PROFILE"; then + report_debug profile_up "connect failed" + report_fail + # "$CONN_DIR/$CONNECTION" down "$PROFILE" # JP: should we do this to make sure? + exit 1 + fi + + if ! ( eval $POST_UP ); then # JP: sandbox the eval + report_debug profile_up "POST_UP failed" + report_fail + # failing POST_UP will take interface down + "$CONN_DIR/$CONNECTION" down "$PROFILE" + exit 1 + fi + + set_profile up "$PROFILE" + unset EXCLUSIVE + + # Successfully running a new profile; erase any suspended profiles on this interface + local iface="$INTERFACE" + find "$STATE_DIR/suspend/" -maxdepth 1 -type f -printf '%f\n' \ + | while read prof; do + # the pipe to "while read" will create a subshell + INTERFACE=$(. "$STATE_DIR/suspend/$prof"; echo "$INTERFACE") + if [[ "$iface" == "$INTERFACE" ]]; then + rm "$STATE_DIR/suspend/$prof" + fi + done + + report_success + ); return $? +} + +## Puts a profile down. +# $1: the profile name +profile_down() +{ + ( + check_make_state_dir + + local status PROFILE="$1" # save PROFILE in a variable so that it's available to PRE_UP/POST_DOWN etc hooks + + load_profile "$PROFILE" || exit 1 + + status=$(check_iface "$INTERFACE") + report_debug "status reported to profile_down as: $status" + + if [[ "$status" != "$PROFILE" ]]; then + # if interface not available to be controlled by netcfg, then + # any profiles should have been removed by check_iface + # else we get here if another profile is running + report_err "Profile not connected" + exit 1 + fi + + report_try "$PROFILE down" + if [[ "$(check_iface "$INTERFACE")" == "external" ]]; then + report_fail "$interface was connected by another application" + exit 1 + fi + + if ! ( eval $PRE_DOWN ); then # JP: sandbox the eval + report_debug profile_down "PRE_DOWN failed" + # true # JP: did we want failing PRE_DOWN to leave the profile active? + report_fail + exit 1 + fi + + if ! "$CONN_DIR/$CONNECTION" down "$PROFILE"; then + report_debug profile_up "disconnect failed" + report_fail + exit 1 + fi + + if ! ( eval $POST_DOWN ); then # JP: sandbox the eval + report_debug profile_down "POST_DOWN failed" + report_fail + exit 1 + fi + + set_profile down "$PROFILE" + report_success + ); return $? +} + +# interface_down interface +# take interface down +# +interface_down() +{ + local profile=$(check_iface "$1") + case "$profile" in + ""|disabled) return 0 ;; + external) return 1 ;; + *) profile_down "$profile" ;; + esac +} + +# interface_reconnect interface +# reconnects the profile active on interface +interface_reconnect() +{ + local profile=$(check_iface "$1") + case "$profile" in + ""|disabled|external) + return 1 + ;; + *) + profile_down "$profile" + profile_up "$profile" + ;; + esac +} + +## +# check_iface interface +# Return 0 if interface unavailable (in use by a profile or externally, or disabled) +# Return 1 if interface down and available to be used +# +check_iface() { + if [[ -f "$STATE_DIR/interfaces/$1" ]]; then + cat "$STATE_DIR/interfaces/$1" + return 0 + else + return 1 + fi +} + +# list_profiles +# Outputs a list of all profiles +list_profiles() { + # JP: follow aliases with -L, also skip profiles that start with '.' or end with '~' or '.conf' (so profile.conf can be the wpa.conf file for profile) + find -L "$PROFILE_DIR/" -maxdepth 1 -type f -not -name '*~' -not -name '*.conf' -not -name '.*' -printf "%f\n" +} + +# check_profile profile +# Return 0 if profile registered as being up +# Return 1 if profile not registered +# +check_profile() { + [[ -f "$STATE_DIR/profiles/$1" && ! -f "$STATE_DIR/suspend/$1" ]] && return 0 + return 1 +} + +### Status setting functions +## +# set_profile up/down profile +# Set profile state, either up or down +# +set_profile() { + local INTERFACE + if [[ "$1" == "up" ]]; then + INTERFACE=$(. "$PROFILE_DIR/$2"; echo "$INTERFACE") + cp "$PROFILE_DIR/$2" "$STATE_DIR/profiles/" + set_iface up "$INTERFACE" "$2" + elif [[ "$1" == "down" && -f "$STATE_DIR/profiles/$2" ]]; then # JP: skip if profile not already up + INTERFACE=$(. "$STATE_DIR/profiles/$2"; echo "$INTERFACE") + rm "$STATE_DIR/profiles/$2" + set_iface down "$INTERFACE" "$2" + fi +} + +# set_iface up/down interface [profile] +# Set interface status to up/down +# optionally link it to a profile. +# +set_iface() { + local PROFILE="${3:-external}" + if [[ "$1" == "up" ]]; then + echo "$PROFILE" > "$STATE_DIR/interfaces/$2" + elif [[ "$1" == "down" ]]; then + rm -f "$STATE_DIR/interfaces/$2" # JP: add -f so we don't complain if the interface isn't up + fi +} + +is_interface() { + local INTERFACE="$1" + if [[ ! -e "/sys/class/net/$INTERFACE" ]]; then + if ! echo "$INTERFACE" | grep -F -q ":"; then + return 1 + fi + fi + return 0 +} + +interface_is_up() { + local flags + read flags < "/sys/class/net/$1/flags" + # IFF_UP is defined as 0x1 in linux/if.h + (( flags & 0x1 )) +} + +## Changes a network interface state. +# $1: up, flush, or down. +# $2: the interface name +bring_interface() +{ + local INTERFACE="$2" + case "$1" in + up) + ip link set dev "$INTERFACE" up &>/dev/null + timeout_wait "${UP_TIMEOUT:-5}" 'interface_is_up "$INTERFACE"' || return 1 + ;; + flush|down) + ip addr flush dev "$INTERFACE" &>/dev/null + ;;& + down) + ip link set dev "$INTERFACE" down &>/dev/null + ;; + esac +} + +# vim: ft=sh ts=4 et sw=4: |