]> git.ipfire.org Git - people/ms/network.git/blobdiff - src/functions/functions.wireless
wireless-ap: Allow setting the wireless environment (indoor/outdoor)
[people/ms/network.git] / src / functions / functions.wireless
index cd17de4cf97d99568933b3137f1c89005d91b09b..12204c07879b6794c99f94e9d13b0442674cb4ce 100644 (file)
 #                                                                             #
 ###############################################################################
 
-function wireless_create() {
+# Sets the global wireless country code. Default is 00 = world.
+WIRELESS_REGULATORY_DOMAIN="00"
+NETWORK_SETTINGS_FILE_PARAMS="${NETWORK_SETTINGS_FILE_PARAMS} WIRELESS_REGULATORY_DOMAIN"
+
+WIRELESS_REGULATORY_DOMAIN_DATABASE="/usr/lib/crda/regulatory.bin"
+
+WIRELESS_DEFAULT_ENCRYPTION_MODE="NONE"
+WIRELESS_VALID_ENCRYPTION_MODES="WPA2-PSK-SHA256 WPA2-PSK \
+       WPA-PSK-SHA256 WPA-PSK NONE"
+
+declare -A WIRELESS_CHANNEL_BANDWIDTHS=(
+       ["802.11ac"]="20 40 80 160 80+80"
+       ["802.11a/n"]="20 40"
+       ["802.11a"]="20 40"
+       ["802.11g/n"]="20 40"
+       ["802.11g"]="20 40"
+)
+
+WIRELESS_ENVIRONMENTS=( "indoor+outdoor" "indoor" "outdoor" )
+WIRELESS_DEFAULT_ENVIRONMENT="${WIRELESS_ENVIRONMENTS[0]}"
+
+cli_wireless() {
+       local action=${1}
+       shift 1
+
+       case "${action}" in
+               network)
+                       cli_wireless_network "$@"
+                       ;;
+               *)
+                       error "Unrecognized argument: ${action}"
+                       exit ${EXIT_ERROR}
+                       ;;
+       esac
+}
+
+wireless_create() {
        local device=${1}
        assert isset device
        shift
 
        local address
+       local channel
        local phy
        local type="managed"
 
        while [ $# -gt 0 ]; do
                case "${1}" in
                        --address=*)
-                               address=$(cli_get_val ${1})
+                               address=$(cli_get_val "${1}")
+                               ;;
+                       --channel=*)
+                               channel=$(cli_get_val "${1}")
                                ;;
                        --phy=*)
-                               phy=$(cli_get_val ${1})
+                               phy=$(cli_get_val "${1}")
                                phy=$(phy_get ${phy})
                                ;;
                        --type=*)
-                               type=$(cli_get_val ${1})
+                               type=$(cli_get_val "${1}")
 
                                # ap --> __ap
                                [ "${type}" = "ap" ] && type="__ap"
@@ -51,7 +91,19 @@ function wireless_create() {
                shift
        done
 
-       assert isoneof type ibss managed __ap
+       case "${type}" in
+               ibss|managed|monitor|__ap)
+                       ;;
+               mesh-point)
+                       type="mp"
+                       ;;
+               *)
+                       log ERROR "Unknown type: ${type}"
+                       return ${EXIT_ERROR}
+                       ;;
+
+       esac
+
        assert phy_exists ${phy}
        isset address || address=$(mac_generate)
 
@@ -68,10 +120,15 @@ function wireless_create() {
                log ERROR "could not create wireless device '${device}' (${type}): ${ret}"
        fi
 
+       # Set the channel
+       if isset channel; then
+               wireless_set_channel "${device}" "${channel}" "auto" || return $?
+       fi
+
        return ${ret}
 }
 
-function wireless_remove() {
+wireless_remove() {
        local device=${1}
        assert isset device
 
@@ -95,14 +152,92 @@ function wireless_remove() {
        return ${ret}
 }
 
-function wireless_channel_to_frequency() {
-       # http://en.wikipedia.org/wiki/List_of_WLAN_channels
+wireless_get_reg_domain() {
+       # Returns the country code for the wireless device.
+       # Defaults to 00 = world if unset.
+       print "${WIRELESS_REGULATORY_DOMAIN:-00}"
+}
+
+wireless_init_reg_domain() {
+       local country_code="$(wireless_get_reg_domain)"
+
+       wireless_set_reg_domain "${country_code}" --no-reset
+}
+
+wireless_set_reg_domain() {
+       local country_code
+       local reset="true"
+
+       while [ $# -gt 0 ]; do
+               case "${1}" in
+                       --no-reset)
+                               reset="false"
+                               ;;
+                       -*)
+                               log ERROR "Ignoring invalid option: ${1}"
+                               ;;
+                       *)
+                               country_code="${1}"
+                               ;;
+               esac
+               shift
+       done
+
+       # Check if configuration value is valid
+       if ! wireless_valid_reg_domain "${country_code}"; then
+               log ERROR "Invalid wireless regulatory domain: ${country_code}"
+               return ${EXIT_ERROR}
+       fi
+
+       # Before the wireless reg domain is set, it helps to reset to 00 first.
+       if enabled reset; then
+               iw reg set 00 &>/dev/null
+       fi
+
+       log INFO "Setting wireless regulatory domain country to '${country_code}'"
+       iw reg set "${country_code}"
+}
+
+wireless_valid_reg_domain() {
+       local country_code="${1}"
+
+       # Empty country codes are invalid
+       isset country_code || return ${EXIT_FALSE}
+
+       local valid_country_codes="$(wireless_list_reg_domains)"
+
+       if list_match "${country_code}" ${valid_country_codes}; then
+               return ${EXIT_TRUE}
+       fi
+
+       return ${EXIT_FALSE}
+}
+
+wireless_list_reg_domains() {
+       if [ ! -r "${WIRELESS_REGULATORY_DOMAIN_DATABASE}" ]; then
+               log ERROR "Could not read ${WIRELESS_REGULATORY_DOMAIN_DATABASE}"
+               return ${EXIT_ERROR}
+       fi
+
+       local line
+       while read line; do
+               # Check if line starts with "country"
+               [ "${line:0:7}" = "country" ] || continue
 
+               # Print country code
+               print "${line:8:2}"
+       done <<< "$(regdbdump ${WIRELESS_REGULATORY_DOMAIN_DATABASE})"
+}
+
+# http://en.wikipedia.org/wiki/List_of_WLAN_channels
+wireless_channel_to_frequency() {
        local channel=${1}
-       assert isset channel
 
-       # Channel number must be positive.
-       assert [ "${channel}" -gt 0 ]
+       # Works only for valid channel numbers
+       if ! wireless_channel_is_valid "${channel}"; then
+               log ERROR "Invalid wireless channel: ${channel}"
+               return ${EXIT_ERROR}
+       fi
 
        # 2.4 GHz band
        case "${channel}" in
@@ -127,19 +262,168 @@ function wireless_channel_to_frequency() {
        return ${EXIT_ERROR}
 }
 
-function wireless_set_channel() {
-       local device=${1}
-       assert isset device
+wireless_frequency_to_channel() {
+       local frequency=${1}
+
+       assert isinteger frequency
+
+       # Everything that is too high
+       if [ ${frequency} -gt 5825 ]; then
+               return ${EXIT_ERROR}
+
+       # 5 GHz Band
+       elif [ ${frequency} -gt 5000 ]; then
+               (( frequency = frequency - 5000 ))
+
+               # Must be divisible by 5
+               [ "$(( frequency % 5 ))" -ne 0 ] && return ${EXIT_ERROR}
+
+               print "$(( frequency / 5 ))"
+
+       # 2.4 GHz Band - Channel 14
+       elif [ ${frequency} -eq 2484 ]; then
+               print "14"
+
+       # 2.4 GHz Band
+       elif [ ${frequency} -gt 2407 ]; then
+               (( frequency = frequency - 2407 ))
+
+               # Must be divisible by 5
+               [ "$(( frequency % 5 ))" -ne 0 ] && return ${EXIT_ERROR}
+
+               print "$(( frequency / 5 ))"
+
+       # Everything else
+       else
+               return ${EXIT_ERROR}
+       fi
+
+       return ${EXIT_OK}
+}
+
+wireless_channel_is_valid() {
+       local channel=${1}
+
+       case "${channel}" in
+               # 2.4 GHz Band
+               [123456789]|1[0123]|14)
+                       return ${EXIT_TRUE}
+                       ;;
+
+               # 5 GHz Band
+               3[68]|4[02468]|5[26]|6[04]|10[048]|11[26]|12[048]|13[26]|14[09]|15[37]|16[15])
+                       return ${EXIT_TRUE}
+                       ;;
+       esac
+
+       # Invalid channel number given
+       return ${EXIT_FALSE}
+}
+
+wireless_channel_bandwidth_is_valid() {
+       local mode="${1}"
+       assert isset mode
 
-       local channel=${2}
-       assert isset channel
+       local bandwidth="${2}"
+       assert isset bandwidth
 
-       device_exists ${device} || return ${EXIT_ERROR}
+       local bandwidths="${WIRELESS_CHANNEL_BANDWIDTHS["${mode}"]}"
 
-       cmd_quiet iw dev ${device} set channel ${channel}
+       list_match "${bandwidth}" ${bandwidths}
 }
 
-function wireless_ibss_join() {
+wireless_channel_is_ht40_plus() {
+       local channel="${1}"
+       assert isinteger channel
+
+       # 2.4 GHz
+       if [ ${channel} -le 6 ]; then
+               return ${EXIT_TRUE}
+       fi
+
+       return ${EXIT_FALSE}
+}
+
+wireless_channel_is_ht40_minus() {
+       local channel="${1}"
+       assert isinteger channel
+
+       # 2.4 GHz
+       if [ ${channel} -ge 6 ]; then
+               return ${EXIT_TRUE}
+       fi
+
+       return ${EXIT_FALSE}
+}
+
+wireless_set_channel() {
+       local device="${1}"
+       local channel="${2}"
+       local bandwidth="${3}"
+
+       # Check if the device exists
+       if ! device_exists "${device}"; then
+               log ERROR "No such device: ${device}"
+               return ${EXIT_ERROR}
+       fi
+
+       # Check if the channel number is valid
+       if ! wireless_channel_is_valid "${channel}"; then
+               log ERROR "Invalid wireless channel: ${channel}"
+               return ${EXIT_ERROR}
+       fi
+
+       local ht_flag
+       if [ "${bandwidth}" = "auto" ]; then
+               local phy="$(device_get_phy "${device}")"
+
+               # Offset of a 40 MHz channel
+               local ht_offset=5
+
+               if wireless_channel_is_ht40_plus "${channel}" \
+                               && phy_supports_ht_capability "${phy}" "HT40+" \
+                               && phy_supports_channel "${phy}" $(( channel + ht_offset )); then
+                       ht_flag="HT40+"
+
+               elif wireless_channel_is_ht40_minus "${channel}" \
+                               && phy_supports_ht_capability "${phy}" "HT40-" \
+                               && phy_supports_channel "${phy}" $(( channel - ht_offset )); then
+                       ht_flags="HT40-"
+               fi
+       fi
+
+       log DEBUG "Setting wireless channel on device '${device}' to channel '${channel}'"
+       cmd iw dev "${device}" set channel "${channel}" "${ht_flag}"
+}
+
+wireless_pre_shared_key_is_valid() {
+       local encryption_mode="${1}"
+       local psk="${2}"
+
+       # Length of the PSK
+       local l="${#psk}"
+
+       case "${encryption_mode}" in
+               # For WPA*, the key must be between 8 and 63 chars
+               WPA2-PSK|WPA2-PSK-SHA256|WPA-PSK|WPA-PSK-SHA256)
+                       if [ ${l} -ge 8 ] && [ ${l} -le 63 ]; then
+                               return ${EXIT_TRUE}
+                       fi
+
+                       return ${EXIT_FALSE}
+                       ;;
+       esac
+
+       return ${EXIT_ERROR}
+}
+
+wireless_client_is_connected() {
+       local device="${1}"
+
+       device_has_carrier "${device}"
+}
+
+wireless_ibss_join() {
        local device=${1}
        assert isset device
        shift
@@ -151,13 +435,13 @@ function wireless_ibss_join() {
        while [ $# -gt 0 ]; do
                case "${1}" in
                        --bssid=*)
-                               bssid="$(cli_get_val ${1})"
+                               bssid="$(cli_get_val "${1}")"
                                ;;
                        --essid=*)
-                               essid="$(cli_get_val ${1})"
+                               essid="$(cli_get_val "${1}")"
                                ;;
                        --channel=*)
-                               local channel="$(cli_get_val ${1})"
+                               local channel="$(cli_get_val "${1}")"
 
                                # Save the frequency of the channel instead
                                # of the channel itself.
@@ -182,7 +466,7 @@ function wireless_ibss_join() {
                "${frequency}" fixed-freq "${bssid}"
 }
 
-function wireless_ibss_leave() {
+wireless_ibss_leave() {
        local device=${1}
        assert isset device
 
@@ -190,9 +474,99 @@ function wireless_ibss_leave() {
        cmd_quiet iw dev "${device}" ibss leave
 }
 
-function wireless_is_radar_frequency() {
+wireless_is_radar_frequency() {
        local frequency="${1}"
        assert isset frequency
 
        [[ ${frequency} -ge 5260 ]] && [[ ${frequency} -le 5700 ]]
 }
+
+wireless_monitor() {
+       local device="${1}"
+       assert isset device
+       shift
+
+       local monitor_device="$(port_find_free "${PORT_PATTERN_WIRELESS_MONITOR}")"
+
+       # Create an 802.11 monitoring device
+       wireless_create "${monitor_device}" --phy="${device}" --type="monitor"
+       local ret=$?
+
+       case "${ret}" in
+               0)
+                       # Bring up the device
+                       device_set_up "${monitor_device}"
+
+                       # Starting tcpdump
+                       tcpdump -i "${monitor_device}" "$@"
+
+                       # Remove the monitoring interface.
+                       wireless_remove "${monitor_device}"
+                       ;;
+
+               *)
+                       log ERROR "Could not create a monitoring interface on ${device}"
+                       return ${EXIT_ERROR}
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+wireless_get_ht_caps() {
+       local device="${1}"
+       assert isset device
+
+       local phy="$(device_get_phy "${device}")"
+       if ! isset phy; then
+               log ERROR "Could not determine PHY for ${device}"
+               return ${EXIT_ERROR}
+       fi
+
+       network-phy-list-ht-caps "${phy}"
+}
+
+wireless_get_vht_caps() {
+       local device="${1}"
+       assert isset device
+
+       local phy="$(device_get_phy "${device}")"
+       if ! isset phy; then
+               log ERROR "Could not determine PHY for ${device}"
+               return ${EXIT_ERROR}
+       fi
+
+       network-phy-list-vht-caps "${phy}"
+}
+
+wireless_supports_acs() {
+       local device="${1}"
+       assert isset device
+
+       local phy="$(device_get_phy "${device}")"
+       if ! isset phy; then
+               log ERROR "Could not determine PHY for ${device}"
+               return ${EXIT_ERROR}
+       fi
+
+       phy_supports_acs "${phy}"
+}
+
+wireless_supports_dfs() {
+       local device="${1}"
+       assert isset device
+
+       local phy="$(device_get_phy "${device}")"
+       if ! isset phy; then
+               log ERROR "Could not determine PHY for ${device}"
+               return ${EXIT_ERROR}
+       fi
+
+       phy_supports_dfs "${phy}"
+}
+
+wireless_environment_is_valid() {
+       local environment="${1}"
+
+       list_match "${environment}" "${WIRELESS_ENVIRONMENTS[@]}"
+}