#!/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 # make sure that all command outputs are in english # so we can parse them correctly export LC_ALL=C usage() { echo "Usage: $(basename $0) [options] [] [ []]" echo echo "Options:" echo " -h, --help Show this help" echo " --version Print version number" echo " -c Channel number (default: 1)" echo " -w 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 argument)" echo " -m 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 capabilities (default: [HT40+])" echo " --country Set two-letter country code for regularity (example: US)" echo " --freq-band 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 Set MAC address" echo " --daemon Run create_ap in the background" echo " --stop Send stop command to an already running create_ap. For an " 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 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 is not a bridge interface, then" echo " a bridge interface is created automatically." echo echo "Examples:" echo " $(basename $0) wlan0 eth0 MyAccessPoint MyPassPhrase" echo " echo -e 'MyAccessPoint\nMyPassPhrase' | $(basename $0) wlan0 eth0" echo " $(basename $0) wlan0 eth0 MyAccessPoint" echo " echo 'MyAccessPoint' | $(basename $0) wlan0 eth0" echo " $(basename $0) wlan0 wlan0 MyAccessPoint MyPassPhrase" echo " $(basename $0) -n wlan0 MyAccessPoint MyPassPhrase" echo " $(basename $0) -m bridge wlan0 eth0 MyAccessPoint MyPassPhrase" echo " $(basename $0) -m bridge wlan0 br0 MyAccessPoint MyPassPhrase" echo " $(basename $0) --driver rtl871xdrv wlan0 eth0 MyAccessPoint MyPassPhrase" echo " $(basename $0) --daemon wlan0 eth0 MyAccessPoint MyPassPhrase" echo " $(basename $0) --stop wlan0" } # 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() { [[ ! $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() { 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() { PHY=$(get_phy_device "$1") [[ $? -ne 0 ]] && return 1 iw phy $PHY info } get_adapter_kernel_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() { 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() { 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() { 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() { i=0 while :; do if ! is_interface br${i}; then echo br${i} return fi i=$((i + 1)) done } get_virt_iface_name() { i=0 while :; do if ! is_interface ap${i}; then echo ap${i} return fi i=$((i+1)) done } get_all_macaddrs() { cat /sys/class/net/*/address } get_new_macaddr() { OLDMAC=$(get_macaddr "$1") LAST_BYTE=$(printf %d 0x${OLDMAC##*:}) 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 echo $NEWMAC } # start haveged when needed haveged_watchdog() { local show_warn=0 while :; do if [[ $(cat /proc/sys/kernel/random/entropy_avail) -lt 1000 ]]; then if ! which haveged > /dev/null 2>&1; then if [[ $show_warn -eq 0 ]]; then echo "WARN: Low entropy detected. We recommend you to install \`haveged'" show_warn=1 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 wait $haveged_pid fi fi sleep 2 done } NETWORKMANAGER_CONF=/etc/NetworkManager/NetworkManager.conf NM_OLDER_VERSION=1 networkmanager_exists() { 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() { 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() { 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 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 ';,' ' ') for x in $UNMANAGED; do [[ $x == "mac:${MAC}" ]] && return 2 [[ $NM_OLDER_VERSION -eq 0 && $x == "interface-name:${1}" ]] && return 2 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} " return 0 } networkmanager_rm_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 UNMANAGED=$(grep -m1 -Eo '^unmanaged-devices=[[:alnum:]:;,-]*' /etc/NetworkManager/NetworkManager.conf | sed 's/unmanaged-devices=//' | tr ';,' ' ') [[ -z "$UNMANAGED" ]] && return 1 [[ -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} /}" return 0 } networkmanager_fix_unmanaged() { [[ -f ${NETWORKMANAGER_CONF} ]] || return sed -e "/^unmanaged-devices=.*/d" -i ${NETWORKMANAGER_CONF} } networkmanager_rm_unmanaged_if_needed() { [[ $ADDED_UNMANAGED =~ .*\ ${1}\ .* ]] && networkmanager_rm_unmanaged $1 $2 } networkmanager_wait_until_unmanaged() { 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() { trap "" SIGINT trap "" SIGUSR1 # 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 } cleanup() { echo echo -n "Doing cleanup.. " _cleanup > /dev/null 2>&1 echo "done" } die() { [[ -n "$1" ]] && echo -e "\nERROR: $1\n" >&2 cleanup exit 1 } clean_exit() { cleanup exit 0 } list_running() { 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 } is_running_pid() { list_running | grep -E "^${1} " > /dev/null 2>&1 } send_stop() { # send stop signal to specific pid if is_running_pid $1; then kill -USR1 $1 return fi # send stop signal to specific interface for x in $(list_running | grep -E " ${1}\$" | cut -f1 -d' '); do kill -USR1 $x done } # 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 $(basename $0) -- "$@") [[ $? -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 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 [[ $LIST_RUNNING -eq 1 ]]; then list_running 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 CONFDIR=$(mktemp -d /tmp/create_ap.${WIFI_IFACE}.conf.XXXXXXXX) echo "Config dir: $CONFDIR" echo "PID: $$" echo $$ > $CONFDIR/pid 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" # 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=$! 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: