]> 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 e19f9b3f16d34103d00c9b4dcc75227439313044..6c2fbd9231c6a99b6bfc7e0e8103f8f9f55a1384 100644 (file)
@@ -23,6 +23,19 @@ 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
@@ -33,15 +46,29 @@ 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 dfs="on"
-       local encryption
-       local key
+       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
@@ -51,14 +78,20 @@ hostapd_config_write() {
                        --channel=*)
                                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}")
                                ;;
-                       --key=*)
-                               key=$(cli_get_val "${1}")
+                       --environment=*)
+                               environment="$(cli_get_val "${1}")"
+                               ;;
+                       --mfp=*)
+                               mfp="$(cli_get_val "${1}")"
                                ;;
                        --mode=*)
                                mode=$(cli_get_val "${1}")
@@ -68,6 +101,9 @@ hostapd_config_write() {
                                        return ${EXIT_ERROR}
                                fi
                                ;;
+                       --secret=*)
+                               secret="$(cli_get_val "${1}")"
+                               ;;
                        --ssid=*)
                                ssid=$(cli_get_val "${1}")
                                ;;
@@ -79,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}'."
                                ;;                      
@@ -101,16 +143,41 @@ 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
@@ -149,9 +216,40 @@ hostapd_config_write() {
 
                        # 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
@@ -175,13 +273,30 @@ hostapd_config_write() {
        fi
 
        (
-               print "# Default settings"
-
                # 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; then
+               if enabled dfs && wireless_supports_dfs "${device}"; then
                        print "ieee80211h=1"
                else
                        print "ieee80211h=0"
@@ -201,17 +316,61 @@ hostapd_config_write() {
                fi
 
                print "channel=${channel}"
-               print "country_code=${country_code}"
                print "ignore_broadcast_ssid=${ignore_broadcast_ssid}"
 
-               if contains_spaces "${ssid}"; then
-                       print "ssid=\"${ssid}\""
-               else
-                       print "ssid=${ssid}"
-               fi
+               print "ssid2=\"${ssid}\""
+               print "utf8_ssid=1"
+
+               # Kick stations that are too far away
+               print "disassoc_low_ack=1"
 
-               # WMM
+               # 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
@@ -221,7 +380,17 @@ hostapd_config_write() {
                # 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.
@@ -232,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"
+
+               # 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
 
-               (
-                       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}
+       # 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}
 }
 
@@ -284,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
+}