From 8f6e396f8ee47d52faac67922d0ddf6fdfbabecd Mon Sep 17 00:00:00 2001 From: Sebastian Wicki Date: Sun, 21 Jul 2013 17:05:03 +0200 Subject: Rewrite of netctl-auto This adds a command line interface to the netctl-auto script to allow users more control over the automatic profile selection. --- src/netctl-auto | 268 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 228 insertions(+), 40 deletions(-) diff --git a/src/netctl-auto b/src/netctl-auto index ea692ca..429fa3f 100755 --- a/src/netctl-auto +++ b/src/netctl-auto @@ -4,71 +4,259 @@ . "$SUBR_DIR/wpa" . "$SUBR_DIR/rfkill" -: ${ACTIOND:=wpa_actiond -p /run/wpa_supplicant} -: ${ACTION_SCRIPT:=$SUBR_DIR/auto.action} - -if [[ $# != 2 || $1 != @(start|stop) ]]; then - exit_error "Usage: netctl-auto [start|stop] " -fi - -STARTSTOP=$1 -INTERFACE=$2 -PIDFILE="$STATE_DIR/wpa_actiond_$INTERFACE.pid" -shift 2 - -case $STARTSTOP in - start) - if wpa_is_active "$INTERFACE"; then - exit_error "The interface ($INTERFACE) is already in use" +usage() { + cat << END +Usage: netctl-auto {COMMAND} ... + [--help|--version] + +Commands: + list List available profiles (active='*', disabled='!') + current List currently active profiles + switch-to [PROFILE] Switch to a profile, enable it if necessary + enable [PROFILE] Enable a profile for automatic selection + disable [PROFILE] Disable a profile temporarily for automatic selection + enable-all Enable all profiles for automatic selection + disable-all Disable all profiles temporarily for automatic selection +END +} + +## Print a list of interfaces for which wpa_actiond is active +list_actiond_interfaces() { + find "$STATE_DIR" -maxdepth 1 -type f -name 'wpa_actiond_*.pid' \ + -exec basename -s ".pid" -a {} + | cut -d'_' -f3- +} + +## List all profiles available to the WPA supplicant +## Output format: INTERFACE ID FLAG PROFILE.. +## INTERFACE network interface of the profile +## ID wpa_supplicant numerical network id +## FLAG 'e'=enabled, 'd'=disabled, 'a'=active +## PROFILE.. profile name, may contain spaces +list_wpa_profiles() { + local interface + for interface in $(list_actiond_interfaces); do + local id ssid bssid flags + while IFS=$'\t' read -r id ssid bssid flags; do + local flag="e" + if [[ "$flags" =~ \[CURRENT\] ]]; then + flag="a" + elif [[ "$flags" =~ \[DISABLED\] ]]; then + flag="d" + fi + + local profile=$(wpa_call $interface get_network $id id_str) + profile=$(wpa_unquote "$profile") + + echo $interface $id $flag "$profile" + done < <(wpa_call $interface list_networks | tail -n+2) + done +} + +## Get WPA supplicant network id and interface for the given profile +## Output format: INTERFACE ID +# $1: profile name +get_wpa_network_id() { + local interface id flag profile + while read -r interface id flag profile; do + if [[ "$1" == "$profile" ]]; then + echo $interface $id + return 0 + fi + done < <(list_wpa_profiles) + + report_error "Profile '$1' does not exist or is not available" >&2 + return 1 +} + +## Enable or disable profiles in WPA supplicant +# $1: profile action: "enable", "disable", "enable-all" or "disable-all" +# $2: profile name if action is "enable" or "disable" +profile_enable_disable() { + local action=$1 profile="$2" + local id interfaces wpa_cmd + + if [ -n "$profile" ]; then + read -r interfaces id < <(get_wpa_network_id "$profile") || return 1 + else + interfaces=$(list_actiond_interfaces) + fi + + case $action in + enable) + wpa_cmd=(enable_network $id);; + disable) + wpa_cmd=(disable_network $id);; + enable-all) + wpa_cmd=(enable_network all);; + disable-all) + wpa_cmd=(disable_network all);; + *) + return 1; + esac + + local interface + for interface in $interfaces; do + wpa_call $interface ${wpa_cmd[@]} >/dev/null + if [[ "${wpa_cmd[0]}" == "enable_network" ]]; then + wpa_call $interface reassociate >/dev/null + fi + done +} + +## Select profile in WPA supplicant, but preserve state of all other networks +# $1: profile name +switch_to() { + local profile="$1" + local id interface timeout + + # Load profile interface, WPA network id and timeout + read -r interface id < <(get_wpa_network_id "$profile") || return 1 + timeout=$(. "$PROFILE_DIR/$profile" >/dev/null; echo ${TimeoutWPA:=15}) + + # List of enabled networks + local enabled_networks=$(wpa_call $interface list_networks | tail -n+2 | \ + cut -f 1,4 | fgrep -v '[DISABLED]' | cut -f 1 | tr "\n" ' ') + + reenable_networks() { + for network in $enabled_networks; do + wpa_call $interface enable_network $network >/dev/null + done + + if [[ $(wpa_get_state $interface) != "COMPLETED" ]]; then + if ! in_array $id $enabled_networks; then + wpa_call $interface disable_network $id >/dev/null + fi + fi + } + + # Reenable networks in case user aborts + trap "reenable_networks; exit 1" SIGINT SIGTERM + + # select_network will disable all other networks on that interface + wpa_call $interface select_network $id >/dev/null + if ! wpa_wait_until_state $timeout $interface "COMPLETED"; then + report_error "WPA association/authentication failed for interface '$interface'" fi - if [[ -x "$PROFILE_DIR/interfaces/$INTERFACE" ]]; then - source "$PROFILE_DIR/interfaces/$INTERFACE" + + reenable_networks +} + +## List currently active profiles +current() { + local interface + for interface in $(list_actiond_interfaces); do + local state=$(wpa_get_state $interface) + if [[ "$state" == "COMPLETED" ]]; then + wpa_call $interface status | grep '^id_str=' | cut -d'=' -f2- + fi + done +} + +## List all available profiles and their status +list() { + local interface id flag profile + while read -r interface id flag profile; do + echo "$(echo $flag | tr 'aed' '* !')" "$profile" + done < <(list_wpa_profiles) +} + +## Start and generate config file for the WPA supplicant, start wpa_actiond +# $1: interface +start() { + local interface=$1 + local pidfile="$STATE_DIR/wpa_actiond_$1.pid" + + if wpa_is_active "$interface"; then + exit_error "The interface ($interface) is already in use" + fi + if [[ -x "$PROFILE_DIR/interfaces/$interface" ]]; then + source "$PROFILE_DIR/interfaces/$interface" fi if [[ $RFKill ]]; then - enable_rf "$INTERFACE" "$RFKill" || exit 1 + enable_rf "$interface" "$RFKill" || return 1 fi - if ! WPA_CONF=$(wpa_make_config_file "$INTERFACE"); then - exit_error "Could not create the configuration file for interface '$INTERFACE'" + + local wpa_conf + if ! wpa_conf=$(wpa_make_config_file "$interface"); then + exit_error "Could not create the configuration file for interface '$interface'" fi + + local profile list_profiles | while read -r profile; do report_debug "Examining profile '$profile'" ( source "$PROFILE_DIR/$profile" - [[ $Interface == "$INTERFACE" ]] || continue + [[ $Interface == "$interface" ]] || continue is_yes "${ExcludeAuto:-no}" && exit 1 [[ $Connection != "wireless" ]] && exit 1 : ${Security:=none} # Exclude wpa-config, the wpa_conf is 'complete' and doesn't fit in this scheme [[ $Security == "wpa-config" ]] && exit 1 - printf "%s\n" "network={" "$(wpa_make_config_block)" "id_str=\"$profile\"" "}" >> "$WPA_CONF" + printf "%s\n" "network={" "$(wpa_make_config_block)" "id_str=\"$profile\"" "}" >> "$wpa_conf" report_notice "Included profile '$profile'" ) done - # Start the WPA supplicant + # Start the WPA supplicant and wpa_actiond : ${WPADriver:=nl80211,wext} WPAOptions+=" -W" - if wpa_start "$INTERFACE" "$WPADriver" "$WPA_CONF"; then - if $ACTIOND -i "$INTERFACE" -P "$PIDFILE" -a "$ACTION_SCRIPT" "$@"; then - exit 0 + if wpa_start "$interface" "$WPADriver" "$wpa_conf"; then + if $ACTIOND -i "$interface" -P "$pidfile" -a "$ACTION_SCRIPT"; then + return 0 fi - wpa_stop "$INTERFACE" + wpa_stop "$interface" fi - exit 1 - ;; - stop) - kill "$(< "$PIDFILE")" - if [[ -x "$PROFILE_DIR/interfaces/$INTERFACE" ]]; then - source "$PROFILE_DIR/interfaces/$INTERFACE" + return 1 +} + +## Stop wpa_supplicant and wpa_actiond +# $1: interface +stop() { + local interface=$1 + local pidfile="$STATE_DIR/wpa_actiond_$1.pid" + + [[ -e "$pidfile" ]] && kill "$(< "$pidfile")" + if [[ -x "$PROFILE_DIR/interfaces/$interface" ]]; then + source "$PROFILE_DIR/interfaces/$interface" fi - timeout_wait 1 '! wpa_is_active "$INTERFACE"' || wpa_stop "$INTERFACE" - ip link set dev "$INTERFACE" down - [[ $RFKill ]] && disable_rf "$INTERFACE" "$RFKill" - exit 0 - ;; -esac + timeout_wait 1 '! wpa_is_active "$interface"' || wpa_stop "$interface" + ip link set dev "$interface" down + [[ $RFKill ]] && disable_rf "$interface" "$RFKill" + return 0 +} +case $# in + 1) + case $1 in + --version) + report_notice "netctl version $NETCTL_VERSION";; + --help) + usage;; + list|current) + "$1";; + enable-all|disable-all) + profile_enable_disable $1;; + *) + exit_error "$(usage)";; + esac;; + 2) + case $1 in + enable|disable) + profile_enable_disable $1 "$2";; + switch-to) + switch_to "$2";; + start|stop) + ensure_root "$(basename "$0")" + : ${ACTIOND:=wpa_actiond -p /run/wpa_supplicant} + : ${ACTION_SCRIPT:=$SUBR_DIR/auto.action} + $1 "$2";; + *) + exit_error "$(usage)";; + esac;; + *) + exit_error "$(usage)";; +esac # vim: ft=sh ts=4 et sw=4: -- cgit v1.2.3-24-g4f1b