]> git.ipfire.org Git - people/ms/network.git/blobdiff - src/functions/functions.hostapd
wireless-ap: Add support for WPA3 and rewrite WPA2
[people/ms/network.git] / src / functions / functions.hostapd
index f7bf2fa58bc343ec379a5acc3ecfe52e8a0d1fd7..6c2fbd9231c6a99b6bfc7e0e8103f8f9f55a1384 100644 (file)
 
 HOSTAPD_CONTROL_INTERFACE_DIR="/run/hostapd/ctrl"
 
+HOSTAPD_SUPPORTED_MODES="802.11a 802.11a/n 802.11ac 802.11g 802.11g/n"
+
+HOSTAPD_SUPPORTED_PAIRWISE_CIPHERS=(
+       "GCMP-256"      # Galois/counter mode protocol with 256 bit key
+       "CCMP-256"      # AES in Counter mode with CBC-MAC with 256 bit key
+       "GCMP-128"      # Galois/counter mode protocol with 128 bit key
+       "CCMP-128"      # AES in Counter mode with CBC-MAC with 128 bit key
+)
+
+# This must be supported by all stations on the network and therefore
+# can effectively only be CCMP
+HOSTAPD_SUPPORTED_GROUP_CIPHERS=(
+       "CCMP-128"
+)
+
 hostapd_config_write() {
        local device=${1}
        assert isset device
@@ -31,43 +46,66 @@ hostapd_config_write() {
        # Shift the device and file argument.
        shift 2
 
+       # Device must exist
+       if ! device_exists "${device}"; then
+               error "Cannot write hostapd configuration for non-existant device: ${device}"
+               return ${EXIT_ERROR}
+       fi
+
+       # Get the phy for device
+       local phy="$(device_get_phy "${device}")"
+       assert isset phy
+
        local broadcast_ssid
        local channel
+       local channel_bandwidth
        local country_code="$(wireless_get_reg_domain)"
-       local encryption
-       local ieee80211d="1"
-       local key
+       local dfs="on"
+       local environment="${WIRELESS_DEFAULT_ENVIRONMENT}"
+       local mfp="off"
        local mode
+       local secret
        local ssid
        local wmm="1"
+       local wpa2_personal="off"
+       local wpa3_personal="off"
 
        while [ $# -gt 0 ]; do
                case "${1}" in
                        --broadcast-ssid=*)
-                               broadcast_ssid=$(cli_get_val ${1})
+                               broadcast_ssid=$(cli_get_val "${1}")
                                ;;
                        --channel=*)
-                               channel=$(cli_get_val ${1})
+                               channel=$(cli_get_val "${1}")
+                               ;;
+                       --channel-bandwidth=*)
+                               channel_bandwidth="$(cli_get_val "${1}")"
+                               ;;
+                       --dfs=*)
+                               dfs="$(cli_get_val "${1}")"
                                ;;
                        --encryption=*)
-                               encryption=$(cli_get_val ${1})
+                               encryption=$(cli_get_val "${1}")
                                ;;
-                       --ieee80211d=*)
-                               local val="$(cli_get_val "${1}")"
-                               if enabled val; then
-                                       ieee80211d="1"
-                               else
-                                       ieee80211d="0"
-                               fi
+                       --environment=*)
+                               environment="$(cli_get_val "${1}")"
                                ;;
-                       --key=*)
-                               key=$(cli_get_val ${1})
+                       --mfp=*)
+                               mfp="$(cli_get_val "${1}")"
                                ;;
                        --mode=*)
-                               mode=$(cli_get_val ${1})
+                               mode=$(cli_get_val "${1}")
+
+                               if ! isoneof mode ${HOSTAPD_SUPPORTED_MODES}; then
+                                       error "Unsupported mode: ${mode}"
+                                       return ${EXIT_ERROR}
+                               fi
+                               ;;
+                       --secret=*)
+                               secret="$(cli_get_val "${1}")"
                                ;;
                        --ssid=*)
-                               ssid=$(cli_get_val ${1})
+                               ssid=$(cli_get_val "${1}")
                                ;;
                        --wmm=*)
                                local val="$(cli_get_val "${1}")"
@@ -77,6 +115,12 @@ hostapd_config_write() {
                                        wmm="0"
                                fi
                                ;;
+                       --wpa2-personal=*)
+                               wpa2_personal="$(cli_get_bool "${1}")"
+                               ;;
+                       --wpa3-personal=*)
+                               wpa3_personal="$(cli_get_bool "${1}")"
+                               ;;
                        *)
                                warning_log "Ignoring unknown argument '${1}'."
                                ;;                      
@@ -84,6 +128,12 @@ hostapd_config_write() {
                shift
        done
 
+       # Check if mode is set
+       if ! isset mode; then
+               error "Mode is not set"
+               return ${EXIT_ERROR}
+       fi
+
        assert isset broadcast_ssid
        assert isbool broadcast_ssid
 
@@ -93,12 +143,113 @@ hostapd_config_write() {
        assert isset mode
        assert isset ssid
 
-       # Check if key is set when encryption is used.
-       if isset encryption; then
-               assert isoneof encryption WPA WPA2 WPA/WPA2
-               assert isset key
+       # Check wireless environment
+       if ! wireless_environment_is_valid "${environment}"; then
+               error "Invalid wireless environment: ${environment}"
+               return ${EXIT_ERROR}
+       fi
+
+       # With channel 0, ACS must be supported
+       if [ ${channel} -eq 0 ] && ! wireless_supports_acs "${device}"; then
+               error "ACS requested, but not supported by ${device}"
+               return ${EXIT_ERROR}
        fi
 
+       # Check channel bandwidth for validity
+       if isset channel_bandwidth && ! wireless_channel_bandwidth_is_valid "${mode}" "${channel_bandwidth}"; then
+               error "Invalid channel bandwidth for ${mode}: ${channel_bandwidth}"
+               return ${EXIT_ERROR}
+       fi
+
+       # Management Frame Proection
+       if ! isbool mfp; then
+               error "Invalid value for --mfp: ${mfp}"
+               return ${EXIT_ERROR}
+       fi
+
+       # Check if secret is set for personal authentication
+       if ! isset secret && (enabled WPA3_PERSONAL || enabled WPA2_PERSONAL); then
+               error "Secret not set but personal authentication enabled"
+               return ${EXIT_ERROR}
+       fi
+
+       # 802.11ac/n flags
+       local ieee80211ac
+       local ieee80211n
+       local vht_caps
+       local vht_oper_chwidth="0"
+       local ht_caps
+
+       local hw_mode
+       case "${mode}" in
+               802.11a)
+                       hw_mode="a"
+                       ;;
+
+               802.11a/n)
+                       hw_mode="a"
+                       ieee80211n="1"
+
+                       # Fetch HT caps
+                       ht_caps="$(wireless_get_ht_caps "${device}")"
+                       ;;
+
+               802.11g)
+                       hw_mode="g"
+                       ;;
+
+               802.11g/n)
+                       hw_mode="g"
+                       ieee80211n="1"
+
+                       # Fetch HT caps
+                       ht_caps="$(wireless_get_ht_caps "${device}")"
+                       ;;
+
+               802.11ac)
+                       hw_mode="a"
+                       ieee80211ac="1"
+                       ieee80211n="1"
+
+                       # Fetch VHT caps
+                       vht_caps="$(wireless_get_vht_caps "${device}")"
+
+                       # Fetch HT caps
+                       ht_caps="$(wireless_get_ht_caps "${device}")"
+
+                       case "${channel_bandwidth}" in
+                               80)
+                                       vht_oper_chwidth="1"
+                                       ;;
+                               160)
+                                       vht_oper_chwidth="2"
+                                       ;;
+                               80+80)
+                                       vht_oper_chwidth="3"
+                                       ;;
+                       esac
+                       ;;
+       esac
+
+       # Cryptography
+       local cipher
+
+       # Get all supported pairwise ciphers
+       local pairwise_ciphers=()
+       for cipher in ${HOSTAPD_SUPPORTED_PAIRWISE_CIPHERS[*]}; do
+               if phy_supports_cipher "${phy}" "${cipher}"; then
+                       pairwise_ciphers+=( "$(hostapd_cipher_name "${cipher}")" )
+               fi
+       done
+
+       # Get all supported group ciphers
+       local group_ciphers=()
+       for cipher in ${HOSTAPD_SUPPORTED_GROUP_CIPHERS[*]}; do
+               if phy_supports_cipher "${phy}" "${cipher}"; then
+                       group_ciphers+=( "$(hostapd_cipher_name "${cipher}")" )
+               fi
+       done
+
        # Create configuration directory.
        local config_dir=$(dirname ${file})
        mkdir -p ${HOSTAPD_CONTROL_INTERFACE_DIR} ${config_dir} 2>/dev/null
@@ -121,37 +272,125 @@ hostapd_config_write() {
                ignore_broadcast_ssid="1"
        fi
 
-       local hw_mode ieee80211n="0"
-       if [ "${mode}" = "n" ]; then
-               if [ ${channel} -le 15 ]; then
-                       hw_mode="g"
+       (
+               # Advertise country code and maximum transmission power
+               print "ieee80211d=1"
+               print "country_code=${country_code}"
+
+               # Wireless Environment
+               case "${environment}" in
+                       indoor)
+                               print "country3=0x49"
+                                      country3
+                               ;;
+                       outdoor)
+                               print "country3=0x4f"
+                               ;;
+                       indoor+outdoor)
+                               print "country3=0x20"
+                               ;;
+               esac
+
+               # Always advertise TPC
+               print "local_pwr_constraint=3"
+               print "spectrum_mgmt_required=1"
+
+               # Enable Radar Detection
+               if enabled dfs && wireless_supports_dfs "${device}"; then
+                       print "ieee80211h=1"
                else
-                       hw_mode="a"
+                       print "ieee80211h=0"
                fi
-               ieee80211n="1"
-       else
-               hw_mode="${mode}"
-       fi
 
-       (
+               print # empty line
+
                print "# Wireless configuration"
-               print "channel=${channel}"
-               print "country_code=${country_code}"
                print "hw_mode=${hw_mode}"
-               print "ieee80211d=${ieee80211d}"
-               print "ieee80211n=${ieee80211n}"
-               print "ignore_broadcast_ssid=${ignore_broadcast_ssid}"
 
-               if contains_spaces "${ssid}"; then
-                       print "ssid=\"${ssid}\""
-               else
-                       print "ssid=${ssid}"
+               if isset ieee80211ac; then
+                       print "ieee80211ac=${ieee80211ac}"
+               fi
+
+               if isset ieee80211n; then
+                       print "ieee80211n=${ieee80211n}"
                fi
 
-               # WMM
+               print "channel=${channel}"
+               print "ignore_broadcast_ssid=${ignore_broadcast_ssid}"
+
+               print "ssid2=\"${ssid}\""
+               print "utf8_ssid=1"
+
+               # Kick stations that are too far away
+               print "disassoc_low_ack=1"
+
+               # WMM & WMM-PS Unscheduled Automatic Power Save Delivery
                print "wmm_enabled=${wmm}"
+               print "uapsd_advertisement_enabled=1"
+
+               # Low Priority / AC_BK = Background
+               print "wmm_ac_bk_cwmin=4"
+               print "wmm_ac_bk_cwmax=10"
+               print "wmm_ac_bk_aifs=7"
+               print "wmm_ac_bk_txop_limit=0"
+               print "wmm_ac_bk_acm=0"
+               print "tx_queue_data3_aifs=7"
+               print "tx_queue_data3_cwmin=15"
+               print "tx_queue_data3_cwmax=1023"
+               print "tx_queue_data3_burst=0"
+
+               # Normal Priority / AC_BE = Best Effort
+               print "wmm_ac_be_aifs=3"
+               print "wmm_ac_be_cwmin=4"
+               print "wmm_ac_be_cwmax=10"
+               print "wmm_ac_be_txop_limit=0"
+               print "wmm_ac_be_acm=0"
+               print "tx_queue_data2_aifs=3"
+               print "tx_queue_data2_cwmin=15"
+               print "tx_queue_data2_cwmax=63"
+               print "tx_queue_data2_burst=0"
+
+               # High Priority / AC_VI = Video
+               print "wmm_ac_vi_aifs=2"
+               print "wmm_ac_vi_cwmin=3"
+               print "wmm_ac_vi_cwmax=4"
+               print "wmm_ac_vi_txop_limit=94"
+               print "wmm_ac_vi_acm=0"
+               print "tx_queue_data1_aifs=1"
+               print "tx_queue_data1_cwmin=7"
+               print "tx_queue_data1_cwmax=15"
+               print "tx_queue_data1_burst=3.0"
+
+               # Highest Priority / AC_VO = Voice
+               print "wmm_ac_vo_aifs=2"
+               print "wmm_ac_vo_cwmin=2"
+               print "wmm_ac_vo_cwmax=3"
+               print "wmm_ac_vo_txop_limit=47"
+               print "wmm_ac_vo_acm=0"
+               print "tx_queue_data0_aifs=1"
+               print "tx_queue_data0_cwmin=3"
+               print "tx_queue_data0_cwmax=7"
+               print "tx_queue_data0_burst=1.5"
+
+               # Enable VHT caps
+               if isset vht_caps; then
+                       print "vht_capab=${vht_caps}"
+               fi
+
+               # Enable HT caps
+               print "ht_capab=${ht_caps}"
+
+               # Wider Channels
+               print "vht_oper_chwidth=${vht_oper_chwidth}"
 
                print
+
+               # 802.11w - Management Frame Protection (MFP)
+               if enabled mfp; then
+                       print "ieee80211w=2" # required
+               else
+                       print "ieee80211w=0"
+               fi
        ) >> ${file}
 
        # Control interface.
@@ -162,32 +401,55 @@ hostapd_config_write() {
                print
        ) >> ${file}
 
-       # Encryption settings
-       if isset encryption; then
-               local encryption_mode=0
-               case "${encryption}" in
-                       WPA)
-                               encryption_mode=1
-                               ;;
-                       WPA2)
-                               encryption_mode=2
-                               ;;
-                       WPA/WPA2)
-                               encryption_mode=3
-                               ;;
-               esac
+       # Authentication Settings
+       local wpa
+       local wpa_key_mgmt
+       local wpa_passphrase
+       local sae_password
+       local wpa_strict_rekey
+
+       # WPA3 Personal
+       if enabled WPA3_PERSONAL; then
+               # Enable RSN
+               wpa="2"
 
-               (
-                       print "# Encryption settings"
-                       print "wpa=${encryption_mode}"
-                       print "wpa_passphrase=${key}"
-                       print "wpa_key_mgmt=WPA-PSK"
-                       print "wpa_pairwise=TKIP"
-                       print "rsn_pairwise=CCMP"
-                       print
-               ) >> ${file}
+               # Add WPA key management
+               list_append wpa_key_mgmt "SAE"
+               sae_password="${secret}"
        fi
 
+       # WPA2 Personal
+       if enabled WPA2_PERSONAL; then
+               # Enable RSN
+               wpa="2"
+
+               # Add WPA key management
+               list_append wpa_key_mgmt "WPA-PSK-SHA256" "WPA-PSK"
+               wpa_passphrase="${secret}"
+
+               # Enable WPA strict rekey
+               wpa_strict_rekey="1"
+       fi
+
+       # Enable RSN ciphers when RSN is enabled
+       local rsn_pairwise
+       local group_cipher
+       if [ "${wpa}" = "2" ]; then
+               rsn_pairwise="${pairwise_ciphers[*]}"
+               group_cipher="${group_ciphers[*]}"
+       fi
+
+       local var
+       for var in wpa wpa_key_mgmt wpa_passphrase sae_password \
+                       rsn_pairwise group_cipher wpa_strict_rekey; do
+               if [ -n "${!var}" ]; then
+                       print "${var}=${!var}"
+               fi
+       done >> "${file}"
+
+       # Log configuration file
+       file_to_log DEBUG "${file}"
+
        return ${EXIT_OK}
 }
 
@@ -214,3 +476,21 @@ hostapd_stop() {
 
        service_stop "hostapd@${device}.service"
 }
+
+hostapd_cipher_name() {
+       local cipher="${1}"
+
+       case "${cipher}" in
+               CCMP-128)
+                       print "CCMP"
+                       ;;
+
+               GCMP-128)
+                       print "GCMP"
+                       ;;
+
+               *)
+                       print "${cipher}"
+                       ;;
+       esac
+}