create_ap

create a nat-ed wifi ap
git clone git://git.2f30.org/create_ap
Log | Files | Refs | README | LICENSE

commit 4ea5d02193d24b2fea90d785e965a7c9e596a3bc
parent cafef2e185ee147bc3d2fd32b2db358bb2ef91ae
Author: oblique <psyberbits@gmail.com>
Date:   Sun, 26 Apr 2015 15:20:49 +0300

Implement recursive mutex that works across processes/threads

Diffstat:
Mcreate_ap | 166++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 148 insertions(+), 18 deletions(-)

diff --git a/create_ap b/create_ap @@ -23,7 +23,8 @@ export LC_ALL=C # all new files and directories must be readable only by root. # in special cases we must use chmod to give any other permissions. -umask 0077 +SCRIPT_UMASK=0077 +umask $SCRIPT_UMASK usage() { echo "Usage: "$PROGNAME" [options] <wifi-interface> [<interface-with-internet>] [<access-point-name> [<passphrase>]]" @@ -82,6 +83,78 @@ usage() { echo " "$PROGNAME" --stop wlan0" } +# allocate lock for the caller bash thread +alloc_lock() { + # lock file for creating mutex + local LOCK_FILE=/tmp/create_ap.lock + local LOCK_FD=LOCK_FD_$BASHPID + + # if lock FD is already allocated just return + eval "[[ \$$LOCK_FD -ne 0 ]]" && return 0 + + # use the lowest unused FD. avoid FD 0 because we use it to + # indicate that LOCK_FD_$BASHPID is not set. + for x in $(seq 1 $(ulimit -n)); do + if [[ ! -a "/proc/$BASHPID/fd/$x" ]]; then + eval "$LOCK_FD=$x" + + # open/create lock file with write access for all users + # otherwise normal users will not be able to use it. + # to avoid race conditions on creation, we need to + # use umask to set the permissions. + umask 0555 + eval "eval \"exec \$$LOCK_FD>$LOCK_FILE\"" > /dev/null 2>&1 || return 1 + umask $SCRIPT_UMASK + + # there is a case where lock file was created from a normal + # user. change the owner to root as soon as we can. + [[ $(id -u) -eq 0 ]] && chown 0:0 $LOCK_FILE + + return 0 + fi + done + + return 1 +} + +# recursive mutex lock for all create_ap processes/threads. +# +# WARNING: if you lock in a function that their caller need their +# output (i.e. the caller use | or $()) then you can have dead-lock +# if the caller took the lock before. this happens because bash creates +# a new thread for the callie function. +mutex_lock() { + local MUTEX_COUNTER=MUTEX_COUNTER_$BASHPID + local LOCK_FD=LOCK_FD_$BASHPID + + # allocate lock FD if needed + if eval "[[ \$$LOCK_FD -eq 0 ]]"; then + alloc_lock || die "Failed to allocate lock" + fi + + # lock if needed and increase the counter + eval "[[ \$$MUTEX_COUNTER -eq 0 ]]" && eval "flock \$$LOCK_FD" + eval "$MUTEX_COUNTER=\$(( \$$MUTEX_COUNTER + 1 ))" + + return 0 +} + +# recursive mutex unlock for all create_ap processes/threads +mutex_unlock() { + local MUTEX_COUNTER=MUTEX_COUNTER_$BASHPID + local LOCK_FD=LOCK_FD_$BASHPID + + # if lock FD was not allocated or we didn't lock before + # then just return + eval "[[ \$$LOCK_FD -eq 0 || \$$MUTEX_COUNTER -eq 0 ]]" && return 0 + + # unlock if needed and decrease the counter + eval "$MUTEX_COUNTER=\$(( \$$MUTEX_COUNTER - 1 ))" + eval "[[ \$$MUTEX_COUNTER -eq 0 ]]" && eval "flock -u \$$LOCK_FD" + + return 0 +} + # it takes 2 arguments # returns: # 0 if v1 (1st argument) and v2 (2nd argument) are the same @@ -246,24 +319,30 @@ get_macaddr() { get_avail_bridge() { local i=0 + mutex_lock while :; do if ! is_interface br${i}; then + mutex_unlock echo br${i} return fi i=$((i + 1)) done + mutex_unlock } get_virt_iface_name() { local i=0 + mutex_lock while :; do if ! is_interface ap${i}; then + mutex_unlock echo ap${i} return fi i=$((i+1)) done + mutex_unlock } get_all_macaddrs() { @@ -274,10 +353,12 @@ get_new_macaddr() { local OLDMAC NEWMAC LAST_BYTE i OLDMAC=$(get_macaddr "$1") LAST_BYTE=$(printf %d 0x${OLDMAC##*:}) + mutex_lock for i in {1..255}; do NEWMAC="${OLDMAC%:*}:$(printf %02x $(( ($LAST_BYTE + $i) % 256 )))" (get_all_macaddrs | grep "$NEWMAC" > /dev/null 2>&1) || break done + mutex_unlock echo $NEWMAC } @@ -285,6 +366,7 @@ get_new_macaddr() { haveged_watchdog() { local show_warn=1 while :; do + mutex_lock if [[ $(cat /proc/sys/kernel/random/entropy_avail) -lt 1000 ]]; then if ! which haveged > /dev/null 2>&1; then if [[ $show_warn -eq 1 ]]; then @@ -297,9 +379,11 @@ haveged_watchdog() { haveged -w 1024 -F > /dev/null 2>&1 & local haveged_pid=$! echo $haveged_pid > $CONFDIR/haveged.pid + mutex_unlock wait $haveged_pid fi fi + mutex_unlock sleep 2 done } @@ -354,14 +438,20 @@ networkmanager_add_unmanaged() { [[ -z "$MAC" ]] && return 1 fi + mutex_lock UNMANAGED=$(grep -m1 -Eo '^unmanaged-devices=[[:alnum:]:;,-]*' /etc/NetworkManager/NetworkManager.conf) + WAS_EMPTY=0 [[ -z "$UNMANAGED" ]] && WAS_EMPTY=1 UNMANAGED=$(echo "$UNMANAGED" | sed 's/unmanaged-devices=//' | tr ';,' ' ') + # if it exists, do nothing for x in $UNMANAGED; do - [[ $x == "mac:${MAC}" ]] && return 2 - [[ $NM_OLDER_VERSION -eq 0 && $x == "interface-name:${1}" ]] && return 2 + if [[ $x == "mac:${MAC}" ]] || + [[ $NM_OLDER_VERSION -eq 0 && $x == "interface-name:${1}" ]]; then + mutex_unlock + return 2 + fi done if [[ $NM_OLDER_VERSION -eq 1 ]]; then @@ -383,6 +473,7 @@ networkmanager_add_unmanaged() { fi ADDED_UNMANAGED="${ADDED_UNMANAGED} ${1} " + mutex_unlock return 0 } @@ -401,9 +492,13 @@ networkmanager_rm_unmanaged() { [[ -z "$MAC" ]] && return 1 fi + mutex_lock UNMANAGED=$(grep -m1 -Eo '^unmanaged-devices=[[:alnum:]:;,-]*' /etc/NetworkManager/NetworkManager.conf | sed 's/unmanaged-devices=//' | tr ';,' ' ') - [[ -z "$UNMANAGED" ]] && return 1 + if [[ -z "$UNMANAGED" ]]; then + mutex_unlock + return 1 + fi [[ -n "$MAC" ]] && UNMANAGED=$(echo $UNMANAGED | sed -e "s/mac:${MAC}\( \|$\)//g") UNMANAGED=$(echo $UNMANAGED | sed -e "s/interface-name:${1}\( \|$\)//g") @@ -418,13 +513,16 @@ networkmanager_rm_unmanaged() { fi ADDED_UNMANAGED="${ADDED_UNMANAGED/ ${1} /}" + mutex_unlock return 0 } networkmanager_fix_unmanaged() { [[ -f ${NETWORKMANAGER_CONF} ]] || return + mutex_lock sed -e "/^unmanaged-devices=.*/d" -i ${NETWORKMANAGER_CONF} + mutex_unlock } networkmanager_rm_unmanaged_if_needed() { @@ -482,8 +580,9 @@ HAVEGED_WATCHDOG_PID= _cleanup() { local PID x - trap "" SIGINT - trap "" SIGUSR1 + mutex_lock + + trap "" SIGINT SIGUSR1 SIGUSR2 # kill haveged_watchdog [[ -n "$HAVEGED_WATCHDOG_PID" ]] && kill $HAVEGED_WATCHDOG_PID @@ -569,6 +668,8 @@ _cleanup() { fi networkmanager_rm_unmanaged_if_needed ${WIFI_IFACE} ${OLD_MACADDR} fi + + mutex_unlock } cleanup() { @@ -580,15 +681,33 @@ cleanup() { die() { [[ -n "$1" ]] && echo -e "\nERROR: $1\n" >&2 - cleanup - exit 1 + # we cleanup and exit only if we are the main thread + if [[ $$ -eq $BASHPID ]]; then + cleanup + exit 1 + else + # send die signal to the main thread + kill -USR2 $$ + # terminate our thread + kill -9 $BASHPID + fi } clean_exit() { - cleanup - exit 0 + # we cleanup and exit only if we are the main thread + if [[ $$ -eq $BASHPID ]]; then + cleanup + exit 0 + else + # send clean_exit signal to the main thread + kill -USR1 $$ + # terminate our thread + kill -9 $BASHPID + fi } +# WARNING: never call mutex_lock in this function, otherwise we +# will have dead-lock when send_stop is called list_running() { local PID IFACE x for x in /tmp/create_ap.*; do @@ -610,9 +729,11 @@ is_running_pid() { send_stop() { local x + mutex_lock # send stop signal to specific pid if is_running_pid $1; then kill -USR1 $1 + mutex_unlock return fi @@ -620,12 +741,9 @@ send_stop() { for x in $(list_running | grep -E " ${1}\$" | cut -f1 -d' '); do kill -USR1 $x done + mutex_unlock } -# if the user press ctrl+c then execute die() -trap "clean_exit" SIGINT -trap "clean_exit" SIGUSR1 - ARGS=( "$@" ) GETOPT_ARGS=$(getopt -o hc:w:g:dnm: -l "help","hidden","ieee80211n","ht_capab:","driver:","no-virt","fix-unmanaged","country:","freq-band:","mac:","daemon","stop:","list","version","no-haveged" -n "$PROGNAME" -- "$@") [[ $? -ne 0 ]] && exit 1 @@ -740,8 +858,22 @@ if [[ $# -lt 1 && $FIX_UNMANAGED -eq 0 && -z "$STOP_ID" && $LIST_RUNNING -eq 0 exit 1 fi +# allocate lock for the main thread to avoid any failures later +if ! alloc_lock; then + echo "ERROR: Failed to allocate lock" >&2 + exit 1 +fi + +# if the user press ctrl+c or we get USR1 signal +# then run clean_exit() +trap "clean_exit" SIGINT SIGUSR1 +# if we get USR2 signal then run die(). +trap "die" SIGUSR2 + if [[ $LIST_RUNNING -eq 1 ]]; then + mutex_lock list_running + mutex_unlock exit 0 fi @@ -951,6 +1083,7 @@ if [[ $NO_VIRT -eq 1 && "$WIFI_IFACE" == "$INTERNET_IFACE" ]]; then exit 1 fi +mutex_lock CONFDIR=$(mktemp -d /tmp/create_ap.${WIFI_IFACE}.conf.XXXXXXXX) echo "Config dir: $CONFDIR" echo "PID: $$" @@ -960,6 +1093,7 @@ echo $$ > $CONFDIR/pid # permitions to $CONFDIR and $CONFDIR/pid chmod 755 $CONFDIR chmod 444 $CONFDIR/pid +mutex_unlock if [[ $NO_VIRT -eq 0 ]]; then VWIFI_IFACE=$(get_virt_iface_name) @@ -1198,10 +1332,6 @@ fi # start access point echo "hostapd command-line interface: hostapd_cli -p $CONFDIR/hostapd_ctrl" -# from now on we exit with 0 on SIGINT -trap "clean_exit" SIGINT -trap "clean_exit" SIGUSR1 - if [[ $NO_HAVEGED -eq 0 ]]; then haveged_watchdog & HAVEGED_WATCHDOG_PID=$!