diff --git a/Makefile b/Makefile index 5ad53c6..59b2fde 100644 --- a/Makefile +++ b/Makefile @@ -5,15 +5,26 @@ BINDIR=$(PREFIX)/bin all: @echo "Run 'make install' for installation." @echo "Run 'make uninstall' for uninstallation." + @echo "Run 'make reinstall' for installation without clobbering existing config file." -install: +install-noconf: install -Dm755 create_ap $(DESTDIR)$(BINDIR)/create_ap - install -Dm644 create_ap.conf $(DESTDIR)/etc/create_ap.conf [ ! -d /lib/systemd/system ] || install -Dm644 create_ap.service $(DESTDIR)$(PREFIX)/lib/systemd/system/create_ap.service [ ! -e /sbin/openrc-run ] || install -Dm755 create_ap.openrc $(DESTDIR)/etc/init.d/create_ap install -Dm644 bash_completion $(DESTDIR)$(PREFIX)/share/bash-completion/completions/create_ap install -Dm644 README.md $(DESTDIR)$(PREFIX)/share/doc/create_ap/README.md +install: install-noconf + [ -e $(DESTDIR)/etc/create_ap.conf ] && echo "Existing $(DESTDIR)/etc/create_ap.conf will be renamed with .orig suffix" >&2 + install -Dm644 --backup=existing --suffix=.orig create_ap.conf $(DESTDIR)/etc/create_ap.conf + +reinstall: install-noconf + if [ -e $(DESTDIR)/etc/create_ap.conf ]; then \ + echo "Leaving existing $(DESTDIR)/etc/create_ap.conf unchanged" >&2; \ + else \ + install -Dm644 --backup=existing --suffix=.orig create_ap.conf $(DESTDIR)/etc/create_ap.conf; \ + fi + uninstall: rm -f $(DESTDIR)$(BINDIR)/create_ap rm -f $(DESTDIR)/etc/create_ap.conf diff --git a/README.md b/README.md index c5b6e0f..4af6d41 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,11 @@ -## NOT MAINTAINED - -This project is no longer maintained. - -If you are still interested in this project, checkout the following fork that -also provides GUI: [lakinduakash/linux-wifi-hotspot] +This project is a fork of the no-longer-maintained [oblique/create_ap](//github.com/oblique/create_ap). ## Features * Create an AP (Access Point) at any channel. -* Choose one of the following encryptions: WPA, WPA2, WPA/WPA2, Open (no encryption). +* Choose one of the following encryptions: WPA, WPA2, WPA3, WPA/WPA2, WPA2/WPA3, Open (no encryption). * Hide your SSID. +* Enable [Wi-Fi Protected Setup](https://en.wikipedia.org/wiki/Wi-Fi_Protected_Setup) (WPS) with push-button or PIN methods. * Disable communication between clients (client isolation). * IEEE 802.11n & 802.11ac support * Internet sharing methods: NATed or Bridged or None (no Internet sharing). @@ -23,11 +19,9 @@ also provides GUI: [lakinduakash/linux-wifi-hotspot] * bash (to run this script) * util-linux (for getopt) * procps or procps-ng -* hostapd +* hostapd (v2.6 or newer) * iproute2 -* iw -* iwconfig (you only need this if 'iw' can not recognize your adapter) -* haveged (optional) +* iw (non-`nl80211` wifi drivers are no longer supported) ### For 'NATed' or 'None' Internet sharing method * dnsmasq @@ -52,8 +46,13 @@ also provides GUI: [lakinduakash/linux-wifi-hotspot] ### No passphrase (open network): create_ap wlan0 eth0 MyAccessPoint -### WPA + WPA2 passphrase: +### WPA2 + WPA3 passphrase: + create_ap wlan0 eth0 MyAccessPoint MyPassPhrase + +### WPS Push Button or PIN entry: create_ap wlan0 eth0 MyAccessPoint MyPassPhrase + create_ap --wps-pbc wlan0 # Device will have 2 minutes to connect + create_ap --wps-pin wlan0,12345678 # Enroll PIN 12345678 for device requesting it ### AP without Internet sharing: create_ap -n wlan0 MyAccessPoint MyPassPhrase @@ -67,13 +66,10 @@ also provides GUI: [lakinduakash/linux-wifi-hotspot] ### Internet sharing from the same WiFi interface: create_ap wlan0 wlan0 MyAccessPoint MyPassPhrase -### Choose a different WiFi adapter driver - create_ap --driver rtl871xdrv wlan0 eth0 MyAccessPoint MyPassPhrase - ### No passphrase (open network) using pipe: echo -e "MyAccessPoint" | create_ap wlan0 eth0 -### WPA + WPA2 passphrase using pipe: +### WPA2 + WPA3 passphrase using pipe: echo -e "MyAccessPoint\nMyPassPhrase" | create_ap wlan0 eth0 ### Enable IEEE 802.11n @@ -94,5 +90,3 @@ Using the persistent [systemd](https://wiki.archlinux.org/index.php/systemd#Basi ## License FreeBSD - -[lakinduakash/linux-wifi-hotspot]: https://github.com/lakinduakash/linux-wifi-hotspot diff --git a/bash_completion b/bash_completion index aae0bb2..78a2a15 100644 --- a/bash_completion +++ b/bash_completion @@ -115,9 +115,6 @@ _create_ap() { --no-virt) # No Options ;; - --no-haveged) - # No Options - ;; --fix-unmanaged) # No Options ;; diff --git a/create_ap b/create_ap index 07fa4f6..b81a12e 100755 --- a/create_ap +++ b/create_ap @@ -7,8 +7,6 @@ # 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 @@ -33,34 +31,43 @@ usage() { 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 " -w Use 1 for WPA, use 2 for WPA2, use 3 for WPA3, use 1+2+3 for all (default: 2+3)" + # --wep is intentionally hidden because it's obviously insecure 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 " --metered Indicate to clients that WiFi is metered and/or should be used sparingly." + echo " --wps Enable WPS (Wireless Protected Setup) with push button (PBC) and PIN modes" echo " --psk Use 64 hex digits pre-shared-key instead of passphrase" echo " --hidden Make the Access Point hidden (do not broadcast the SSID)" - echo " --mac-filter Enable MAC address filtering" - echo " --mac-filter-accept Location of MAC address filter list (defaults to /etc/hostapd/hostapd.accept)" - echo " --redirect-to-localhost If -n is set, redirect every web request to localhost (useful for public information networks)" + echo " --mac-filter-accept File containing MAC addresses to accept" + echo " --mac-filter-deny File containing MAC addresses to deny" + echo " --redirect-to-localhost If -n is set, redirect every DNS request to localhost (useful for public information networks)" echo " --hostapd-debug With level between 1 and 2, passes arguments -d or -dd to hostapd for debugging." + echo " --hostapd-timestamps Include timestamps in hostapd debug messages." echo " --isolate-clients Disable communication between clients" echo " --ieee80211n Enable IEEE 802.11n (HT)" echo " --ieee80211ac Enable IEEE 802.11ac (VHT)" echo " --ht_capab HT capabilities (default: [HT40+])" echo " --vht_capab VHT capabilities" - echo " --country Set two-letter country code for regularity (example: US)" + echo " --beacon-interval Set beacon interval in milliseconds (default 100)" + echo " --dtim-period Set DTIM period in numbers of beacons (default 2)" + echo " --country Set two-letter country code for 802.11d regulatory domain (example: US)" + echo " --timezone Advertise local timezone in beacons (following 802.11v-2011 7.3.2.87)" + echo " --gps Advertise location in beacons (coordinates are in 10^5 degree)" 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 " --dhcp-dns Set DNS returned by DHCP" + echo " --mac Set MAC address of WiFi interface" + echo " --dhcp-dns Set DNS returned by DHCP (default is gateway; see also --no-dns below)" + echo " Multiple servers may be specified (example: 'gateway,8.8.8.8')" + echo " --dhcp-dns6 Set DNSv6 returned by DHCP (default is gateway; see also --no-dns below)" + echo " Multiple servers may be specified (example: 'gateway,2001:4860:4860::8888')" echo " --daemon Run create_ap in the background" echo " --pidfile Save daemon PID to file" echo " --logfile Save daemon messages to file" @@ -72,22 +79,27 @@ usage() { echo " For an you can put the PID of create_ap or the WiFi interface." echo " If virtual WiFi interface was created, then use that one." echo " You can get them with --list-running" + echo " --wps-pbc Simulate WPS push button (PBC)" + echo " --wps-pin , Enroll PIN for WPS" echo " --mkconfig Store configs in conf_file" echo " --config Load configs from conf_file" echo - echo "Non-Bridging Options:" - echo " --no-dns Disable dnsmasq DNS server" - echo " --no-dnsmasq Disable dnsmasq server completely" - echo " -g IPv4 Gateway for the Access Point (default: 192.168.12.1)" - echo " -d DNS server will take into account /etc/hosts" - echo " -e DNS server will take into account additional hosts file" + echo "Non-bridging options:" + echo " --no-dns Disable dnsmasq DNS server (prevents clients from using Access Point for DNS)" + echo " --no-dnsmasq Disable dnsmasq completely (implies --no-dns and also disables DHCP)" + echo " -g IPv4 Gateway address for the Access Point (default: 192.168.12.1)" + echo " --ipv6 Enable IPv6 support" + echo " -d DNS server will include /etc/hosts in its results" + echo " -e DNS server will include additional hosts file in its results" 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 " * With bridge method, if the is not a bridge interface, then" echo " a bridge interface is created automatically." + echo " * With other methods, the default behavior is to run dnsmasq as a DNS and DHCP server." + echo " It will assign client addresses in the /24 address range matching the gateway." echo echo "Examples:" echo " "$PROGNAME" wlan0 eth0 MyAccessPoint MyPassPhrase" @@ -98,7 +110,6 @@ usage() { 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" } @@ -238,8 +249,6 @@ version_cmp() { return 0 } -USE_IWCONFIG=0 - is_interface() { [[ -z "$1" ]] && return 1 [[ -d "/sys/class/net/${1}" ]] @@ -247,10 +256,6 @@ is_interface() { 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 } @@ -260,22 +265,9 @@ is_bridge_interface() { } 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 + local PHY + PHY=$(readlink -f "/sys/class/net/$1/phy80211") + echo ${PHY##*/} } get_adapter_info() { @@ -291,26 +283,29 @@ get_adapter_kernel_module() { echo ${MODULE##*/} } +can_do_80211w() { + local PHY + PHY=$(get_phy_device "$1") + [[ $? -ne 0 ]] && return 1 + # Per https://kevinlocke.name/bits/2019/12/28/checking-802.11w-support, debugfs + # is the most reliable way to detect 802.11, aka management frame protection. + # Grep iw output as a fallback. + grep -q MFP_CAPABLE /sys/kernel/debug/ieee80211/${PHY}/hwflags && return 0 + get_adapter_info "$PHY" | grep -q 'CMAC (00-0f-ac:6)' +} + can_be_sta_and_ap() { - # iwconfig does not provide this information, assume false - [[ $USE_IWCONFIG -eq 1 ]] && return 1 - if [[ "$(get_adapter_kernel_module "$1")" == "brcmfmac" ]]; then - echo "WARN: brmfmac driver doesn't work properly with virtual interfaces and" >&2 - echo " it can cause kernel panic. For this reason we disallow virtual" >&2 - echo " interfaces for your adapter." >&2 - echo " For more info: https://github.com/oblique/create_ap/issues/203" >&2 - return 1 - fi 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_multi_channel() { + get_adapter_info "$1" | grep -E '#channels <= [2-9]+' > /dev/null 2>&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 + get_adapter_info "$1" | grep -E '\* AP$' > /dev/null 2>&1 } can_transmit_to_channel() { @@ -318,27 +313,22 @@ 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 + if [[ $FREQ_BAND == 2.4 ]]; then + CHANNEL_INFO=$(get_adapter_info ${IFACE} | grep " 24[0-9][0-9]\(\.0\+\)\? MHz \[${CHANNEL_NUM}\]") else - CHANNEL_NUM=$(printf '%02d' ${CHANNEL_NUM}) - CHANNEL_INFO=$(iwlist ${IFACE} channel | grep -E "Channel[[:blank:]]${CHANNEL_NUM}[[:blank:]]?:") - [[ -z "${CHANNEL_INFO}" ]] && return 1 - return 0 + CHANNEL_INFO=$(get_adapter_info ${IFACE} | grep " \(49[0-9][0-9]\|5[0-9]\{3\}\)\(\.0\+\)\? MHz \[${CHANNEL_NUM}\]") fi + [[ -z "${CHANNEL_INFO}" ]] && return 1 + [[ "${CHANNEL_INFO}" == *no\ IR* ]] && return 1 + [[ "${CHANNEL_INFO}" == *disabled* ]] && return 1 + return 0 } # taken from iw/util.c ieee80211_frequency_to_channel() { - local FREQ=$1 + local FREQ_MAYBE_FRACTIONAL=$1 + local FREQ=${FREQ_MAYBE_FRACTIONAL%.*} + if [[ $FREQ -eq 2484 ]]; then echo 14 elif [[ $FREQ -lt 2484 ]]; then @@ -355,16 +345,11 @@ ieee80211_frequency_to_channel() { } is_5ghz_frequency() { - [[ $1 =~ ^(49[0-9]{2})|(5[0-9]{3})$ ]] + [[ $1 =~ ^(49[0-9]{2})|(5[0-9]{3})(\.0+)?$ ]] } 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 + iw dev "$1" link 2>&1 | grep -E '^Connected to' > /dev/null 2>&1 } is_macaddr() { @@ -428,26 +413,26 @@ get_new_macaddr() { 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 -p $COMMON_CONFDIR/haveged.pid - fi +get_ipv6addr() { + ip -6 addr show "$1" scope global | sed -e 's/ /\n/g' | sed -ne '/inet6/{n;p}' | tail -n1 +} + +get_new_ipv6addr() { + local OLDIP NEWIP LAST_CHUNK i + OLDIP=$(get_ipv6addr "$1") + OLDIP="${OLDIP%/*}" + mutex_lock + for i in {1..65535}; do + if [[ "$OLDIP" == ::* ]]; then + NEWIP="${OLDIP}::$(printf %04x $i)" + else + LAST_CHUNK=$(printf %d 0x${OLDIP##*:}) + NEWIP="${OLDIP%:*}:$(printf %02x $(( ($LAST_CHUNK + $i) % 65536 )))" + ip -6 addr show "$1" scope global | sed -e 's/ /\n/g' | sed -ne '/inet6/{n;p}' | grep "$NEWIP" > /dev/null 2>&1 || break fi - mutex_unlock - sleep 2 done + mutex_unlock + echo $NEWIP } NETWORKMANAGER_CONF=/etc/NetworkManager/NetworkManager.conf @@ -626,40 +611,48 @@ networkmanager_wait_until_unmanaged() { CHANNEL=default GATEWAY=192.168.12.1 -WPA_VERSION=1+2 +WPA_VERSION=2+3 +WEP=0 ETC_HOSTS=0 ADDN_HOSTS= DHCP_DNS=gateway +DHCP_DNS6=gateway NO_DNS=0 NO_DNSMASQ=0 DNS_PORT= HIDDEN=0 MAC_FILTER=0 -MAC_FILTER_ACCEPT=/etc/hostapd/hostapd.accept +MAC_FILTER_ACCEPT= +MAC_FILTER_DENY= ISOLATE_CLIENTS=0 SHARE_METHOD=nat +METERED=0 IEEE80211N=0 IEEE80211AC=0 HT_CAPAB='[HT40+]' VHT_CAPAB= -DRIVER=nl80211 NO_VIRT=0 COUNTRY= +TIMEZONE= +GPS= FREQ_BAND=2.4 +BEACON_INTERVAL=100 +DTIM_PERIOD=2 NEW_MACADDR= DAEMONIZE=0 DAEMON_PIDFILE= DAEMON_LOGFILE=/dev/null -NO_HAVEGED=0 USE_PSK=0 +IPV6=0 +WPS=0 HOSTAPD_DEBUG_ARGS= REDIRECT_TO_LOCALHOST=0 -CONFIG_OPTS=(CHANNEL GATEWAY WPA_VERSION ETC_HOSTS DHCP_DNS NO_DNS NO_DNSMASQ HIDDEN MAC_FILTER MAC_FILTER_ACCEPT ISOLATE_CLIENTS - SHARE_METHOD IEEE80211N IEEE80211AC HT_CAPAB VHT_CAPAB DRIVER NO_VIRT COUNTRY FREQ_BAND - NEW_MACADDR DAEMONIZE DAEMON_PIDFILE DAEMON_LOGFILE NO_HAVEGED WIFI_IFACE INTERNET_IFACE - SSID PASSPHRASE USE_PSK) +CONFIG_OPTS=(CHANNEL GATEWAY WPA_VERSION WEP ETC_HOSTS DHCP_DNS DHCP_DNS6 NO_DNS NO_DNSMASQ HIDDEN MAC_FILTER MAC_FILTER_ACCEPT ISOLATE_CLIENTS + SHARE_METHOD IEEE80211N IEEE80211AC HT_CAPAB VHT_CAPAB NO_VIRT COUNTRY TIMEZONE GPS FREQ_BAND + NEW_MACADDR DAEMONIZE DAEMON_PIDFILE DAEMON_LOGFILE WIFI_IFACE INTERNET_IFACE + SSID PASSPHRASE USE_PSK BEACON_INTERVAL DTIM_PERIOD IPV6 ADDN_HOSTS WPS METERED WEP) FIX_UNMANAGED=0 LIST_RUNNING=0 @@ -678,8 +671,6 @@ OLD_MACADDR= IP_ADDRS= ROUTE_ADDRS= -HAVEGED_WATCHDOG_PID= - _cleanup() { local PID x @@ -687,9 +678,6 @@ _cleanup() { mutex_lock disown -a - # kill haveged_watchdog - [[ -n "$HAVEGED_WATCHDOG_PID" ]] && kill $HAVEGED_WATCHDOG_PID - # kill processes for x in $CONFDIR/*.pid; do # even if the $CONFDIR is empty, the for loop will assign @@ -697,6 +685,7 @@ _cleanup() { [[ -f $x ]] && kill -9 $(cat $x) done + rm -f "/lib/systemd/system-sleep/create_ap.${WIFI_IFACE}.resume" rm -rf $CONFDIR local found=0 @@ -707,10 +696,20 @@ _cleanup() { fi done + # if we are the last create_ap instance using this Internet-facing + # interface, then set back common values for that interface if [[ $found -eq 0 ]]; then cp -f $COMMON_CONFDIR/${INTERNET_IFACE}_forwarding \ /proc/sys/net/ipv4/conf/$INTERNET_IFACE/forwarding rm -f $COMMON_CONFDIR/${INTERNET_IFACE}_forwarding + + cp -f $COMMON_CONFDIR/ipv6_forwarding \ + /proc/sys/net/ipv6/conf/all/forwarding + rm -f $COMMON_CONFDIR/ipv6_forwarding + + cp -f $COMMON_CONFDIR/${INTERNET_IFACE}_ipv6_forwarding \ + /proc/sys/net/ipv6/conf/$INTERNET_IFACE/forwarding + rm -f $COMMON_CONFDIR/${INTERNET_IFACE}_ipv6_forwarding fi # if we are the last create_ap instance then set back the common values @@ -733,6 +732,12 @@ _cleanup() { fi rm -f $COMMON_CONFDIR/bridge-nf-call-iptables fi + if [[ -f $COMMON_CONFDIR/bridge-nf-call-ip6tables ]]; then + if [[ -e /proc/sys/net/bridge/bridge-nf-call-ip6tables ]]; then + cp -f $COMMON_CONFDIR/bridge-nf-call-ip6tables /proc/sys/net/bridge + fi + rm -f $COMMON_CONFDIR/bridge-nf-call-ip6tables + fi rm -rf $COMMON_CONFDIR fi @@ -742,6 +747,7 @@ _cleanup() { iptables -w -t nat -D POSTROUTING -s ${GATEWAY%.*}.0/24 ! -o ${WIFI_IFACE} -j MASQUERADE iptables -w -D FORWARD -i ${WIFI_IFACE} -s ${GATEWAY%.*}.0/24 -j ACCEPT iptables -w -D FORWARD -i ${INTERNET_IFACE} -d ${GATEWAY%.*}.0/24 -j ACCEPT + # FIXME: restore ip6tables? elif [[ "$SHARE_METHOD" == "bridge" ]]; then if ! is_bridge_interface $INTERNET_IFACE; then ip link set dev $BRIDGE_IFACE down @@ -789,8 +795,22 @@ _cleanup() { -p tcp -m tcp --dport 53 -j REDIRECT --to-ports $DNS_PORT iptables -w -t nat -D PREROUTING -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} \ -p udp -m udp --dport 53 -j REDIRECT --to-ports $DNS_PORT + if [[ "$IPV6" -ne 0 ]]; then + ip6tables -w -D INPUT -p tcp -m tcp --dport $DNS_PORT -j ACCEPT + ip6tables -w -D INPUT -p udp -m udp --dport $DNS_PORT -j ACCEPT + ip6tables -w -t nat -D PREROUTING -s ${GATEWAY6}/$PREFIXLEN6 -d ${GATEWAY6} \ + -p tcp -m tcp --dport 53 -j REDIRECT --to-ports $DNS_PORT + ip6tables -w -t nat -D PREROUTING -s ${GATEWAY6}/$PREFIXLEN6 -d ${GATEWAY6} \ + -p udp -m udp --dport 53 -j REDIRECT --to-ports $DNS_PORT + fi fi iptables -w -D INPUT -p udp -m udp --dport 67 -j ACCEPT + if [[ $IPV6 -ne 0 ]]; then + ip6tables -w -D INPUT -p udp -m udp --dport 67 -j ACCEPT + # Restore original subnet to internet-facing interface + ip -6 addr del "$INTERNET6"/128 dev ${INTERNET_IFACE} + ip -6 addr add "$INTERNET6"/"$PREFIXLEN6" dev ${INTERNET_IFACE} + fi fi if [[ $NO_VIRT -eq 0 ]]; then @@ -843,7 +863,7 @@ clean_exit() { list_running_conf() { local x mutex_lock - for x in /tmp/create_ap.*; do + for x in /run/create_ap.*; do if [[ -f $x/pid && -f $x/wifi_iface && -d /proc/$(cat $x/pid) ]]; then echo $x fi @@ -923,31 +943,61 @@ list_clients() { Use --list-running to find it out." [[ -z "$CONFDIR" ]] && CONFDIR=$(get_confdir_from_pid "$pid") - if [[ $USE_IWCONFIG -eq 0 ]]; then - local awk_cmd='($1 ~ /Station$/) {print $2}' - local client_list=$(iw dev "$wifi_iface" station dump | awk "$awk_cmd") + local awk_cmd='($1 ~ /Station$/) {print $2}' + local client_list=$(iw dev "$wifi_iface" station dump | awk "$awk_cmd") - if [[ -z "$client_list" ]]; then - echo "No clients connected" - return - fi + if [[ -z "$client_list" ]]; then + echo "No clients connected" + return + fi - printf "%-20s %-18s %s\n" "MAC" "IP" "Hostname" + printf "%-20s %-18s %s\n" "MAC" "IP" "Hostname" - local mac - for mac in $client_list; do - print_client $mac - done + local mac + for mac in $client_list; do + print_client $mac + done +} + +wps_trigger() { + local wifi_iface pid + + # If PID is given, get the associated wifi iface + if [[ "$1" =~ ^[1-9][0-9]*$ ]]; then + pid="$1" + wifi_iface=$(get_wifi_iface_from_pid "$pid") + [[ -z "$wifi_iface" ]] && die "'$pid' is not the pid of a running $PROGNAME instance." + fi + + [[ -z "$wifi_iface" ]] && wifi_iface="$1" + is_wifi_interface "$wifi_iface" || die "'$wifi_iface' is not a WiFi interface." + + [[ -z "$pid" ]] && pid=$(get_pid_from_wifi_iface "$wifi_iface") + [[ -z "$pid" ]] && die "'$wifi_iface' is not used from $PROGNAME instance.\n\ + Maybe you need to pass the virtual interface instead.\n\ + Use --list-running to find it out." + [[ -z "$CONFDIR" ]] && CONFDIR=$(get_confdir_from_pid "$pid") + + HOSTAPD_CLI=$(which hostapd_cli) + if ! $HOSTAPD_CLI -p $CONFDIR/hostapd_ctrl get_config | grep -q wps_state=configured; then + die "WPS is not configured. You need to run $PROGNAME with --wps, or add WPS=1 to config file." + fi + + if [[ "$2" = "PBC" ]]; then + echo "Triggering WPS pushbutton; your device will have 120 seconds to connect..." + $HOSTAPD_CLI -p $CONFDIR/hostapd_ctrl wps_pbc else - die "This option is not supported for the current driver." + echo "Enrolling WPS PIN '$2'..." + $HOSTAPD_CLI -p $CONFDIR/hostapd_ctrl wps_pin any "$2" fi + $HOSTAPD_CLI -p $CONFDIR/hostapd_ctrl wps_get_status } has_running_instance() { local PID x mutex_lock - for x in /tmp/create_ap.*; do + for x in /run/create_ap.*; do if [[ -f $x/pid ]]; then PID=$(cat $x/pid) if [[ -d /proc/$PID ]]; then @@ -1026,6 +1076,9 @@ read_config() { local opt_name opt_val line while read line; do + # Skip comments and blank lines + [[ -z "${line}" || "${line:0:1}" == "#" ]] && continue + # Read switches and their values opt_name="${line%%=*}" opt_val="${line#*=}" @@ -1054,7 +1107,7 @@ for ((i=0; i<$#; i++)); do fi done -GETOPT_ARGS=$(getopt -o hc:w:g:de:nm: -l "help","hidden","hostapd-debug:","redirect-to-localhost","mac-filter","mac-filter-accept:","isolate-clients","ieee80211n","ieee80211ac","ht_capab:","vht_capab:","driver:","no-virt","fix-unmanaged","country:","freq-band:","mac:","dhcp-dns:","daemon","pidfile:","logfile:","stop:","list","list-running","list-clients:","version","psk","no-haveged","no-dns","no-dnsmasq","mkconfig:","config:" -n "$PROGNAME" -- "$@") +GETOPT_ARGS=$(getopt -o hc:w:g:de:nm: -l "help","hidden","hostapd-debug:","hostapd-timestamps","redirect-to-localhost","mac-filter-accept:","mac-filter-deny:","isolate-clients","ieee80211n","ieee80211ac","ht_capab:","vht_capab:","no-virt","fix-unmanaged","country:","timezone","gps:","freq-band:","mac:","dhcp-dns:","dhcp-dns6:","daemon","pidfile:","logfile:","stop:","list","list-running","list-clients:","version","psk","no-dns","no-dnsmasq","ipv6","mkconfig:","config:","wps","wps-pbc:","wps-pin:","metered","wep" -n "$PROGNAME" -- "$@") [[ $? -ne 0 ]] && exit 1 eval set -- "$GETOPT_ARGS" @@ -1072,15 +1125,16 @@ while :; do shift HIDDEN=1 ;; - --mac-filter) - shift - MAC_FILTER=1 - ;; --mac-filter-accept) shift MAC_FILTER_ACCEPT="$1" shift ;; + --mac-filter-deny) + shift + MAC_FILTER_DENY="$1" + shift + ;; --isolate-clients) shift ISOLATE_CLIENTS=1 @@ -1093,14 +1147,23 @@ while :; do -w) shift WPA_VERSION="$1" - [[ "$WPA_VERSION" == "2+1" ]] && WPA_VERSION=1+2 shift ;; + --wep) + shift + WEP=1 + WPA_VERSION= + echo -e "WARN: WEP is insecure and obsolete and should only be used for testing or research" >&2 + ;; -g) shift GATEWAY="$1" shift ;; + --ipv6) + shift + IPV6=1 + ;; -d) shift ETC_HOSTS=1 @@ -1119,6 +1182,10 @@ while :; do SHARE_METHOD="$1" shift ;; + --metered) + shift + METERED=1 + ;; --ieee80211n) shift IEEE80211N=1 @@ -1137,11 +1204,6 @@ while :; do VHT_CAPAB="$1" shift ;; - --driver) - shift - DRIVER="$1" - shift - ;; --no-virt) shift NO_VIRT=1 @@ -1155,6 +1217,15 @@ while :; do COUNTRY="$1" shift ;; + --timezone) + shift + TIMEZONE=1 + ;; + --gps) + shift + GPS="$1" + shift + ;; --freq-band) shift FREQ_BAND="$1" @@ -1170,6 +1241,11 @@ while :; do DHCP_DNS="$1" shift ;; + --dhcp-dns6) + shift + DHCP_DNS6="$1" + shift + ;; --daemon) shift DAEMONIZE=1 @@ -1203,9 +1279,21 @@ while :; do LIST_CLIENTS_ID="$1" shift ;; - --no-haveged) + --wps-pbc) shift - NO_HAVEGED=1 + WPS_TRIGGER_ID="$1" + WPS_TRIGGER_PIN="PBC" + shift; + ;; + --wps-pin) + shift + WPS_TRIGGER_ID="${1/,*/}" + WPS_TRIGGER_PIN="${1/*,/}" + shift; + ;; + --wps) + shift + WPS=1 ;; --psk) shift @@ -1226,15 +1314,19 @@ while :; do --hostapd-debug) shift if [ "x$1" = "x1" ]; then - HOSTAPD_DEBUG_ARGS="-d" + HOSTAPD_DEBUG_ARGS+="-d " elif [ "x$1" = "x2" ]; then - HOSTAPD_DEBUG_ARGS="-dd" + HOSTAPD_DEBUG_ARGS+="-dd " else printf "Error: argument for --hostapd-debug expected 1 or 2, got %s\n" "$1" exit 1 fi shift ;; + --hostapd-timestamps) + shift + HOSTAPD_DEBUG_ARGS+="-t " + ;; --mkconfig) shift STORE_CONFIG="$1" @@ -1267,7 +1359,7 @@ fi # Check if required number of positional args are present if [[ $# -lt 1 && $FIX_UNMANAGED -eq 0 && -z "$STOP_ID" && - $LIST_RUNNING -eq 0 && -z "$LIST_CLIENTS_ID" ]]; then + $LIST_RUNNING -eq 0 && -z "$LIST_CLIENTS_ID" && -z "$WPS_TRIGGER_ID" ]]; then usage >&2 exit 1 fi @@ -1308,6 +1400,11 @@ if [[ $(id -u) -ne 0 ]]; then exit 1 fi +if [[ -n "$WPS_TRIGGER_ID" ]]; then + wps_trigger "$WPS_TRIGGER_ID" "$WPS_TRIGGER_PIN" + exit 0 +fi + if [[ -n "$STOP_ID" ]]; then echo "Trying to kill $PROGNAME instance associated with $STOP_ID..." send_stop "$STOP_ID" @@ -1373,6 +1470,16 @@ if ! can_be_sta_and_ap ${WIFI_IFACE}; then fi fi +if [[ "$WPA_VERSION" = *3* ]] && ! can_do_80211w ${WIFI_IFACE}; then + if [[ "$WPA_VERSION" = 3 ]]; then + echo "ERROR: Your adapter does not support protected management frames (802.11w), no WPA3" >&2 + exit 1 + else + echo "WARN: Your adapter does not support protected management frames (802.11w), disabling WPA3" >&2 + WPA_VERSION="${WPA_VERSION/3}" + fi +fi + HOSTAPD=$(which hostapd) if [[ ! -x "$HOSTAPD" ]]; then @@ -1380,16 +1487,31 @@ if [[ ! -x "$HOSTAPD" ]]; then exit 1 fi -if [[ $(get_adapter_kernel_module ${WIFI_IFACE}) =~ ^(8192[cd][ue]|8723a[sue])$ ]]; then - if ! strings "$HOSTAPD" | grep -m1 rtl871xdrv > /dev/null 2>&1; then - echo "ERROR: You need to patch your hostapd with rtl871xdrv patches." >&2 - exit 1 - fi +WIFI_KMOD="$(get_adapter_kernel_module ${WIFI_IFACE})" +if [[ "$WIFI_KMOD" =~ ^(8192[cd][ue]|8723a[sue])$ ]]; then + echo "WARNING: You are running a very old and buggy wifi driver. It may not work" >&2 + echo " at all. See https://github.com/dlenski/create_ap/commit/3460580c" >&2 + echo >&2 +elif [[ "$WIFI_KMOD" =~ ^rtl[0-9].*$ ]]; then + echo "WARNING: You are running a very old and buggy wifi driver. It may not work with" >&2 + echo " WPA properly. See https://github.com/dlenski/create_ap/commit/04fa4860" >&2 + echo >&2 +elif [[ "$WIFI_KMOD" = "rtl871xdrv" ]]; then + echo "WARNING: You are running an old and buggy wifi driver. It may not work with" >&2 + echo " with iw. See https://github.com/oblique/create_ap/issues/43" >&2 + echo >&2 +elif [[ "$WIFI_KMOD" = "brcmfmac" ]]; then + echo "WARNING: You are running an old and buggy wifi driver. It may not work with" >&2 + echo " virtual wifi devices. See https://github.com/oblique/create_ap/issues/203" >&2 + echo >&2 +fi - if [[ $DRIVER != "rtl871xdrv" ]]; then - echo "WARN: Your adapter needs rtl871xdrv, enabling --driver=rtl871xdrv" >&2 - DRIVER=rtl871xdrv - fi +HOSTAPD_VER=$($HOSTAPD -v 2>&1 | grep -m1 -oE '[0-9]+(\.[0-9]+)*\.[0-9]+') +version_cmp $HOSTAPD_VER 2.6 +if [[ $? -eq 1 ]]; then + echo "ERROR: You are running hostapd $HOSTAPD_VERSION, which has a broken" >&2 + echo " random number generator. Please upgrade to v2.6+" >&2 + exit 1 fi if [[ "$SHARE_METHOD" != "nat" && "$SHARE_METHOD" != "bridge" && "$SHARE_METHOD" != "none" ]]; then @@ -1505,14 +1627,6 @@ elif [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -ne 64 ]]; then 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 [[ $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 @@ -1521,28 +1635,24 @@ 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-running work from any user, we must give read -# permissions to $CONFDIR and $CONFDIR/pid -chmod 755 $CONFDIR -chmod 444 $CONFDIR/pid - -COMMON_CONFDIR=/tmp/create_ap.common.conf +COMMON_CONFDIR=/run/create_ap.common.conf mkdir -p $COMMON_CONFDIR if [[ "$SHARE_METHOD" == "nat" ]]; then - echo $INTERNET_IFACE > $CONFDIR/nat_internet_iface cp_n /proc/sys/net/ipv4/conf/$INTERNET_IFACE/forwarding \ $COMMON_CONFDIR/${INTERNET_IFACE}_forwarding + cp_n /proc/sys/net/ipv6/conf/all/forwarding \ + $COMMON_CONFDIR/ipv6_forwarding + cp_n /proc/sys/net/ipv6/conf/${INTERNET_IFACE}/forwarding \ + $COMMON_CONFDIR/${INTERNET_IFACE}_ipv6_forwarding fi cp_n /proc/sys/net/ipv4/ip_forward $COMMON_CONFDIR if [[ -e /proc/sys/net/bridge/bridge-nf-call-iptables ]]; then cp_n /proc/sys/net/bridge/bridge-nf-call-iptables $COMMON_CONFDIR fi +if [[ -e /proc/sys/net/bridge/bridge-nf-call-ip6tables ]]; then + cp_n /proc/sys/net/bridge/bridge-nf-call-ip6tables $COMMON_CONFDIR +fi mutex_unlock if [[ "$SHARE_METHOD" == "bridge" ]]; then @@ -1551,11 +1661,21 @@ if [[ "$SHARE_METHOD" == "bridge" ]]; then else BRIDGE_IFACE=$(alloc_new_iface br) fi +elif [[ $IPV6 -ne 0 ]]; then + echo "Looking up IPv6 address of internet interface..." + NETADDR6=$(get_ipv6addr $INTERNET_IFACE) + if [[ -n "$NETADDR6" ]]; then + INTERNET6="${NETADDR6%/*}" + PREFIXLEN6="${NETADDR6#*/}" + GATEWAY6=$(get_new_ipv6addr $INTERNET_IFACE) + echo "Internet interface is $INTERNET6/$PREFIXLEN6. Will route $GATEWAY6/$PREFIXLEN6 to wireless clients." + else + echo "No IPv6 address found. Disabling IPv6." + IPV6=0 + fi fi -if [[ $USE_IWCONFIG -eq 0 ]]; then - iw dev ${WIFI_IFACE} set power_save off -fi +iw dev ${WIFI_IFACE} set power_save off if [[ $NO_VIRT -eq 0 ]]; then VWIFI_IFACE=$(alloc_new_iface ap) @@ -1574,16 +1694,21 @@ if [[ $NO_VIRT -eq 0 ]]; 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 + if can_be_multi_channel ${WIFI_IFACE}; then + echo ", allowing because adapter supports multiple simultaneous channels" + echo "WARN: This may result in poor or unreliable performance." >&2 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 + 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 fi @@ -1609,11 +1734,21 @@ else fi mutex_lock +CONFDIR=/run/create_ap.${WIFI_IFACE}.conf +mkdir $CONFDIR || die "Config dir $CONFDIR already exists. Previous $PROGNAME instance may have crashed without cleanup!" +echo "Config dir: $CONFDIR" +echo "PID: $$" +echo $$ > $CONFDIR/pid echo $WIFI_IFACE > $CONFDIR/wifi_iface -chmod 444 $CONFDIR/wifi_iface +[[ "$SHARE_METHOD" == "nat" ]] && echo $INTERNET_IFACE > $CONFDIR/nat_internet_iface + +# to make --list-running work from any user, we must give read +# permissions to $CONFDIR, $CONFDIR/pid, and $CONFDIR/wifi_iface +chmod 755 $CONFDIR +chmod 444 $CONFDIR/pid $CONFDIR/wifi_iface mutex_unlock -if [[ -n "$COUNTRY" && $USE_IWCONFIG -eq 0 ]]; then +if [[ -n "$COUNTRY" ]]; then iw reg set "$COUNTRY" fi @@ -1632,22 +1767,79 @@ fi [[ $HIDDEN -eq 1 ]] && echo "Access Point's SSID is hidden!" -[[ $MAC_FILTER -eq 1 ]] && echo "MAC address filtering is enabled!" +[[ -n "$MAC_FILTER_ACCEPT" ]] && echo "MAC address allow-list is enabled! $MAC_FILTER_ACCEPT" +[[ -n "$MAC_FILTER_DENY" ]] && echo "MAC address deny-list is enabled! $MAC_FILTER_DENY" [[ $ISOLATE_CLIENTS -eq 1 ]] && echo "Access Point's clients will be isolated!" # hostapd config cat << EOF > $CONFDIR/hostapd.conf -beacon_int=100 ssid=${SSID} interface=${WIFI_IFACE} -driver=${DRIVER} +driver=nl80211 channel=${CHANNEL} ctrl_interface=$CONFDIR/hostapd_ctrl ctrl_interface_group=0 ignore_broadcast_ssid=$HIDDEN ap_isolate=$ISOLATE_CLIENTS +beacon_int=${BEACON_INTERVAL} +dtim_period=${DTIM_PERIOD} +#device_name=Fnord +#manufacturer=Vark +EOF + +# hostapd resume-after-suspend +if [[ -d /lib/systemd/system-sleep ]]; then + resumer="/lib/systemd/system-sleep/create_ap.${WIFI_IFACE}.resume" + cat << EOF > "$resumer" +#!/bin/sh +# After resuming, ensure hostapd is still sending beacons, and if it's not then +# disable and reenable to ensure AP continues working +if [ "\$1" = post ]; then + if [ OK != \`hostapd_cli -i "${WIFI_IFACE}" -p "$CONFDIR/hostapd_ctrl" update_beacon\` ]; then + hostapd_cli -i "${WIFI_IFACE}" -p "$CONFDIR/hostapd_ctrl" disable + hostapd_cli -i "${WIFI_IFACE}" -p "$CONFDIR/hostapd_ctrl" enable + fi +fi EOF + chmod 755 "$resumer" + echo "Installed systemd resume-after-suspend handler in $resumer" +fi + +VENDOR_ELEMENTS="" +if [[ $METERED -ne 0 ]]; then + # Add Microsoft's "network cost information element" + # (https://learn.microsoft.com/fi-fi/windows-hardware/drivers/mobilebroadband/network-cost-information-element) + # and Apple's vendor element that indicates a personal hotspot + # (https://apple.stackexchange.com/questions/199163/how-does-ios-and-os-x-detect-when-a-wi-fi-network-is-a-personal-hotspot) + # The Microsoft element seems to be quasi-standardized now. + VENDOR_ELEMENTS+="dd080050f21102000002" + + # We intentionally set the last byte (which is reserved and + # supposed to always be 00) to 02 in order to work around a bug caused by + # previously-incorrect documentation by Microsoft. See: + # https://openwrt.org/docs/guide-user/network/wifi/ms-meteredconnection#under_the_hood + VENDOR_ELEMENTS+="dd0a0017f206010103010000" +fi + +if [[ $WPS -eq 1 ]]; then + echo "Enabling WPS push button (PBC) and PIN modes!" + # WPS configuration: AP configured, do not allow external WPS Registrars, + # device type is "Network Infrastructure / AP" + cat << EOF >> $CONFDIR/hostapd.conf +eap_server=1 +wps_state=2 +ap_setup_locked=1 +device_type=6-0050F204-1 +config_methods=push_button virtual_push_button label +EOF + # Show SSID as device_name unless hidden + # (other potential descriptive options are manufacturer, model_name, + # model_number, serial_number, os_version) + if [[ $HIDDEN -ne 1 ]]; then + echo "device_name=${SSID}" >> $CONFDIR/hostapd.conf + fi +fi if [[ -n "$COUNTRY" ]]; then cat << EOF >> $CONFDIR/hostapd.conf @@ -1656,17 +1848,41 @@ ieee80211d=1 EOF fi +if [[ "$TIMEZONE" -ne 0 ]]; then + # FIXME: This capability was added to hostapd in + # https://chromium.googlesource.com/external/w1.fi/cgit/hostap/+/39b97072b2a45551e6f20e6251eeaca269f22a2d%5E%21/#F1 + # with the 'time_zone' variable, but appears to be broken in hostapd v2.9 + + # Per 802.11v-2011 7.3.2.87, the timezone is supposed to be specified via tag 98 (0x62) + # as a string in "POSIX format" (https://developer.ibm.com/articles/au-aix-posix/#understanding-the-posix-format3). + # Happily, the POSIX format timezone is easily extracted from the tzfile (https://stackoverflow.com/a/67045287), + # and /etc/localtime is a symlink to such a file. + POSIXTZ=$(tail -n1 /etc/localtime) + TZNAME=$(readlink -f /etc/localtime | sed -e 's|^.*/zoneinfo/||') + VENDOR_ELEMENTS+=$(printf "62%02x" ${#POSIXTZ})$(echo -n "$POSIXTZ" | xxd -p) + echo "Kludging timezone tag (802.11v-2011 7.3.2.87) for POSIX timezone '$POSIXTZ' ($TZNAME) into beacon via vendor_elements" >&2 + + # If/when 'time_zone' variable in hostapd.conf is fixed: + #echo "time_advertisement=2" >> $CONFDIR/hostapd.conf + #echo "time_zone=$POSIXTZ" >> $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 [[ $MAC_FILTER -eq 1 ]]; then +if [[ -n "$MAC_FILTER_ACCEPT" ]]; then cat << EOF >> $CONFDIR/hostapd.conf -macaddr_acl=${MAC_FILTER} +macaddr_acl=1 accept_mac_file=${MAC_FILTER_ACCEPT} EOF +elif [[ -n "$MAC_FILTER_DENY" ]]; then + cat << EOF >> $CONFDIR/hostapd.conf +macaddr_acl=0 +deny_mac_file=${MAC_FILTER_DENY} +EOF fi if [[ $IEEE80211N -eq 1 ]]; then @@ -1689,19 +1905,67 @@ if [[ $IEEE80211N -eq 1 ]] || [[ $IEEE80211AC -eq 1 ]]; then fi if [[ -n "$PASSPHRASE" ]]; then - [[ "$WPA_VERSION" == "1+2" ]] && WPA_VERSION=3 - if [[ $USE_PSK -eq 0 ]]; then - WPA_KEY_TYPE=passphrase + if [[ $WEP -eq 1 ]]; then + if [[ $USE_PSK -eq 1 ]]; then + psk="${PASSPHRASE}" + else + case ${#PASSPHRASE} in + 5) ;; 13) ;; 16) ;; + *) die "WEP password must be 5, 13, or 16 characters in length" ;; + esac + psk="\"${PASSPHRASE}\"" + fi + echo "wep_key0=${psk}" >> $CONFDIR/hostapd.conf else - WPA_KEY_TYPE=psk - fi - cat << EOF >> $CONFDIR/hostapd.conf -wpa=${WPA_VERSION} + if [[ $USE_PSK -eq 0 ]]; then + WPA_KEY_TYPE=passphrase + else + WPA_KEY_TYPE=psk + fi + case "$WPA_VERSION" in + 1) wpa=1; wpapw=TKIP ;; + *1*) wpa=3; wpapw="TKIP CCMP" ;; + *) wpa=2; wpapw=CCMP ;; + esac + case "$WPA_VERSION" in + 3) wpaw=2; wpakm="SAE" ;; + *3*) wpaw=1; wpakm="WPA-PSK WPA-PSK-SHA256 SAE" ;; + *) wpaw=0; wpakm="WPA-PSK WPA-PSK-SHA256" ;; + esac + cat <> $CONFDIR/hostapd.conf +wpa=$wpa wpa_${WPA_KEY_TYPE}=${PASSPHRASE} -wpa_key_mgmt=WPA-PSK -wpa_pairwise=TKIP CCMP +wpa_key_mgmt=$wpakm +wpa_pairwise=$wpapw rsn_pairwise=CCMP +ieee80211w=$wpaw EOF + fi +fi + + +if [[ -n "$GPS" ]]; then + # The format is defined in this French law requiring UAVs/drones to broadcast + # their GPS position and heading via WiFi beacons: + # https://www.legifrance.gouv.fr/eli/arrete/2019/12/27/ECOI1934044A/jo/texte + # Wireshark dissector clarifies some things: + # https://gitlab.com/wireshark/wireshark/commit/7ed3180 + # Other fields such as "takeoff" coords, takeoff-relative altitude, and heading seem pointless + # for a non-aircraft application. + + IFS=, read LAT LNG <<< "$GPS" + [[ $LAT -lt 0 ]] && LAT=$((0x100000000+$LAT)) + [[ $LNG -lt 0 ]] && LNG=$((0x100000000+$LNG)) + VENDOR_ELEMENTS+="dd136a5c3501" + VENDOR_ELEMENTS+="010101" # version (01) = 01 "sa valeur est fixée à 1" + VENDOR_ELEMENTS+=$(printf "0404%08x" $LAT) # current latitude (+N,-S) in 10^-5 degrees + VENDOR_ELEMENTS+=$(printf "0504%08x" $LNG) # current longitude (+W,-E) in 10^-5 degrees + #VENDOR_ELEMENTS+="06020000" # absolute altitude in m + #VENDOR_ELEMENTS+="0a0100" # horizontal speed in m/s +fi + +if [[ -n "$VENDOR_ELEMENTS" ]]; then + echo "vendor_elements=$VENDOR_ELEMENTS" >> $CONFDIR/hostapd.conf fi if [[ "$SHARE_METHOD" == "bridge" ]]; then @@ -1715,24 +1979,38 @@ elif [[ $NO_DNSMASQ -eq 0 ]]; then else DNSMASQ_BIND=bind-dynamic fi - if [[ "$DHCP_DNS" == "gateway" ]]; then - DHCP_DNS="$GATEWAY" - fi + DHCP_DNS="${DHCP_DNS/gateway/$GATEWAY}" + DHCP_DNS6="${DHCP_DNS6/gateway/[$GATEWAY6]}" cat << EOF > $CONFDIR/dnsmasq.conf -listen-address=${GATEWAY} +interface=${WIFI_IFACE} ${DNSMASQ_BIND} dhcp-range=${GATEWAY%.*}.1,${GATEWAY%.*}.254,255.255.255.0,24h dhcp-option-force=option:router,${GATEWAY} dhcp-option-force=option:dns-server,${DHCP_DNS} EOF + + # Android devices apparently detect metered networks using + # this DHCP vendor option, according to + # https://www.lorier.net/docs/android-metered.html + if [[ $METERED -ne 0 ]]; then + echo "dhcp-option-force=43,ANDROID_METERED" >> $CONFDIR/dnsmasq.conf + fi + + if [[ $IPV6 -ne 0 ]]; then + if [[ "$NO_DNS" -ne 0 ]]; then + RA_MODE="ra-names" + else + RA_MODE="ra-stateless" + fi + [[ -n "$DHCP_DNS6" ]] && echo "dhcp-option-force=option6:dns-server,${DHCP_DNS6}" >> $CONFDIR/dnsmasq.conf + echo "dhcp-range=::,constructor:${WIFI_IFACE}, $RA_MODE, slaac, 24h" >> $CONFDIR/dnsmasq.conf + fi MTU=$(get_mtu $INTERNET_IFACE) [[ -n "$MTU" ]] && echo "dhcp-option-force=option:mtu,${MTU}" >> $CONFDIR/dnsmasq.conf [[ $ETC_HOSTS -eq 0 ]] && echo no-hosts >> $CONFDIR/dnsmasq.conf [[ -n "$ADDN_HOSTS" ]] && echo "addn-hosts=${ADDN_HOSTS}" >> $CONFDIR/dnsmasq.conf if [[ "$SHARE_METHOD" == "none" && "$REDIRECT_TO_LOCALHOST" == "1" ]]; then - cat << EOF >> $CONFDIR/dnsmasq.conf -address=/#/$GATEWAY -EOF + echo "address=/#/$GATEWAY" >> $CONFDIR/dnsmasq.conf fi fi @@ -1751,6 +2029,16 @@ 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" + if [[ $IPV6 -ne 0 ]]; then + # Normally, the Internet-facing device has been assigned a /64 subnet (saved in INTERNET6/PREFIXLEN6). + # We want to keep only /128 for the Internet-facing device, but route the rest of the subnet to + # the AP, so that it can be used by clients via DHCPv6 or SLAAC. + + ip -6 addr del "$INTERNET6"/"$PREFIXLEN6" dev ${INTERNET_IFACE} || die "$VIRTDIEMSG" + ip -6 addr add "$INTERNET6"/128 dev ${INTERNET_IFACE} || die "$VIRTDIEMSG" + + ip -6 addr add "$GATEWAY6"/"$PREFIXLEN6" dev ${WIFI_IFACE} || die "$VIRTDIEMSG" + fi fi # enable Internet sharing @@ -1762,6 +2050,17 @@ if [[ "$SHARE_METHOD" != "none" ]]; then iptables -w -I FORWARD -i ${INTERNET_IFACE} -d ${GATEWAY%.*}.0/24 -j ACCEPT || die echo 1 > /proc/sys/net/ipv4/conf/$INTERNET_IFACE/forwarding || die echo 1 > /proc/sys/net/ipv4/ip_forward || die + if [[ $IPV6 -ne 0 ]]; then + # This enables forwarding of *all* IPv6 packets, which is handy for running an IPv6 + # server on a device inside the network without NAT... + ip6tables -P FORWARD ACCEPT || die + # ... but if you want something that prevents random incoming connections, try: + #ip6tables -P FORWARD DENY || die + #ip6tables -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT || die + #ip6tables -I FORWARD -i ${WIFI_IFACE} -j ACCEPT || die + echo 1 > /proc/sys/net/ipv6/conf/$INTERNET_IFACE/forwarding || die + echo 1 > /proc/sys/net/ipv6/conf/all/forwarding || die + fi # to enable clients to establish PPTP connections we must # load nf_nat_pptp module modprobe nf_nat_pptp > /dev/null 2>&1 @@ -1849,12 +2148,23 @@ if [[ "$SHARE_METHOD" != "bridge" ]]; then -p tcp -m tcp --dport 53 -j REDIRECT --to-ports $DNS_PORT || die iptables -w -t nat -I PREROUTING -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} \ -p udp -m udp --dport 53 -j REDIRECT --to-ports $DNS_PORT || die + if [[ $IPV6 -ne 0 ]]; then + ip6tables -w -I INPUT -p tcp -m tcp --dport $DNS_PORT -j ACCEPT || die + ip6tables -w -I INPUT -p udp -m udp --dport $DNS_PORT -j ACCEPT || die + ip6tables -w -t nat -I PREROUTING -s ${GATEWAY6}/$PREFIXLEN6 -d ${GATEWAY6} \ + -p tcp -m tcp --dport 53 -j REDIRECT --to-ports $DNS_PORT || die + ip6tables -w -t nat -I PREROUTING -s ${GATEWAY6}/$PREFIXLEN6 -d ${GATEWAY6} \ + -p udp -m udp --dport 53 -j REDIRECT --to-ports $DNS_PORT || die + fi else DNS_PORT=0 fi if [[ $NO_DNSMASQ -eq 0 ]]; then iptables -w -I INPUT -p udp -m udp --dport 67 -j ACCEPT || die + if [[ $IPV6 -ne 0 ]]; then + ip6tables -w -I INPUT -p udp -m udp --dport 67 -j ACCEPT || die + fi if which complain > /dev/null 2>&1; then # openSUSE's apparmor does not allow dnsmasq to read files. @@ -1868,14 +2178,35 @@ if [[ "$SHARE_METHOD" != "bridge" ]]; then fi fi -# start access point -echo "hostapd command-line interface: hostapd_cli -p $CONFDIR/hostapd_ctrl" +escape() { + local length="${#1}" + for (( i = 0; i < length; i++ )); do + local c="${1:i:1}" + case $c in + [\\\"\;\:\,]) echo -n "\\$c" ;; + *) echo -n "$c" ;; + esac + done +} -if [[ $NO_HAVEGED -eq 0 ]]; then - haveged_watchdog & - HAVEGED_WATCHDOG_PID=$! +QRENCODE_PATH=`which qrencode` +if [ -t 1 -a $? -eq 0 ]; then + URI="WIFI:S:$(escape $SSID);" + if [[ -n "$PASSPHRASE" ]]; then + URI+="T:WPA;P:$(escape $PASSPHRASE);" + fi + if [[ $HIDDEN -ne 0 ]]; then + URI+="H:true;" + fi + # If the AP is WPA3-only, should we add the transition-disable indicator? "R:1;" (https://superuser.com/a/1752085) + URI+=";" + echo "WiFi connection QR code:" + qrencode -t utf8 "$URI" >&2 fi +# start access point +echo "hostapd command-line interface: hostapd_cli -p $CONFDIR/hostapd_ctrl" + # start hostapd (use stdbuf when available for no delayed output in programs that redirect stdout) STDBUF_PATH=`which stdbuf` if [ $? -eq 0 ]; then @@ -1888,7 +2219,7 @@ 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 "If an error like 'nl80211: 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 diff --git a/create_ap.conf b/create_ap.conf index 9f55f09..df68241 100644 --- a/create_ap.conf +++ b/create_ap.conf @@ -1,28 +1,100 @@ -CHANNEL=default -GATEWAY=10.0.0.1 -WPA_VERSION=2 -ETC_HOSTS=0 -DHCP_DNS=gateway -NO_DNS=0 -NO_DNSMASQ=0 +# Configuration file for create_ap (https://github.com/oblique/create_ap) + +########## Access Point Information ########## + +# Name of access point +SSID=MyAccessPoint +# Pre-shared key or passphrase of access point (see USE_PSK option) +PASSPHRASE=12345678 +# Type of key in PASSPHRASE +# - Set to "0" if the content of PASSPHRASE is an 8..63 character long ASCII +# passphrase (i.e. what you type when you connect) +# - Set to "1" if the content of PASSPHRASE is a 256-bit secret in hex format +# (64 hex digits) +USE_PSK=0 +# Set whether the access point should hide its SSID or not HIDDEN=0 -MAC_FILTER=0 -MAC_FILTER_ACCEPT=/etc/hostapd/hostapd.accept -ISOLATE_CLIENTS=0 + +########## Sharing Options ########## + +# The network interface where create_ap creates access point +WIFI_IFACE=wlan0 +# The network interface to be shared +INTERNET_IFACE=eth0 +# Method to share the network +# Available options: "nat", "bridge" SHARE_METHOD=nat +# IP address of the gateway in NAT mode (the subnet is /24) +GATEWAY=10.0.0.1 +# Set whether IPv6 should be enabled +IPV6=0 +# Set whether the connected clients can see each other or not +ISOLATE_CLIENTS=0 +# Indicate to clients that WiFi is metered and/or should be used sparingly +METERED=0 + +########## Wireless Options ########## + +# The channel number on which the access point is served +# Set to "default" will set channel to 1 in 2.4Ghz band and 36 in 5GHz band +CHANNEL=default +# Wireless frequency (band) +# Available options: "2.4", "5" +FREQ_BAND=2.4 +# Country code (ISO/IEC 3166-1) to set regulatory domain +COUNTRY= +# Whether to advertise local timezone (802.11v-2011 7.3.2.87) +TIMEZONE=0 +# Advertise location in GPS coordinates, lat/lng in 10^-5 degrees +# (e.g. '4078628,-7396287' for Central Park in New York) +GPS= +# WPA-Personal version +# Available options: "1", "2", "3" +WPA_VERSION=2+3 +# Whether IEEE 802.11n (HT) is enabled IEEE80211N=0 +# Whether IEEE 802.11ac (VHT) is enabled IEEE80211AC=0 +# Whether Wi-Fi Protected Setup (WPS) is enabled +# Use '--wps-pbc' to simulate the push button, or '--wps-pin' for PIN entry. +WPS=0 +# HT / VHT capabilities +# For all possible options please look at +# https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf HT_CAPAB=[HT40+] VHT_CAPAB= -DRIVER=nl80211 -NO_VIRT=0 -COUNTRY= -FREQ_BAND=2.4 +# Beacon interval, and DTIM period (https://en.wikipedia.org/wiki/DTIM) +# in multiples of the beacon interval. +BEACON_INTERVAL=100 +DTIM_PERIOD=2 + +########## Network Options ########## + +# DNS servers to be pushed by DHCP server; separate multiple with , +# Include "gateway" to use the gateway itself +DHCP_DNS=gateway +# IPv6 DNS servers to be pushed by DHCP server; enclose IPv6 +# addresses in [], and separate multiple with , +# Include "gateway" to use the gateway itself +DHCP_DNS6= +# Set to 1 to disable DNS +NO_DNS=0 +# Set to 1 to disable dnsmasq completely (DHCP and DNS) +NO_DNSMASQ=0 +# Set to 1 to make dnsmasq DNS server include /etc/hosts in its results +ETC_HOSTS=0 +# Additional hosts file that dnsmasq should include in its results +ADDN_HOSTS= +# Set to 1 to enable MAC address based authentication +MAC_FILTER=0 +# Acceptance list for MAC address based authentication +MAC_FILTER_ACCEPT=/etc/hostapd/hostapd.accept +# New MAC address for the access point, leave it blank to keep it as-is NEW_MACADDR= + +########## Miscellaneous ########## + +# Set whether create_ap should run in the background DAEMONIZE=0 -NO_HAVEGED=0 -WIFI_IFACE=wlan0 -INTERNET_IFACE=eth0 -SSID=MyAccessPoint -PASSPHRASE=12345678 -USE_PSK=0 +# Set to 1 to disable virtual interface creation +NO_VIRT=0 diff --git a/howto/realtek.md b/howto/realtek.md deleted file mode 100644 index 266d818..0000000 --- a/howto/realtek.md +++ /dev/null @@ -1,74 +0,0 @@ -## Try this first - -If you are facing any problems with Realtek adapters (e.g. Edimax EW-7811Un) -first try to run create_ap with `-w 2` (i.e. use WPA2 only) or use it -without passphrase. If you are still facing any problems or you want to -also use WPA1, then follow the instructions below. - -NOTE: The instructions below are only valid for Realtek adapters with 8192 chipset. - -## Before installation - -If you're using ArchLinux, run: - -``` -pacman -S base-devel linux-headers dkms git -pacman -R hostapd -``` - -If you're using Debian, Ubuntu, or any Debian-based distribution, run: - -``` -apt-get install build-essential linux-headers-generic dkms git -apt-get remove hostapd -apt-get build-dep hostapd -``` - -## Install driver - -The driver in the mainline of Linux kernel doesn't work well with the 8192 adapters. -For this reason you need to install the driver that is provided from Realtek. Their -driver can not be compiled with newer kernels, but since it was an open-source -release under GPL license some people were able to fixed it and make it compile. - -With the following commands you can install a fixed version of Realtek's driver: - -``` -git clone https://github.com/pvaret/rtl8192cu-fixes.git -dkms add rtl8192cu-fixes -dkms install 8192cu/1.9 -cp rtl8192cu-fixes/blacklist-native-rtl8192.conf /etc/modprobe.d -cp rtl8192cu-fixes/8192cu-disable-power-management.conf /etc/modprobe.d -``` - -After installation, unload the previous driver and load the new one, or just reboot. - -## Install hostapd - -Realtek's driver is using an old subsystem which is called `wireless-extensions` -(or `wext`). Hostapd works only with the new subsystem (which is called `nl80211`). -For this reason Realtek wrote a patch for hostapd. You can install it with the -following commands: - -If you have ArchLinux install [hostapd-rtl871xdrv](https://aur.archlinux.org/packages/hostapd-rtl871xdrv) -from AUR or just run: - -``` -yaourt -S hostapd-rtl871xdrv -``` - -If you're using any other distribution, run: - -``` -git clone https://github.com/pritambaral/hostapd-rtl871xdrv.git -wget http://w1.fi/releases/hostapd-2.2.tar.gz -tar zxvf hostapd-2.2.tar.gz -cd hostapd-2.2 -patch -p1 -i ../hostapd-rtl871xdrv/rtlxdrv.patch -cp ../hostapd-rtl871xdrv/driver_* src/drivers -cd hostapd -cp defconfig .config -echo CONFIG_DRIVER_RTW=y >> .config -make -make install -```