Files
create_ap/create_ap
oblique fee914c359 Remove mutex locking between subprocesses
Bash is not flexible and the recersive mutex lock that was implemented
in the previous commit is full of undefined behaviors if piping is
used. We don't actually need to lock between subprocesses so with
this commit we lock only between other create_ap process.
2015-05-01 21:14:54 +03:00

1384 lines
40 KiB
Bash
Executable File

#!/bin/bash
# general dependencies:
# bash (to run this script)
# util-linux (for getopt)
# procps or procps-ng
# hostapd
# iproute2
# iw
# iwconfig (you only need this if 'iw' can not recognize your adapter)
# haveged (optional)
# dependencies for 'nat' or 'none' Internet sharing method
# dnsmasq
# iptables
VERSION=0.1
PROGNAME="$(basename $0)"
# make sure that all command outputs are in english
# so we can parse them correctly
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.
SCRIPT_UMASK=0077
umask $SCRIPT_UMASK
usage() {
echo "Usage: "$PROGNAME" [options] <wifi-interface> [<interface-with-internet>] [<access-point-name> [<passphrase>]]"
echo
echo "Options:"
echo " -h, --help Show this help"
echo " --version Print version number"
echo " -c <channel> Channel number (default: 1)"
echo " -w <WPA version> Use 1 for WPA, use 2 for WPA2, use 1+2 for both (default: 1+2)"
echo " -n Disable Internet sharing (if you use this, don't pass"
echo " the <interface-with-internet> argument)"
echo " -m <method> Method for Internet sharing."
echo " Use: 'nat' for NAT (default)"
echo " 'bridge' for bridging"
echo " 'none' for no Internet sharing (equivalent to -n)"
echo " --hidden Make the Access Point hidden (do not broadcast the SSID)"
echo " --ieee80211n Enable IEEE 802.11n (HT)"
echo " --ht_capab <HT> HT capabilities (default: [HT40+])"
echo " --country <code> Set two-letter country code for regularity (example: US)"
echo " --freq-band <GHz> Set frequency band. Valid inputs: 2.4, 5 (default: 2.4)"
echo " --driver Choose your WiFi adapter driver (default: nl80211)"
echo " --no-virt Do not create virtual interface"
echo " --no-haveged Do not run \`haveged' automatically when needed"
echo " --fix-unmanaged If NetworkManager shows your interface as unmanaged after you"
echo " close create_ap, then use this option to switch your interface"
echo " back to managed"
echo " --mac <MAC> Set MAC address"
echo " --daemon Run create_ap in the background"
echo " --stop <id> Send stop command to an already running create_ap. For an <id>"
echo " you can put the PID of create_ap or the WiFi interface. You can"
echo " get them with --list"
echo " --list Show the create_ap processes that are already running"
echo
echo "Non-Bridging Options:"
echo " -g <gateway> IPv4 Gateway for the Access Point (default: 192.168.12.1)"
echo " -d DNS server will take into account /etc/hosts"
echo
echo "Useful informations:"
echo " * If you're not using the --no-virt option, then you can create an AP with the same"
echo " interface you are getting your Internet connection."
echo " * You can pass your SSID and password through pipe or through arguments (see examples)."
echo " * On bridge method if the <interface-with-internet> is not a bridge interface, then"
echo " a bridge interface is created automatically."
echo
echo "Examples:"
echo " "$PROGNAME" wlan0 eth0 MyAccessPoint MyPassPhrase"
echo " echo -e 'MyAccessPoint\nMyPassPhrase' | "$PROGNAME" wlan0 eth0"
echo " "$PROGNAME" wlan0 eth0 MyAccessPoint"
echo " echo 'MyAccessPoint' | "$PROGNAME" wlan0 eth0"
echo " "$PROGNAME" wlan0 wlan0 MyAccessPoint MyPassPhrase"
echo " "$PROGNAME" -n wlan0 MyAccessPoint MyPassPhrase"
echo " "$PROGNAME" -m bridge wlan0 eth0 MyAccessPoint MyPassPhrase"
echo " "$PROGNAME" -m bridge wlan0 br0 MyAccessPoint MyPassPhrase"
echo " "$PROGNAME" --driver rtl871xdrv wlan0 eth0 MyAccessPoint MyPassPhrase"
echo " "$PROGNAME" --daemon wlan0 eth0 MyAccessPoint MyPassPhrase"
echo " "$PROGNAME" --stop wlan0"
}
# on success it echos a non-zero unused FD
# on error it echos 0
get_avail_fd() {
local x
for x in $(seq 1 $(ulimit -n)); do
if [[ ! -a "/proc/$BASHPID/fd/$x" ]]; then
echo $x
return
fi
done
echo 0
}
# lock file for the mutex counter
COUNTER_LOCK_FILE=/tmp/create_ap.$$.lock
cleanup_lock() {
rm -f $COUNTER_LOCK_FILE
}
init_lock() {
local LOCK_FILE=/tmp/create_ap.all.lock
# we initialize only once
[[ $LOCK_FD -ne 0 ]] && return 0
LOCK_FD=$(get_avail_fd)
[[ $LOCK_FD -eq 0 ]] && return 1
# 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 "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
# create mutex counter lock file
echo 0 > $COUNTER_LOCK_FILE
return $?
}
# recursive mutex lock for all create_ap processes
mutex_lock() {
local counter_mutex_fd
local counter
# lock local mutex and read counter
counter_mutex_fd=$(get_avail_fd)
if [[ $counter_mutex_fd -ne 0 ]]; then
eval "exec $counter_mutex_fd<>$COUNTER_LOCK_FILE"
flock $counter_mutex_fd
read -u $counter_mutex_fd counter
else
echo "Failed to lock mutex counter" >&2
return 1
fi
# lock global mutex and increase counter
[[ $counter -eq 0 ]] && flock $LOCK_FD
counter=$(( $counter + 1 ))
# write counter and unlock local mutex
echo $counter > /proc/$BASHPID/fd/$counter_mutex_fd
eval "exec ${counter_mutex_fd}<&-"
return 0
}
# recursive mutex unlock for all create_ap processes
mutex_unlock() {
local counter_mutex_fd
local counter
# lock local mutex and read counter
counter_mutex_fd=$(get_avail_fd)
if [[ $counter_mutex_fd -ne 0 ]]; then
eval "exec $counter_mutex_fd<>$COUNTER_LOCK_FILE"
flock $counter_mutex_fd
read -u $counter_mutex_fd counter
else
echo "Failed to lock mutex counter" >&2
return 1
fi
# decrease counter and unlock global mutex
if [[ $counter -gt 0 ]]; then
counter=$(( $counter - 1 ))
[[ $counter -eq 0 ]] && flock -u $LOCK_FD
fi
# write counter and unlock local mutex
echo $counter > /proc/$BASHPID/fd/$counter_mutex_fd
eval "exec ${counter_mutex_fd}<&-"
return 0
}
# it takes 2 arguments
# returns:
# 0 if v1 (1st argument) and v2 (2nd argument) are the same
# 1 if v1 is less than v2
# 2 if v1 is greater than v2
version_cmp() {
local V1 V2 VN x
[[ ! $1 =~ ^[0-9]+(\.[0-9]+)*$ ]] && die "Wrong version format!"
[[ ! $2 =~ ^[0-9]+(\.[0-9]+)*$ ]] && die "Wrong version format!"
V1=( $(echo $1 | tr '.' ' ') )
V2=( $(echo $2 | tr '.' ' ') )
VN=${#V1[@]}
[[ $VN -lt ${#V2[@]} ]] && VN=${#V2[@]}
for ((x = 0; x < $VN; x++)); do
[[ ${V1[x]} -lt ${V2[x]} ]] && return 1
[[ ${V1[x]} -gt ${V2[x]} ]] && return 2
done
return 0
}
USE_IWCONFIG=0
is_interface() {
[[ -z "$1" ]] && return 1
[[ -d "/sys/class/net/${1}" ]]
}
is_wifi_interface() {
which iw > /dev/null 2>&1 && iw dev $1 info > /dev/null 2>&1 && return 0
if which iwconfig > /dev/null 2>&1 && iwconfig $1 > /dev/null 2>&1; then
USE_IWCONFIG=1
return 0
fi
return 1
}
is_bridge_interface() {
[[ -z "$1" ]] && return 1
[[ -d "/sys/class/net/${1}/bridge" ]]
}
get_phy_device() {
local x
for x in /sys/class/ieee80211/*; do
[[ ! -e "$x" ]] && continue
if [[ "${x##*/}" = "$1" ]]; then
echo $1
return 0
elif [[ -e "$x/device/net/$1" ]]; then
echo ${x##*/}
return 0
elif [[ -e "$x/device/net:$1" ]]; then
echo ${x##*/}
return 0
fi
done
echo "Failed to get phy interface" >&2
return 1
}
get_adapter_info() {
local PHY
PHY=$(get_phy_device "$1")
[[ $? -ne 0 ]] && return 1
iw phy $PHY info
}
get_adapter_kernel_module() {
local MODULE
MODULE=$(readlink -f "/sys/class/net/$1/device/driver/module")
echo ${MODULE##*/}
}
can_be_sta_and_ap() {
# iwconfig does not provide this information, assume false
[[ $USE_IWCONFIG -eq 1 ]] && return 1
get_adapter_info "$1" | grep -E '{.* managed.* AP.*}' > /dev/null 2>&1 && return 0
get_adapter_info "$1" | grep -E '{.* AP.* managed.*}' > /dev/null 2>&1 && return 0
return 1
}
can_be_ap() {
# iwconfig does not provide this information, assume true
[[ $USE_IWCONFIG -eq 1 ]] && return 0
get_adapter_info "$1" | grep -E '\* AP$' > /dev/null 2>&1 && return 0
return 1
}
can_transmit_to_channel() {
local IFACE CHANNEL_NUM CHANNEL_INFO
IFACE=$1
CHANNEL_NUM=$2
if [[ $USE_IWCONFIG -eq 0 ]]; then
if [[ $FREQ_BAND == 2.4 ]]; then
CHANNEL_INFO=$(get_adapter_info ${IFACE} | grep " 24[0-9][0-9] MHz \[${CHANNEL_NUM}\]")
else
CHANNEL_INFO=$(get_adapter_info ${IFACE} | grep " \(49[0-9][0-9]\|5[0-9]\{3\}\) MHz \[${CHANNEL_NUM}\]")
fi
[[ -z "${CHANNEL_INFO}" ]] && return 1
[[ "${CHANNEL_INFO}" == *no\ IR* ]] && return 1
[[ "${CHANNEL_INFO}" == *disabled* ]] && return 1
return 0
else
CHANNEL_NUM=$(printf '%02d' ${CHANNEL_NUM})
CHANNEL_INFO=$(iwlist ${IFACE} channel | grep "Channel ${CHANNEL_NUM} :")
[[ -z "${CHANNEL_INFO}" ]] && return 1
return 0
fi
}
# taken from iw/util.c
ieee80211_frequency_to_channel() {
local FREQ=$1
if [[ $FREQ -eq 2484 ]]; then
echo 14
elif [[ $FREQ -lt 2484 ]]; then
echo $(( ($FREQ - 2407) / 5 ))
elif [[ $FREQ -ge 4910 && $FREQ -le 4980 ]]; then
echo $(( ($FREQ - 4000) / 5 ))
elif [[ $FREQ -le 45000 ]]; then
echo $(( ($FREQ - 5000) / 5 ))
elif [[ $FREQ -ge 58320 && $FREQ -le 64800 ]]; then
echo $(( ($FREQ - 56160) / 2160 ))
else
echo 0
fi
}
is_5ghz_frequency() {
[[ $1 =~ ^(49[0-9]{2})|(5[0-9]{3})$ ]]
}
is_wifi_connected() {
if [[ $USE_IWCONFIG -eq 0 ]]; then
iw dev "$1" link 2>&1 | grep -E '^Connected to' > /dev/null 2>&1 && return 0
else
iwconfig "$1" 2>&1 | grep -E 'Access Point: [0-9a-fA-F]{2}:' > /dev/null 2>&1 && return 0
fi
return 1
}
is_macaddr() {
echo "$1" | grep -E "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" > /dev/null 2>&1
}
is_unicast_macaddr() {
local x
is_macaddr "$1" || return 1
x=$(echo "$1" | cut -d: -f1)
x=$(printf '%d' "0x${x}")
[[ $(expr $x % 2) -eq 0 ]]
}
get_macaddr() {
is_interface "$1" || return
cat "/sys/class/net/${1}/address"
}
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() {
cat /sys/class/net/*/address
}
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
}
# start haveged when needed
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
echo "WARN: Low entropy detected. We recommend you to install \`haveged'"
show_warn=0
fi
elif ! pidof haveged > /dev/null 2>&1; then
echo "Low entropy detected, starting haveged"
# boost low-entropy
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
}
NETWORKMANAGER_CONF=/etc/NetworkManager/NetworkManager.conf
NM_OLDER_VERSION=1
networkmanager_exists() {
local NM_VER
which nmcli > /dev/null 2>&1 || return 1
NM_VER=$(nmcli -v | grep -m1 -oE '[0-9]+(\.[0-9]+)*\.[0-9]+')
version_cmp $NM_VER 0.9.9
if [[ $? -eq 1 ]]; then
NM_OLDER_VERSION=1
else
NM_OLDER_VERSION=0
fi
return 0
}
networkmanager_is_running() {
local NMCLI_OUT
networkmanager_exists || return 1
if [[ $NM_OLDER_VERSION -eq 1 ]]; then
NMCLI_OUT=$(nmcli -t -f RUNNING nm)
else
NMCLI_OUT=$(nmcli -t -f RUNNING g)
fi
[[ "$NMCLI_OUT" == "running" ]]
}
networkmanager_iface_is_unmanaged() {
is_interface "$1" || return 2
(nmcli -t -f DEVICE,STATE d | grep -E "^$1:unmanaged$" > /dev/null 2>&1) || return 1
}
ADDED_UNMANAGED=
networkmanager_add_unmanaged() {
local MAC UNMANAGED WAS_EMPTY x
networkmanager_exists || return 1
[[ -d ${NETWORKMANAGER_CONF%/*} ]] || mkdir -p ${NETWORKMANAGER_CONF%/*}
[[ -f ${NETWORKMANAGER_CONF} ]] || touch ${NETWORKMANAGER_CONF}
if [[ $NM_OLDER_VERSION -eq 1 ]]; then
if [[ -z "$2" ]]; then
MAC=$(get_macaddr "$1")
else
MAC="$2"
fi
[[ -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
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
UNMANAGED="${UNMANAGED} mac:${MAC}"
else
UNMANAGED="${UNMANAGED} interface-name:${1}"
fi
UNMANAGED=$(echo $UNMANAGED | sed -e 's/^ //')
UNMANAGED="${UNMANAGED// /;}"
UNMANAGED="unmanaged-devices=${UNMANAGED}"
if ! grep -E '^\[keyfile\]' ${NETWORKMANAGER_CONF} > /dev/null 2>&1; then
echo -e "\n\n[keyfile]\n${UNMANAGED}" >> ${NETWORKMANAGER_CONF}
elif [[ $WAS_EMPTY -eq 1 ]]; then
sed -e "s/^\(\[keyfile\].*\)$/\1\n${UNMANAGED}/" -i ${NETWORKMANAGER_CONF}
else
sed -e "s/^unmanaged-devices=.*/${UNMANAGED}/" -i ${NETWORKMANAGER_CONF}
fi
ADDED_UNMANAGED="${ADDED_UNMANAGED} ${1} "
mutex_unlock
return 0
}
networkmanager_rm_unmanaged() {
local MAC UNMANAGED
networkmanager_exists || return 1
[[ ! -f ${NETWORKMANAGER_CONF} ]] && return 1
if [[ $NM_OLDER_VERSION -eq 1 ]]; then
if [[ -z "$2" ]]; then
MAC=$(get_macaddr "$1")
else
MAC="$2"
fi
[[ -z "$MAC" ]] && return 1
fi
mutex_lock
UNMANAGED=$(grep -m1 -Eo '^unmanaged-devices=[[:alnum:]:;,-]*' /etc/NetworkManager/NetworkManager.conf | sed 's/unmanaged-devices=//' | tr ';,' ' ')
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")
UNMANAGED=$(echo $UNMANAGED | sed -e 's/ $//')
if [[ -z "$UNMANAGED" ]]; then
sed -e "/^unmanaged-devices=.*/d" -i ${NETWORKMANAGER_CONF}
else
UNMANAGED="${UNMANAGED// /;}"
UNMANAGED="unmanaged-devices=${UNMANAGED}"
sed -e "s/^unmanaged-devices=.*/${UNMANAGED}/" -i ${NETWORKMANAGER_CONF}
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() {
[[ $ADDED_UNMANAGED =~ .*\ ${1}\ .* ]] && networkmanager_rm_unmanaged $1 $2
}
networkmanager_wait_until_unmanaged() {
local RES
networkmanager_is_running || return 1
while :; do
networkmanager_iface_is_unmanaged "$1"
RES=$?
[[ $RES -eq 0 ]] && break
[[ $RES -eq 2 ]] && die "Interface '${1}' does not exists.
It's probably renamed by a udev rule."
sleep 1
done
sleep 2
return 0
}
CHANNEL=default
GATEWAY=192.168.12.1
WPA_VERSION=1+2
ETC_HOSTS=0
HIDDEN=0
SHARE_METHOD=nat
IEEE80211N=0
HT_CAPAB='[HT40+]'
DRIVER=nl80211
NO_VIRT=0
FIX_UNMANAGED=0
COUNTRY=
FREQ_BAND=2.4
NEW_MACADDR=
DAEMONIZE=0
LIST_RUNNING=0
STOP_ID=
NO_HAVEGED=0
CONFDIR=
WIFI_IFACE=
VWIFI_IFACE=
INTERNET_IFACE=
BRIDGE_IFACE=
OLD_IP_FORWARD=
OLD_BRIDGE_IPTABLES=
OLD_MACADDR=
IP_ADDRS=
ROUTE_ADDRS=
HAVEGED_WATCHDOG_PID=
_cleanup() {
local PID x
mutex_lock
trap "" SIGINT SIGUSR1 SIGUSR2 EXIT
# kill haveged_watchdog
[[ -n "$HAVEGED_WATCHDOG_PID" ]] && kill $HAVEGED_WATCHDOG_PID
# exiting
for x in $CONFDIR/*.pid; do
# even if the $CONFDIR is empty, the for loop will assign
# a value in $x. so we need to check if the value is a file
if [[ -f $x ]]; then
PID=$(cat $x)
disown $PID
kill -9 $PID
fi
done
rm -rf $CONFDIR
if [[ "$SHARE_METHOD" != "none" ]]; then
if [[ "$SHARE_METHOD" == "nat" ]]; then
iptables -t nat -D POSTROUTING -o ${INTERNET_IFACE} -s ${GATEWAY%.*}.0/24 -j MASQUERADE
iptables -D FORWARD -i ${WIFI_IFACE} -s ${GATEWAY%.*}.0/24 -j ACCEPT
iptables -D FORWARD -i ${INTERNET_IFACE} -d ${GATEWAY%.*}.0/24 -j ACCEPT
[[ -n "$OLD_IP_FORWARD" ]] && echo $OLD_IP_FORWARD > /proc/sys/net/ipv4/ip_forward
elif [[ "$SHARE_METHOD" == "bridge" ]]; then
if [[ -n "$OLD_BRIDGE_IPTABLES" ]]; then
echo $OLD_BRIDGE_IPTABLES > /proc/sys/net/bridge/bridge-nf-call-iptables
fi
if ! is_bridge_interface $INTERNET_IFACE; then
ip link set dev $BRIDGE_IFACE down
ip link set dev $INTERNET_IFACE down
ip link set dev $INTERNET_IFACE promisc off
ip link set dev $INTERNET_IFACE nomaster
ip link delete $BRIDGE_IFACE type bridge
ip addr flush $INTERNET_IFACE
ip link set dev $INTERNET_IFACE up
for x in "${IP_ADDRS[@]}"; do
x="${x/inet/}"
x="${x/secondary/}"
x="${x/dynamic/}"
x=$(echo $x | sed 's/\([0-9]\)sec/\1/g')
x="${x/${INTERNET_IFACE}/}"
ip addr add $x dev $INTERNET_IFACE
done
ip route flush dev $INTERNET_IFACE
for x in "${ROUTE_ADDRS[@]}"; do
[[ -z "$x" ]] && continue
[[ "$x" == default* ]] && continue
ip route add $x dev $INTERNET_IFACE
done
for x in "${ROUTE_ADDRS[@]}"; do
[[ -z "$x" ]] && continue
[[ "$x" != default* ]] && continue
ip route add $x dev $INTERNET_IFACE
done
networkmanager_rm_unmanaged_if_needed $INTERNET_IFACE
fi
fi
fi
if [[ "$SHARE_METHOD" != "bridge" ]]; then
iptables -D INPUT -p tcp -m tcp --dport 53 -j ACCEPT
iptables -D INPUT -p udp -m udp --dport 53 -j ACCEPT
iptables -D INPUT -p udp -m udp --dport 67 -j ACCEPT
fi
if [[ $NO_VIRT -eq 0 ]]; then
if [[ -n "$VWIFI_IFACE" ]]; then
ip link set down dev ${VWIFI_IFACE}
ip addr flush ${VWIFI_IFACE}
networkmanager_rm_unmanaged_if_needed ${VWIFI_IFACE} ${OLD_MACADDR}
iw dev ${VWIFI_IFACE} del
fi
else
ip link set down dev ${WIFI_IFACE}
ip addr flush ${WIFI_IFACE}
if [[ -n "$NEW_MACADDR" ]]; then
ip link set dev ${WIFI_IFACE} address ${OLD_MACADDR}
fi
networkmanager_rm_unmanaged_if_needed ${WIFI_IFACE} ${OLD_MACADDR}
fi
mutex_unlock
cleanup_lock
}
cleanup() {
echo
echo -n "Doing cleanup.. "
_cleanup > /dev/null 2>&1
echo "done"
}
die() {
[[ -n "$1" ]] && echo -e "\nERROR: $1\n" >&2
# send die signal to the main process
[[ $BASHPID -ne $$ ]] && kill -USR2 $$
# we don't need to call cleanup because it's traped on EXIT
exit 1
}
clean_exit() {
# send clean_exit signal to the main process
[[ $BASHPID -ne $$ ]] && kill -USR1 $$
# we don't need to call cleanup because it's traped on EXIT
exit 0
}
list_running() {
local PID IFACE x
mutex_lock
for x in /tmp/create_ap.*; do
if [[ -f $x/pid ]]; then
PID=$(cat $x/pid)
if [[ -d /proc/$PID ]]; then
IFACE=${x#*.}
IFACE=${IFACE%%.*}
echo $PID $IFACE
fi
fi
done
mutex_unlock
}
is_running_pid() {
list_running | grep -E "^${1} " > /dev/null 2>&1
}
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
# send stop signal to specific interface
for x in $(list_running | grep -E " ${1}\$" | cut -f1 -d' '); do
kill -USR1 $x
done
mutex_unlock
}
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
eval set -- "$GETOPT_ARGS"
while :; do
case "$1" in
-h|--help)
usage
exit 0
;;
--version)
echo $VERSION
exit 0
;;
--hidden)
shift
HIDDEN=1
;;
-c)
shift
CHANNEL="$1"
shift
;;
-w)
shift
WPA_VERSION="$1"
[[ "$WPA_VERSION" == "2+1" ]] && WPA_VERSION=1+2
shift
;;
-g)
shift
GATEWAY="$1"
shift
;;
-d)
shift
ETC_HOSTS=1
;;
-n)
shift
SHARE_METHOD=none
;;
-m)
shift
SHARE_METHOD="$1"
shift
;;
--ieee80211n)
shift
IEEE80211N=1
;;
--ht_capab)
shift
HT_CAPAB="$1"
shift
;;
--driver)
shift
DRIVER="$1"
shift
;;
--no-virt)
shift
NO_VIRT=1
;;
--fix-unmanaged)
shift
FIX_UNMANAGED=1
;;
--country)
shift
COUNTRY="$1"
shift
;;
--freq-band)
shift
FREQ_BAND="$1"
shift
;;
--mac)
shift
NEW_MACADDR="$1"
shift
;;
--daemon)
shift
DAEMONIZE=1
;;
--stop)
shift
STOP_ID="$1"
shift
;;
--list)
shift
LIST_RUNNING=1
;;
--no-haveged)
shift
NO_HAVEGED=1
;;
--)
shift
break
;;
esac
done
if [[ $# -lt 1 && $FIX_UNMANAGED -eq 0 && -z "$STOP_ID" && $LIST_RUNNING -eq 0 ]]; then
usage >&2
exit 1
fi
trap "cleanup_lock" EXIT
if ! init_lock; then
echo "ERROR: Failed to initialize 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
list_running
exit 0
fi
if [[ $(id -u) -ne 0 ]]; then
echo "You must run it as root." >&2
exit 1
fi
if [[ -n "$STOP_ID" ]]; then
send_stop "$STOP_ID"
exit 0
fi
if [[ $FIX_UNMANAGED -eq 1 ]]; then
networkmanager_fix_unmanaged
exit 0
fi
if [[ $DAEMONIZE -eq 1 ]]; then
# remove --daemon
NEW_ARGS=( )
for x in "${ARGS[@]}"; do
[[ "$x" != "--daemon" ]] && NEW_ARGS+=( "$x" )
done
# run a detached create_ap
setsid "$0" "${NEW_ARGS[@]}" &
exit 0
fi
if [[ $FREQ_BAND != 2.4 && $FREQ_BAND != 5 ]]; then
echo "ERROR: Invalid frequency band" >&2
exit 1
fi
if [[ $CHANNEL == default ]]; then
if [[ $FREQ_BAND == 2.4 ]]; then
CHANNEL=1
else
CHANNEL=36
fi
fi
if [[ $FREQ_BAND != 5 && $CHANNEL -gt 14 ]]; then
echo "Channel number is greater than 14, assuming 5GHz frequency band"
FREQ_BAND=5
fi
WIFI_IFACE=$1
if ! is_wifi_interface ${WIFI_IFACE}; then
echo "ERROR: '${WIFI_IFACE}' is not a WiFi interface" >&2
exit 1
fi
if ! can_be_ap ${WIFI_IFACE}; then
echo "ERROR: Your adapter does not support AP (master) mode" >&2
exit 1
fi
if ! can_be_sta_and_ap ${WIFI_IFACE}; then
if is_wifi_connected ${WIFI_IFACE}; then
echo "ERROR: Your adapter can not be a station (i.e. be connected) and an AP at the same time" >&2
exit 1
elif [[ $NO_VIRT -eq 0 ]]; then
echo "WARN: Your adapter does not fully support AP virtual interface, enabling --no-virt" >&2
NO_VIRT=1
fi
fi
if [[ $(get_adapter_kernel_module ${WIFI_IFACE}) =~ ^(8192[cd][ue]|8723a[sue])$ ]]; then
if ! strings $(which hostapd) | grep -m1 rtl871xdrv > /dev/null 2>&1; then
echo "ERROR: You need to patch your hostapd with rtl871xdrv patches." >&2
exit 1
fi
if [[ $DRIVER != "rtl871xdrv" ]]; then
echo "WARN: Your adapter needs rtl871xdrv, enabling --driver=rtl871xdrv" >&2
DRIVER=rtl871xdrv
fi
fi
if [[ "$SHARE_METHOD" != "nat" && "$SHARE_METHOD" != "bridge" && "$SHARE_METHOD" != "none" ]]; then
echo "ERROR: Wrong Internet sharing method" >&2
echo
usage >&2
exit 1
fi
if [[ -n "$NEW_MACADDR" ]]; then
if ! is_macaddr "$NEW_MACADDR"; then
echo "ERROR: '${NEW_MACADDR}' is not a valid MAC address" >&2
exit 1
fi
if ! is_unicast_macaddr "$NEW_MACADDR"; then
echo "ERROR: The first byte of MAC address (${NEW_MACADDR}) must be even" >&2
exit 1
fi
if [[ $(get_all_macaddrs | grep -c ${NEW_MACADDR}) -ne 0 ]]; then
echo "WARN: MAC address '${NEW_MACADDR}' already exists. Because of this, you may encounter some problems" >&2
fi
fi
if [[ "$SHARE_METHOD" != "none" ]]; then
MIN_REQUIRED_ARGS=2
else
MIN_REQUIRED_ARGS=1
fi
if [[ $# -gt $MIN_REQUIRED_ARGS ]]; then
if [[ "$SHARE_METHOD" != "none" ]]; then
if [[ $# -ne 3 && $# -ne 4 ]]; then
usage >&2
exit 1
fi
INTERNET_IFACE=$2
SSID=$3
PASSPHRASE=$4
else
if [[ $# -ne 2 && $# -ne 3 ]]; then
usage >&2
exit 1
fi
SSID=$2
PASSPHRASE=$3
fi
else
if [[ "$SHARE_METHOD" != "none" ]]; then
if [[ $# -ne 2 ]]; then
usage >&2
exit 1
fi
INTERNET_IFACE=$2
fi
if tty -s; then
while :; do
read -p "SSID: " SSID
if [[ ${#SSID} -lt 1 || ${#SSID} -gt 32 ]]; then
echo "ERROR: Invalid SSID length ${#SSID} (expected 1..32)" >&2
continue
fi
break
done
while :; do
read -p "Passphrase: " -s PASSPHRASE
echo
if [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -lt 8 ]] || [[ ${#PASSPHRASE} -gt 63 ]]; then
echo "ERROR: Invalid passphrase length ${#PASSPHRASE} (expected 8..63)" >&2
continue
fi
read -p "Retype passphrase: " -s PASSPHRASE2
echo
if [[ "$PASSPHRASE" != "$PASSPHRASE2" ]]; then
echo "Passphrases do not match."
else
break
fi
done
else
read SSID
read PASSPHRASE
fi
fi
if [[ "$SHARE_METHOD" != "none" ]] && ! is_interface $INTERNET_IFACE; then
echo "ERROR: '${INTERNET_IFACE}' is not an interface" >&2
exit 1
fi
if [[ ${#SSID} -lt 1 || ${#SSID} -gt 32 ]]; then
echo "ERROR: Invalid SSID length ${#SSID} (expected 1..32)" >&2
exit 1
fi
if [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -lt 8 ]] || [[ ${#PASSPHRASE} -gt 63 ]]; then
echo "ERROR: Invalid passphrase length ${#PASSPHRASE} (expected 8..63)" >&2
exit 1
fi
if [[ $(get_adapter_kernel_module ${WIFI_IFACE}) =~ ^rtl[0-9].*$ ]]; then
if [[ -n "$PASSPHRASE" ]]; then
echo "WARN: Realtek drivers usually have problems with WPA1, enabling -w 2" >&2
WPA_VERSION=2
fi
echo "WARN: If AP doesn't work, please read: howto/realtek.md" >&2
fi
if [[ "$SHARE_METHOD" == "bridge" ]]; then
if [[ -e /proc/sys/net/bridge/bridge-nf-call-iptables ]]; then
OLD_BRIDGE_IPTABLES=$(cat /proc/sys/net/bridge/bridge-nf-call-iptables)
fi
if is_bridge_interface $INTERNET_IFACE; then
BRIDGE_IFACE=$INTERNET_IFACE
else
BRIDGE_IFACE=$(get_avail_bridge)
fi
elif [[ "$SHARE_METHOD" == "nat" ]]; then
OLD_IP_FORWARD=$(cat /proc/sys/net/ipv4/ip_forward)
fi
if [[ $NO_VIRT -eq 1 && "$WIFI_IFACE" == "$INTERNET_IFACE" ]]; then
echo -n "ERROR: You can not share your connection from the same" >&2
echo " interface if you are using --no-virt option." >&2
exit 1
fi
mutex_lock
trap "cleanup" EXIT
CONFDIR=$(mktemp -d /tmp/create_ap.${WIFI_IFACE}.conf.XXXXXXXX)
echo "Config dir: $CONFDIR"
echo "PID: $$"
echo $$ > $CONFDIR/pid
# to make --list work from any user, we must give read
# 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)
# in NetworkManager 0.9.9 and above we can set the interface as unmanaged without
# the need of MAC address, so we set it before we create the virtual interface.
if networkmanager_is_running && [[ $NM_OLDER_VERSION -eq 0 ]]; then
echo -n "Network Manager found, set ${VWIFI_IFACE} as unmanaged device... "
networkmanager_add_unmanaged ${VWIFI_IFACE}
# do not call networkmanager_wait_until_unmanaged because interface does not
# exist yet
echo "DONE"
fi
if is_wifi_connected ${WIFI_IFACE}; then
WIFI_IFACE_FREQ=$(iw dev ${WIFI_IFACE} link | grep -i freq | awk '{print $2}')
WIFI_IFACE_CHANNEL=$(ieee80211_frequency_to_channel ${WIFI_IFACE_FREQ})
echo -n "${WIFI_IFACE} is already associated with channel ${WIFI_IFACE_CHANNEL} (${WIFI_IFACE_FREQ} MHz)"
if is_5ghz_frequency $WIFI_IFACE_FREQ; then
FREQ_BAND=5
else
FREQ_BAND=2.4
fi
if [[ $WIFI_IFACE_CHANNEL -ne $CHANNEL ]]; then
echo ", fallback to channel ${WIFI_IFACE_CHANNEL}"
CHANNEL=$WIFI_IFACE_CHANNEL
else
echo
fi
fi
VIRTDIEMSG="Maybe your WiFi adapter does not fully support virtual interfaces.
Try again with --no-virt."
echo -n "Creating a virtual WiFi interface... "
if iw dev ${WIFI_IFACE} interface add ${VWIFI_IFACE} type managed; then
# now we can call networkmanager_wait_until_unmanaged
networkmanager_is_running && [[ $NM_OLDER_VERSION -eq 0 ]] && networkmanager_wait_until_unmanaged ${VWIFI_IFACE}
echo "${VWIFI_IFACE} created."
else
VWIFI_IFACE=
die "$VIRTDIEMSG"
fi
OLD_MACADDR=$(get_macaddr ${VWIFI_IFACE})
if [[ -z "$NEW_MACADDR" && $(get_all_macaddrs | grep -c ${OLD_MACADDR}) -ne 1 ]]; then
NEW_MACADDR=$(get_new_macaddr ${VWIFI_IFACE})
fi
WIFI_IFACE=${VWIFI_IFACE}
else
OLD_MACADDR=$(get_macaddr ${WIFI_IFACE})
fi
can_transmit_to_channel ${WIFI_IFACE} ${CHANNEL} || die "Your adapter can not transmit to channel ${CHANNEL}, frequency band ${FREQ_BAND}GHz."
if networkmanager_is_running && ! networkmanager_iface_is_unmanaged ${WIFI_IFACE}; then
echo -n "Network Manager found, set ${WIFI_IFACE} as unmanaged device... "
networkmanager_add_unmanaged ${WIFI_IFACE}
networkmanager_wait_until_unmanaged ${WIFI_IFACE}
echo "DONE"
fi
[[ $HIDDEN -eq 1 ]] && echo "Access Point's SSID is hidden!"
# hostapd config
cat << EOF > $CONFDIR/hostapd.conf
beacon_int=100
ssid=${SSID}
interface=${WIFI_IFACE}
driver=${DRIVER}
channel=${CHANNEL}
ctrl_interface=$CONFDIR/hostapd_ctrl
ctrl_interface_group=0
ignore_broadcast_ssid=$HIDDEN
EOF
if [[ -n $COUNTRY ]]; then
[[ $USE_IWCONFIG -eq 0 ]] && iw reg set $COUNTRY
echo "country_code=${COUNTRY}" >> $CONFDIR/hostapd.conf
fi
if [[ $FREQ_BAND == 2.4 ]]; then
echo "hw_mode=g" >> $CONFDIR/hostapd.conf
else
echo "hw_mode=a" >> $CONFDIR/hostapd.conf
fi
if [[ $IEEE80211N -eq 1 ]]; then
cat << EOF >> $CONFDIR/hostapd.conf
ieee80211n=1
wmm_enabled=1
ht_capab=${HT_CAPAB}
EOF
fi
if [[ -n "$PASSPHRASE" ]]; then
[[ "$WPA_VERSION" == "1+2" ]] && WPA_VERSION=3
cat << EOF >> $CONFDIR/hostapd.conf
wpa=${WPA_VERSION}
wpa_passphrase=$PASSPHRASE
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP CCMP
rsn_pairwise=CCMP
EOF
fi
if [[ "$SHARE_METHOD" == "bridge" ]]; then
echo "bridge=${BRIDGE_IFACE}" >> $CONFDIR/hostapd.conf
else
# dnsmasq config (dhcp + dns)
DNSMASQ_VER=$(dnsmasq -v | grep -m1 -oE '[0-9]+(\.[0-9]+)*\.[0-9]+')
version_cmp $DNSMASQ_VER 2.63
if [[ $? -eq 1 ]]; then
DNSMASQ_BIND=bind-interfaces
else
DNSMASQ_BIND=bind-dynamic
fi
cat << EOF > $CONFDIR/dnsmasq.conf
listen-address=${GATEWAY}
${DNSMASQ_BIND}
dhcp-range=${GATEWAY%.*}.1,${GATEWAY%.*}.254,255.255.255.0,24h
dhcp-option=option:router,${GATEWAY}
EOF
[[ $ETC_HOSTS -eq 0 ]] && echo no-hosts >> $CONFDIR/dnsmasq.conf
fi
# initialize WiFi interface
if [[ $NO_VIRT -eq 0 && -n "$NEW_MACADDR" ]]; then
ip link set dev ${WIFI_IFACE} address ${NEW_MACADDR} || die "$VIRTDIEMSG"
fi
ip link set down dev ${WIFI_IFACE} || die "$VIRTDIEMSG"
ip addr flush ${WIFI_IFACE} || die "$VIRTDIEMSG"
if [[ $NO_VIRT -eq 1 && -n "$NEW_MACADDR" ]]; then
ip link set dev ${WIFI_IFACE} address ${NEW_MACADDR} || die
fi
if [[ "$SHARE_METHOD" != "bridge" ]]; then
ip link set up dev ${WIFI_IFACE} || die "$VIRTDIEMSG"
ip addr add ${GATEWAY}/24 broadcast ${GATEWAY%.*}.255 dev ${WIFI_IFACE} || die "$VIRTDIEMSG"
fi
# enable Internet sharing
if [[ "$SHARE_METHOD" != "none" ]]; then
echo "Sharing Internet using method: $SHARE_METHOD"
if [[ "$SHARE_METHOD" == "nat" ]]; then
iptables -t nat -I POSTROUTING -o ${INTERNET_IFACE} -s ${GATEWAY%.*}.0/24 -j MASQUERADE || die
iptables -I FORWARD -i ${WIFI_IFACE} -s ${GATEWAY%.*}.0/24 -j ACCEPT || die
iptables -I FORWARD -i ${INTERNET_IFACE} -d ${GATEWAY%.*}.0/24 -j ACCEPT || die
echo 1 > /proc/sys/net/ipv4/ip_forward || die
# to enable clients to establish PPTP connections we must
# load nf_nat_pptp module
modprobe nf_nat_pptp > /dev/null 2>&1
elif [[ "$SHARE_METHOD" == "bridge" ]]; then
# disable iptables rules for bridged interfaces
if [[ -e /proc/sys/net/bridge/bridge-nf-call-iptables ]]; then
echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables
fi
# to initialize the bridge interface correctly we need to do the following:
#
# 1) save the IPs and route table of INTERNET_IFACE
# 2) if NetworkManager is running set INTERNET_IFACE as unmanaged
# 3) create BRIDGE_IFACE and attach INTERNET_IFACE to it
# 4) set the previously saved IPs and route table to BRIDGE_IFACE
#
# we need the above because BRIDGE_IFACE is the master interface from now on
# and it must know where is connected, otherwise connection is lost.
if ! is_bridge_interface $INTERNET_IFACE; then
echo -n "Create a bridge interface... "
OLD_IFS="$IFS"
IFS=$'\n'
IP_ADDRS=( $(ip addr show $INTERNET_IFACE | grep -A 1 -E 'inet[[:blank:]]' | paste - -) )
ROUTE_ADDRS=( $(ip route show dev $INTERNET_IFACE) )
IFS="$OLD_IFS"
if networkmanager_is_running; then
networkmanager_add_unmanaged $INTERNET_IFACE
networkmanager_wait_until_unmanaged $INTERNET_IFACE
fi
# create bridge interface
ip link add name $BRIDGE_IFACE type bridge || die
ip link set dev $BRIDGE_IFACE up || die
# set 0ms forward delay
echo 0 > /sys/class/net/$BRIDGE_IFACE/bridge/forward_delay
# attach internet interface to bridge interface
ip link set dev $INTERNET_IFACE promisc on || die
ip link set dev $INTERNET_IFACE up || die
ip link set dev $INTERNET_IFACE master $BRIDGE_IFACE || die
ip addr flush $INTERNET_IFACE
for x in "${IP_ADDRS[@]}"; do
x="${x/inet/}"
x="${x/secondary/}"
x="${x/dynamic/}"
x=$(echo $x | sed 's/\([0-9]\)sec/\1/g')
x="${x/${INTERNET_IFACE}/}"
ip addr add $x dev $BRIDGE_IFACE || die
done
# remove any existing entries that were added from 'ip addr add'
ip route flush dev $INTERNET_IFACE
ip route flush dev $BRIDGE_IFACE
# we must first add the entries that specify the subnets and then the
# gateway entry, otherwise 'ip addr add' will return an error
for x in "${ROUTE_ADDRS[@]}"; do
[[ "$x" == default* ]] && continue
ip route add $x dev $BRIDGE_IFACE || die
done
for x in "${ROUTE_ADDRS[@]}"; do
[[ "$x" != default* ]] && continue
ip route add $x dev $BRIDGE_IFACE || die
done
echo "$BRIDGE_IFACE created."
fi
fi
else
echo "No Internet sharing"
fi
# start dns + dhcp server
if [[ "$SHARE_METHOD" != "bridge" ]]; then
iptables -I INPUT -p tcp -m tcp --dport 53 -j ACCEPT || die
iptables -I INPUT -p udp -m udp --dport 53 -j ACCEPT || die
iptables -I INPUT -p udp -m udp --dport 67 -j ACCEPT || die
dnsmasq -C $CONFDIR/dnsmasq.conf -x $CONFDIR/dnsmasq.pid || die
fi
# start access point
echo "hostapd command-line interface: hostapd_cli -p $CONFDIR/hostapd_ctrl"
if [[ $NO_HAVEGED -eq 0 ]]; then
haveged_watchdog &
HAVEGED_WATCHDOG_PID=$!
fi
# start hostapd
hostapd $CONFDIR/hostapd.conf &
HOSTAPD_PID=$!
echo $HOSTAPD_PID > $CONFDIR/hostapd.pid
if ! wait $HOSTAPD_PID; then
echo -e "\nError: Failed to run hostapd, maybe a program is interfering." >&2
if networkmanager_is_running; then
echo "If an error like 'n80211: Could not configure driver mode' was thrown" >&2
echo "try running the following before starting create_ap:" >&2
if [[ $NM_OLDER_VERSION -eq 1 ]]; then
echo " nmcli nm wifi off" >&2
else
echo " nmcli r wifi off" >&2
fi
echo " rfkill unblock wlan" >&2
fi
die
fi
clean_exit
# Local Variables:
# tab-width: 4
# indent-tabs-mode: nil
# End: