#!/bin/bash ############################################################################### # # # IPFire.org - A linux based firewall # # Copyright (C) 2012 IPFire Network Development Team # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU 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 General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # # # ############################################################################### # 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}") ;; --channel=*) channel=$(cli_get_val "${1}") ;; --phy=*) phy=$(cli_get_val "${1}") phy=$(phy_get ${phy}) ;; --type=*) type=$(cli_get_val "${1}") # ap --> __ap [ "${type}" = "ap" ] && type="__ap" ;; *) error "Unrecognized argument: ${1}" return ${EXIT_ERROR} ;; esac shift done 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) cmd_quiet iw phy ${phy} interface add ${device} type ${type} local ret=$? if [ ${ret} -eq ${EXIT_OK} ]; then log DEBUG "created wireless device '${device}' (${type})" if isset address; then device_set_address ${device} ${address} fi else 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} } wireless_remove() { local device=${1} assert isset device if ! device_exists ${device}; then return ${EXIT_OK} fi # Tear down the device (if necessary). device_set_down ${device} # Remove it. cmd_quiet iw dev ${device} del local ret=$? if [ ${ret} -eq ${EXIT_OK} ]; then log DEBUG "removed wireless device '${device}'" else log ERROR "could not remove wireless device '${device}': ${ret}" fi return ${ret} } 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} # 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 [123456789]|1[0123]) print "$(( 2407 + (${channel} * 5)))" return ${EXIT_OK} ;; 14) print "2484" return ${EXIT_OK} ;; esac # 5 GHz band case "${channel}" in 3[68]|4[02468]|5[26]|6[04]|10[048]|11[26]|12[048]|13[26]|14[09]|15[37]|16[15]) print "$(( 5000 + (${channel} * 5)))" return ${EXIT_OK} ;; esac return ${EXIT_ERROR} } 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 bandwidth="${2}" assert isset bandwidth local bandwidths="${WIRELESS_CHANNEL_BANDWIDTHS["${mode}"]}" list_match "${bandwidth}" ${bandwidths} } 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 psk="${1}" # Length of the PSK local l="${#psk}" # For WPA*, the key must be between 8 and 63 chars if [ ${l} -lt 8 ] || [ ${l} -gt 63 ]; then return ${EXIT_FALSE} fi # Can only contain ASCII chararcters if contains_non_ascii_characters "${psk}"; then return ${EXIT_FALSE} fi # Seems OK return ${EXIT_TRUE} } wireless_client_is_connected() { local device="${1}" device_has_carrier "${device}" } wireless_ibss_join() { local device=${1} assert isset device shift local bssid local essid local frequency while [ $# -gt 0 ]; do case "${1}" in --bssid=*) bssid="$(cli_get_val "${1}")" ;; --essid=*) essid="$(cli_get_val "${1}")" ;; --channel=*) local channel="$(cli_get_val "${1}")" # Save the frequency of the channel instead # of the channel itself. if isset channel; then frequency="$(wireless_channel_to_frequency ${channel})" fi ;; esac shift done # Check input. assert ismac bssid assert isset essid assert isinteger frequency # Set device up. device_set_up "${device}" log INFO "${device} joining ibss network: ${essid} (${bssid})" cmd_quiet iw dev "${device}" ibss join "${essid}" \ "${frequency}" fixed-freq "${bssid}" } wireless_ibss_leave() { local device=${1} assert isset device log INFO "${device} leaving ibss network" cmd_quiet iw dev "${device}" ibss leave } 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[@]}" }