#!/bin/bash # # Wifi Hotspot app for YunoHost # Copyright (C) 2015 Julien Vaubourg # Contribute at https://github.com/labriqueinternet/hotspot_ynh # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # Functions ## State functions has_vpnclient_app() { [ -e /tmp/.ynh-vpnclient-started ] } has_ip6delegatedprefix() { local i=${1} [[ -n "${ip6_net[${i}]}" ]] && [[ "${ip6_net[${i}]}" != "none" ]] } ip6addrfromdelegatedprefix() { local i=${1} echo "${ip6_net[${i}]}${i}001" } is_nat_set() { local gateway_interface=${1} iptables -w -nvt nat -L POSTROUTING | grep MASQUERADE | grep -q "${gateway_interface}" } is_ip4nataddr_set() { local i=${1} local dev=$(devfromid "${i}") ip address show dev "${dev}" 2>/dev/null | grep -q "${ip4_nat_prefix[${i}]}.1/24" } is_ip6addr_set() { local i=${1} local dev=$(devfromid "${i}") ip address show dev "${dev}" 2>/dev/null | grep -q "$(ip6addrfromdelegatedprefix $i)/64" } is_ip6firewall_set() { local i=${1} local dev=$(devfromid "${i}") ip6tables -w -nvL FORWARD | grep DROP | grep -q "${dev}" } is_forwarding_set() { local ip6=$(sysctl net.ipv6.conf.all.forwarding | awk '{ print $NF; }') local ip4=$(sysctl net.ipv4.conf.all.forwarding | awk '{ print $NF; }') [ "${ip6}" -eq 1 ] && [ "${ip4}" -eq 1 ] } is_dhcpd6_running() { local i=${1} ps aux | grep "dhcpdv6-ssid${i}" | grep -qv grep } is_dhcpd4_running() { local i=${1} ps aux | grep "dhcpdv4-ssid${i}" | grep -qv grep } is_hostapd_running() { systemctl is-active hostapd &>/dev/null } is_running() { for i in $(seq 0 $((${multissid} - 1))); do (has_ip6delegatedprefix ${i} && is_ip6addr_set ${i} \ && ([ "${ip6_firewall[${i}]}" -eq 1 ] && is_ip6firewall_set ${i} || [ "${ip6_firewall[${i}]}" -eq 0 ]) \ && is_dhcpd6_running ${i} || ! has_ip6delegatedprefix ${i}) \ && is_ip4nataddr_set ${i} && is_dhcpd4_running ${i} if [ ! $? -eq 0 ]; then return 1 fi done is_hostapd_running && is_forwarding_set && ([ -z "${new_gateway_interface}" ] || is_nat_set "${new_gateway_interface}") } ## Setters set_nat() { local gateway_interface=${1} iptables -w -t nat -A POSTROUTING -o "${gateway_interface}" -j MASQUERADE } set_ipaddr() { local i=${1} local dev=$(devfromid "${i}") if ! is_ip4nataddr_set ${i}; then echo "hotspot${i}: Set IPv4 NAT address" ip address add "${ip4_nat_prefix[${i}]}.1/24" dev "${dev}" fi if has_ip6delegatedprefix ${i} && ! is_ip6addr_set ${i}; then echo "hotspot${i}: Set IPv6 address" ip address delete "$(ip6addrfromdelegatedprefix $i)/64" dev tun0 &>/dev/null ip address add "$(ip6addrfromdelegatedprefix $i)/64" dev "${dev}" fi } set_ipfirewall() { local i=${1} local dev=$(devfromid "${i}") # Set ipv6 firewalling if has_ip6delegatedprefix ${i} && [ "${ip6_firewall[${i}]}" -eq 1 ] && ! is_ip6firewall_set ${i}; then echo "hotspot${i}: Set IPv6 firewalling" ip6tables -w -A FORWARD -i "${dev}" -j ACCEPT ip6tables -w -A FORWARD -o "${dev}" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -w -A FORWARD -o "${dev}" -j DROP fi } set_forwarding() { sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null sysctl -w net.ipv4.conf.all.forwarding=1 >/dev/null } start_dhcpd() { local i=${1} local dev=$(devfromid "${i}") # Run DHCPv4 server if ! is_dhcpd4_running ${i}; then echo "hotspot${i}: Start the DHCPv4 server (dnsmasq)" cp /etc/dnsmasq.dhcpd/dhcpdv4{.conf.tpl,-ssid${i}.conf} sed "s|__WIFI_DEVICE__|${dev}|g" -i /etc/dnsmasq.dhcpd/dhcpdv4-ssid${i}.conf sed "s|__IP4_DNS__|${ip4_dns[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv4-ssid${i}.conf sed "s|__IP4_NAT_PREFIX__|${ip4_nat_prefix[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv4-ssid${i}.conf dnsmasq -C /etc/dnsmasq.dhcpd/dhcpdv4-ssid${i}.conf -p0 -x /run/dnsmasq/dnsmasq-dhcpv4-ssid${i}.pid fi # Run DHCPv6 server if has_ip6delegatedprefix ${i} && ! is_dhcpd6_running ${i}; then echo "hotspot${i}: Start the NDP and DHCPv6 server (dnsmasq)" cp /etc/dnsmasq.dhcpd/dhcpdv6{.conf.tpl,-ssid${i}.conf} sed "s|__WIFI_DEVICE__|${dev}|g" -i /etc/dnsmasq.dhcpd/dhcpdv6-ssid${i}.conf sed "s|__IP6_DNS__|${ip6_dns[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv6-ssid${i}.conf sed "s|__IP6_NET__|${ip6_net[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv6-ssid${i}.conf dnsmasq -C /etc/dnsmasq.dhcpd/dhcpdv6-ssid${i}.conf -p0 -x /run/dnsmasq/dnsmasq-dhcpv6-ssid${i}.pid fi } configure_hostapd() { local ethaddr=$(ip link show dev "${wifi_device}" | grep link/ether | awk -F: '{ printf "02:%s:%s:%s:%s:00", $2, $3, $4, $5 }') ip link set addr "${ethaddr}" dev "${wifi_device}" cp /etc/hostapd/hostapd.base.conf /etc/hostapd/hostapd.conf sed "s|__WIFI_DEVICE__|${wifi_device}|g" -i /etc/hostapd/hostapd.conf sed "s|__WIFI_CHANNEL__|${wifi_channel}|g" -i /etc/hostapd/hostapd.conf sed "s|__N_COMMENT__||g" -i /etc/hostapd/hostapd.conf for i in $(seq 0 $((${multissid} - 1))); do [ "${wifi_secure[${i}]}" -eq 1 ] && local sec_comment="" || local sec_comment="#" [ "${i}" -eq 0 ] && local bss_comment="#" || local bss_comment="" cp /etc/hostapd/hostapd.accesspoint.conf /etc/hostapd/hostapd.conf.tmp sed "s|__WIFI_INTERFACE__|hotspot${i}|g" -i /etc/hostapd/hostapd.conf.tmp sed "s|__WIFI_SSID__|${wifi_ssid[${i}]}|g" -i /etc/hostapd/hostapd.conf.tmp sed "s|__WIFI_PASSPHRASE__|${wifi_passphrase[${i}]}|g" -i /etc/hostapd/hostapd.conf.tmp sed "s|__SEC_COMMENT__|${sec_comment}|g" -i /etc/hostapd/hostapd.conf.tmp sed "s|__BSS_COMMENT__|${bss_comment}|g" -i /etc/hostapd/hostapd.conf.tmp cat /etc/hostapd/hostapd.conf.tmp >>/etc/hostapd/hostapd.conf rm /etc/hostapd/hostapd.conf.tmp done } ## Unsetters unset_nat() { local gateway_interface=${1} iptables -w -t nat -D POSTROUTING -o "${gateway_interface}" -j MASQUERADE } unset_ipaddr() { local i=${1} local dev=$(devfromid "${i}") if is_ip4nataddr_set ${i}; then echo "hotspot${i}: Unset IPv4 NAT address" ip address delete "${ip4_nat_prefix[${i}]}.1/24" dev "${dev}" fi if has_ip6delegatedprefix ${i} && is_ip6addr_set ${i}; then echo "hotspot${i}: Unset IPv6 address" ip address delete "$(ip6addrfromdelegatedprefix $i)/64" dev "${dev}" fi } unset_ipfirewall() { local i=${1} local dev=$(devfromid "${i}") if has_ip6delegatedprefix ${i} && [ "${ip6_firewall[${i}]}" -eq 1 ] && is_ip6firewall_set ${i}; then echo "hotspot${i}: Unset IPv6 firewalling" ip6tables -w -D FORWARD -i "${dev}" -j ACCEPT ip6tables -w -D FORWARD -o "${dev}" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -w -D FORWARD -o "${dev}" -j DROP fi } unset_forwarding() { sysctl -w net.ipv6.conf.all.forwarding=0 >/dev/null sysctl -w net.ipv4.conf.all.forwarding=0 >/dev/null } stop_dhcpd() { local i=${1} if is_dhcpd6_running ${i}; then echo "hotspot${i}: Stop the NDP and DHCPv6 server (dnsmasq)" kill $(ps aux | grep 'dhcpdv6-ssid' | grep -v grep | awk '{ print $2 }') rm -f /etc/dnsmasq.dhcpd/dhcpdv6-ssid*.conf fi if is_dhcpd4_running ${i}; then echo "hotspot${i}: Stop the DHCPv4 server (dnsmasq)" kill $(ps aux | grep 'dhcpdv4-ssid' | grep -v grep | awk '{ print $2 }') rm -f /etc/dnsmasq.dhcpd/dhcpdv4-ssid*.conf fi } stop_dhcpd4() { : } stop_hostapd() { systemctl stop hostapd } ## Tools ynh_setting_get() { APP="$1" KEY="$2" python3 - </dev/null if [ "$?" -eq 0 ]; then new_gateway_interface=tun0 fi echo "OK" fi # Script case "$1" in start) if is_running; then echo "Already started" exit 0 elif [ "${service_enabled}" != "enabled" ]; then echo "Not starting because hotspod service is disabled" exit 1 fi if [ -z "${wifi_device}" ]; then echo "[FAIL] No wifi device selected. Make sure your wifi antenna is plugged-in / available and select it in the Hotspot admin" exit 1 fi echo "[hotspot] Starting..." touch /tmp/.ynh-hotspot-started # Check old state of the ipv4 NAT settings if [ -n "${old_gateway_interface}" ] && [ "${new_gateway_interface}" != "${old_gateway_interface}" ] && is_nat_set "${old_gateway_interface}"; then unset_nat "${old_gateway_interface}" fi # Set ipv4 NAT if [ -n "${new_gateway_interface}" ] && ! is_nat_set "${new_gateway_interface}"; then echo "Set NAT" set_nat "${new_gateway_interface}" fi # Set forwarding for ipv6 and ipv4 echo "Set forwarding" set_forwarding # Run hostapd if ! is_hostapd_running; then echo "Configuring hostapd" configure_hostapd echo "Starting hostapd..." if ! systemctl start hostapd; then journalctl -u hostapd -n 100 --no-hostname --no-pager exit 1 fi sleep 1 # On single SSID, the hotspot interface will be wlan0 (or similar) # in multissid, we additionally want to make sure that at least hotspot1 started if [ "${multissid}" -gt 1 ]; then i=0 while ! ip link show dev "hotspot1" &>/dev/null; do sleep 1 if [ ${i} -gt 20 ]; then echo "Failed to see hotspot interface showing up in 'ip a'" stop_hostapd exit 1 fi i=$(($i + 1)) done fi fi # For each registred ssid for i in $(seq 0 $((${multissid} - 1))); do set_ipaddr ${i} set_ipfirewall ${i} start_dhcpd ${i} done # Update dynamic settings ynh_setting_set hotspot gateway_interface "${new_gateway_interface}" ;; stop) echo "[hotspot] Stopping..." rm -f /tmp/.ynh-hotspot-started if [ -n "${old_gateway_interface}" ] && is_nat_set "${old_gateway_interface}"; then echo "Unset NAT" unset_nat "${old_gateway_interface}" fi echo "Unset forwarding" unset_forwarding for i in $(seq 0 $((${multissid} - 1))); do unset_ipaddr ${i} unset_ipfirewall ${i} stop_dhcpd ${i} done if is_hostapd_running; then echo "Stop hostapd" stop_hostapd fi # Fix configuration if has_vpnclient_app; then ynh-vpnclient start fi ;; restart) $0 stop $0 start ;; status) exitcode=0 if [ "${service_enabled}" != "enabled" ]; then echo "[FAIL] Hotspot Service disabled" exit 1 fi if [ -z "${wifi_device}" ]; then echo "[FAIL] No wifi device selected. Make sure your wifi antenna is plugged-in / available and select it in the Hotspot admin" exit 1 fi echo "[INFO] Autodetected internet interface: ${new_gateway_interface} (last start: ${old_gateway_interface})" if is_nat_set "${new_gateway_interface}"; then echo "[ OK ] IPv4 NAT set" else if [ -z "${new_gateway_interface}" ]; then echo "[INFO] No IPv4 NAT set (no internet interface)" else echo "[FAIL] No IPv4 NAT set" fi exitcode=1 fi if is_forwarding_set; then echo "[ OK ] IPv6/IPv4 forwarding set" else echo "[FAIL] No IPv6/IPv4 forwarding set" exitcode=1 fi if is_hostapd_running; then echo "[ OK ] Hostapd is running" else echo "[FAIL] Hostapd is not running" exitcode=1 fi for i in $(seq 0 $((${multissid} - 1))); do if has_ip6delegatedprefix ${i}; then echo "[INFO] hotspot${i}: IPv6 delegated prefix found" echo "[INFO] hotspot${i}: IPv6 address computed from the delegated prefix: $(ip6addrfromdelegatedprefix $i)" if is_ip6addr_set ${i}; then echo "[ OK ] hotspot${i}: IPv6 address set" else echo "[FAIL] hotspot${i}: No IPv6 address set" exitcode=1 fi if is_ip6firewall_set ${i}; then echo "[ OK ] hotspot${i}: IPv6 firewalling set" else if [ "${ip6_firewall[${i}]}" -eq 1 ]; then echo "[FAIL] hotspot${i}: No IPv6 firewalling set" else echo "[INFO] hotspot${i}: No IPv6 firewalling set" fi exitcode=1 fi if is_dhcpd6_running ${i}; then echo "[ OK ] hotspot${i}: NDP and DHCPv6 server (dnsmasq) are running" else echo "[FAIL] hotspot${i}: NDP and DHCPv6 server (dnsmasq) are not running" exitcode=1 fi else echo "[INFO] hotspot${i}: No IPv6 delegated prefix found" fi if is_dhcpd4_running ${i}; then echo "[ OK ] hotspot${i}: DHCPv4 server (dnsmasq) is running" else echo "[FAIL] hotspot${i}: DHCPv4 (dnsmasq) is not running" exitcode=1 fi if is_ip4nataddr_set ${i}; then echo "[ OK ] hotspot${i}: IPv4 NAT address set" else echo "[FAIL] hotspot${i}: No IPv4 NAT address set" exitcode=1 fi done exit ${exitcode} ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac exit 0