9e72fe0bb7dead230cb3433a4c0fd108d297dacc
[people/ms/network.git] / src / functions / functions.wireless
1 #!/bin/bash
2 ###############################################################################
3 #                                                                             #
4 # IPFire.org - A linux based firewall                                         #
5 # Copyright (C) 2012  IPFire Network Development Team                         #
6 #                                                                             #
7 # This program is free software: you can redistribute it and/or modify        #
8 # it under the terms of the GNU General Public License as published by        #
9 # the Free Software Foundation, either version 3 of the License, or           #
10 # (at your option) any later version.                                         #
11 #                                                                             #
12 # This program is distributed in the hope that it will be useful,             #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of              #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
15 # GNU General Public License for more details.                                #
16 #                                                                             #
17 # You should have received a copy of the GNU General Public License           #
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
19 #                                                                             #
20 ###############################################################################
21
22 # Sets the global wireless country code. Default is 00 = world.
23 WIRELESS_REGULATORY_DOMAIN="00"
24 NETWORK_SETTINGS_FILE_PARAMS="${NETWORK_SETTINGS_FILE_PARAMS} WIRELESS_REGULATORY_DOMAIN"
25
26 WIRELESS_REGULATORY_DOMAIN_DATABASE="/usr/lib/crda/regulatory.bin"
27
28 WIRELESS_DEFAULT_ENCRYPTION_MODE="NONE"
29 WIRELESS_VALID_ENCRYPTION_MODES="WPA2-PSK-SHA256 WPA2-PSK \
30         WPA-PSK-SHA256 WPA-PSK NONE"
31
32 declare -A WIRELESS_CHANNEL_BANDWIDTHS=(
33         ["802.11ac"]="20 40 80 160 80+80"
34         ["802.11a/n"]="20 40"
35         ["802.11a"]="20 40"
36         ["802.11g/n"]="20 40"
37         ["802.11g"]="20 40"
38 )
39
40 cli_wireless() {
41         local action=${1}
42         shift 1
43
44         case "${action}" in
45                 network)
46                         cli_wireless_network "$@"
47                         ;;
48                 *)
49                         error "Unrecognized argument: ${action}"
50                         exit ${EXIT_ERROR}
51                         ;;
52         esac
53 }
54
55 wireless_create() {
56         local device=${1}
57         assert isset device
58         shift
59
60         local address
61         local channel
62         local phy
63         local type="managed"
64
65         while [ $# -gt 0 ]; do
66                 case "${1}" in
67                         --address=*)
68                                 address=$(cli_get_val "${1}")
69                                 ;;
70                         --channel=*)
71                                 channel=$(cli_get_val "${1}")
72                                 ;;
73                         --phy=*)
74                                 phy=$(cli_get_val "${1}")
75                                 phy=$(phy_get ${phy})
76                                 ;;
77                         --type=*)
78                                 type=$(cli_get_val "${1}")
79
80                                 # ap --> __ap
81                                 [ "${type}" = "ap" ] && type="__ap"
82                                 ;;
83                         *)
84                                 error "Unrecognized argument: ${1}"
85                                 return ${EXIT_ERROR}
86                                 ;;
87                 esac
88                 shift
89         done
90
91         case "${type}" in
92                 ibss|managed|monitor|__ap)
93                         ;;
94                 mesh-point)
95                         type="mp"
96                         ;;
97                 *)
98                         log ERROR "Unknown type: ${type}"
99                         return ${EXIT_ERROR}
100                         ;;
101
102         esac
103
104         assert phy_exists ${phy}
105         isset address || address=$(mac_generate)
106
107         cmd_quiet iw phy ${phy} interface add ${device} type ${type}
108         local ret=$?
109
110         if [ ${ret} -eq ${EXIT_OK} ]; then
111                 log DEBUG "created wireless device '${device}' (${type})"
112
113                 if isset address; then
114                         device_set_address ${device} ${address}
115                 fi
116         else
117                 log ERROR "could not create wireless device '${device}' (${type}): ${ret}"
118         fi
119
120         # Set the channel
121         if isset channel; then
122                 wireless_set_channel "${device}" "${channel}" "auto" || return $?
123         fi
124
125         return ${ret}
126 }
127
128 wireless_remove() {
129         local device=${1}
130         assert isset device
131
132         if ! device_exists ${device}; then
133                 return ${EXIT_OK}
134         fi
135
136         # Tear down the device (if necessary).
137         device_set_down ${device}
138
139         # Remove it.
140         cmd_quiet iw dev ${device} del
141         local ret=$?
142
143         if [ ${ret} -eq ${EXIT_OK} ]; then
144                 log DEBUG "removed wireless device '${device}'"
145         else
146                 log ERROR "could not remove wireless device '${device}': ${ret}"
147         fi
148
149         return ${ret}
150 }
151
152 wireless_get_reg_domain() {
153         # Returns the country code for the wireless device.
154         # Defaults to 00 = world if unset.
155         print "${WIRELESS_REGULATORY_DOMAIN:-00}"
156 }
157
158 wireless_init_reg_domain() {
159         local country_code="$(wireless_get_reg_domain)"
160
161         wireless_set_reg_domain "${country_code}" --no-reset
162 }
163
164 wireless_set_reg_domain() {
165         local country_code
166         local reset="true"
167
168         while [ $# -gt 0 ]; do
169                 case "${1}" in
170                         --no-reset)
171                                 reset="false"
172                                 ;;
173                         -*)
174                                 log ERROR "Ignoring invalid option: ${1}"
175                                 ;;
176                         *)
177                                 country_code="${1}"
178                                 ;;
179                 esac
180                 shift
181         done
182
183         # Check if configuration value is valid
184         if ! wireless_valid_reg_domain "${country_code}"; then
185                 log ERROR "Invalid wireless regulatory domain: ${country_code}"
186                 return ${EXIT_ERROR}
187         fi
188
189         # Before the wireless reg domain is set, it helps to reset to 00 first.
190         if enabled reset; then
191                 iw reg set 00 &>/dev/null
192         fi
193
194         log INFO "Setting wireless regulatory domain country to '${country_code}'"
195         iw reg set "${country_code}"
196 }
197
198 wireless_valid_reg_domain() {
199         local country_code="${1}"
200
201         # Empty country codes are invalid
202         isset country_code || return ${EXIT_FALSE}
203
204         local valid_country_codes="$(wireless_list_reg_domains)"
205
206         if list_match "${country_code}" ${valid_country_codes}; then
207                 return ${EXIT_TRUE}
208         fi
209
210         return ${EXIT_FALSE}
211 }
212
213 wireless_list_reg_domains() {
214         if [ ! -r "${WIRELESS_REGULATORY_DOMAIN_DATABASE}" ]; then
215                 log ERROR "Could not read ${WIRELESS_REGULATORY_DOMAIN_DATABASE}"
216                 return ${EXIT_ERROR}
217         fi
218
219         local line
220         while read line; do
221                 # Check if line starts with "country"
222                 [ "${line:0:7}" = "country" ] || continue
223
224                 # Print country code
225                 print "${line:8:2}"
226         done <<< "$(regdbdump ${WIRELESS_REGULATORY_DOMAIN_DATABASE})"
227 }
228
229 # http://en.wikipedia.org/wiki/List_of_WLAN_channels
230 wireless_channel_to_frequency() {
231         local channel=${1}
232
233         # Works only for valid channel numbers
234         if ! wireless_channel_is_valid "${channel}"; then
235                 log ERROR "Invalid wireless channel: ${channel}"
236                 return ${EXIT_ERROR}
237         fi
238
239         # 2.4 GHz band
240         case "${channel}" in
241                 [123456789]|1[0123])
242                         print "$(( 2407 + (${channel} * 5)))"
243                         return ${EXIT_OK}
244                         ;;
245                 14)
246                         print "2484"
247                         return ${EXIT_OK}
248                         ;;
249         esac
250
251         # 5 GHz band
252         case "${channel}" in
253                 3[68]|4[02468]|5[26]|6[04]|10[048]|11[26]|12[048]|13[26]|14[09]|15[37]|16[15])
254                         print "$(( 5000 + (${channel} * 5)))"
255                         return ${EXIT_OK}
256                         ;;
257         esac
258
259         return ${EXIT_ERROR}
260 }
261
262 wireless_frequency_to_channel() {
263         local frequency=${1}
264
265         assert isinteger frequency
266
267         # Everything that is too high
268         if [ ${frequency} -gt 5825 ]; then
269                 return ${EXIT_ERROR}
270
271         # 5 GHz Band
272         elif [ ${frequency} -gt 5000 ]; then
273                 (( frequency = frequency - 5000 ))
274
275                 # Must be divisible by 5
276                 [ "$(( frequency % 5 ))" -ne 0 ] && return ${EXIT_ERROR}
277
278                 print "$(( frequency / 5 ))"
279
280         # 2.4 GHz Band - Channel 14
281         elif [ ${frequency} -eq 2484 ]; then
282                 print "14"
283
284         # 2.4 GHz Band
285         elif [ ${frequency} -gt 2407 ]; then
286                 (( frequency = frequency - 2407 ))
287
288                 # Must be divisible by 5
289                 [ "$(( frequency % 5 ))" -ne 0 ] && return ${EXIT_ERROR}
290
291                 print "$(( frequency / 5 ))"
292
293         # Everything else
294         else
295                 return ${EXIT_ERROR}
296         fi
297
298         return ${EXIT_OK}
299 }
300
301 wireless_channel_is_valid() {
302         local channel=${1}
303
304         case "${channel}" in
305                 # 2.4 GHz Band
306                 [123456789]|1[0123]|14)
307                         return ${EXIT_TRUE}
308                         ;;
309
310                 # 5 GHz Band
311                 3[68]|4[02468]|5[26]|6[04]|10[048]|11[26]|12[048]|13[26]|14[09]|15[37]|16[15])
312                         return ${EXIT_TRUE}
313                         ;;
314         esac
315
316         # Invalid channel number given
317         return ${EXIT_FALSE}
318 }
319
320 wireless_channel_bandwidth_is_valid() {
321         local mode="${1}"
322         assert isset mode
323
324         local bandwidth="${2}"
325         assert isset bandwidth
326
327         local bandwidths="${WIRELESS_CHANNEL_BANDWIDTHS["${mode}"]}"
328
329         list_match "${bandwidth}" ${bandwidths}
330 }
331
332 wireless_channel_is_ht40_plus() {
333         local channel="${1}"
334         assert isinteger channel
335
336         # 2.4 GHz
337         if [ ${channel} -le 6 ]; then
338                 return ${EXIT_TRUE}
339         fi
340
341         return ${EXIT_FALSE}
342 }
343
344 wireless_channel_is_ht40_minus() {
345         local channel="${1}"
346         assert isinteger channel
347
348         # 2.4 GHz
349         if [ ${channel} -ge 6 ]; then
350                 return ${EXIT_TRUE}
351         fi
352
353         return ${EXIT_FALSE}
354 }
355
356 wireless_set_channel() {
357         local device="${1}"
358         local channel="${2}"
359         local bandwidth="${3}"
360
361         # Check if the device exists
362         if ! device_exists "${device}"; then
363                 log ERROR "No such device: ${device}"
364                 return ${EXIT_ERROR}
365         fi
366
367         # Check if the channel number is valid
368         if ! wireless_channel_is_valid "${channel}"; then
369                 log ERROR "Invalid wireless channel: ${channel}"
370                 return ${EXIT_ERROR}
371         fi
372
373         local ht_flag
374         if [ "${bandwidth}" = "auto" ]; then
375                 local phy="$(device_get_phy "${device}")"
376
377                 # Offset of a 40 MHz channel
378                 local ht_offset=5
379
380                 if wireless_channel_is_ht40_plus "${channel}" \
381                                 && phy_supports_ht_capability "${phy}" "HT40+" \
382                                 && phy_supports_channel "${phy}" $(( channel + ht_offset )); then
383                         ht_flag="HT40+"
384
385                 elif wireless_channel_is_ht40_minus "${channel}" \
386                                 && phy_supports_ht_capability "${phy}" "HT40-" \
387                                 && phy_supports_channel "${phy}" $(( channel - ht_offset )); then
388                         ht_flags="HT40-"
389                 fi
390         fi
391
392         log DEBUG "Setting wireless channel on device '${device}' to channel '${channel}'"
393         cmd iw dev "${device}" set channel "${channel}" "${ht_flag}"
394 }
395
396 wireless_pre_shared_key_is_valid() {
397         local encryption_mode="${1}"
398         local psk="${2}"
399
400         # Length of the PSK
401         local l="${#psk}"
402
403         case "${encryption_mode}" in
404                 # For WPA*, the key must be between 8 and 63 chars
405                 WPA2-PSK|WPA2-PSK-SHA256|WPA-PSK|WPA-PSK-SHA256)
406                         if [ ${l} -ge 8 ] && [ ${l} -le 63 ]; then
407                                 return ${EXIT_TRUE}
408                         fi
409
410                         return ${EXIT_FALSE}
411                         ;;
412         esac
413
414         return ${EXIT_ERROR}
415 }
416
417 wireless_client_is_connected() {
418         local device="${1}"
419
420         device_has_carrier "${device}"
421 }
422
423 wireless_ibss_join() {
424         local device=${1}
425         assert isset device
426         shift
427
428         local bssid
429         local essid
430         local frequency
431
432         while [ $# -gt 0 ]; do
433                 case "${1}" in
434                         --bssid=*)
435                                 bssid="$(cli_get_val "${1}")"
436                                 ;;
437                         --essid=*)
438                                 essid="$(cli_get_val "${1}")"
439                                 ;;
440                         --channel=*)
441                                 local channel="$(cli_get_val "${1}")"
442
443                                 # Save the frequency of the channel instead
444                                 # of the channel itself.
445                                 if isset channel; then
446                                         frequency="$(wireless_channel_to_frequency ${channel})"
447                                 fi
448                                 ;;
449                 esac
450                 shift
451         done
452
453         # Check input.
454         assert ismac bssid
455         assert isset essid
456         assert isinteger frequency
457
458         # Set device up.
459         device_set_up "${device}"
460
461         log INFO "${device} joining ibss network: ${essid} (${bssid})"
462         cmd_quiet iw dev "${device}" ibss join "${essid}" \
463                 "${frequency}" fixed-freq "${bssid}"
464 }
465
466 wireless_ibss_leave() {
467         local device=${1}
468         assert isset device
469
470         log INFO "${device} leaving ibss network"
471         cmd_quiet iw dev "${device}" ibss leave
472 }
473
474 wireless_is_radar_frequency() {
475         local frequency="${1}"
476         assert isset frequency
477
478         [[ ${frequency} -ge 5260 ]] && [[ ${frequency} -le 5700 ]]
479 }
480
481 wireless_monitor() {
482         local device="${1}"
483         assert isset device
484         shift
485
486         local monitor_device="$(port_find_free "${PORT_PATTERN_WIRELESS_MONITOR}")"
487
488         # Create an 802.11 monitoring device
489         wireless_create "${monitor_device}" --phy="${device}" --type="monitor"
490         local ret=$?
491
492         case "${ret}" in
493                 0)
494                         # Bring up the device
495                         device_set_up "${monitor_device}"
496
497                         # Starting tcpdump
498                         tcpdump -i "${monitor_device}" "$@"
499
500                         # Remove the monitoring interface.
501                         wireless_remove "${monitor_device}"
502                         ;;
503
504                 *)
505                         log ERROR "Could not create a monitoring interface on ${device}"
506                         return ${EXIT_ERROR}
507                         ;;
508         esac
509
510         return ${EXIT_OK}
511 }
512
513 wireless_get_ht_caps() {
514         local device="${1}"
515         assert isset device
516
517         local phy="$(device_get_phy "${device}")"
518         if ! isset phy; then
519                 log ERROR "Could not determine PHY for ${device}"
520                 return ${EXIT_ERROR}
521         fi
522
523         network-phy-list-ht-caps "${phy}"
524 }
525
526 wireless_get_vht_caps() {
527         local device="${1}"
528         assert isset device
529
530         local phy="$(device_get_phy "${device}")"
531         if ! isset phy; then
532                 log ERROR "Could not determine PHY for ${device}"
533                 return ${EXIT_ERROR}
534         fi
535
536         network-phy-list-vht-caps "${phy}"
537 }
538
539 wireless_supports_acs() {
540         local device="${1}"
541         assert isset device
542
543         local phy="$(device_get_phy "${device}")"
544         if ! isset phy; then
545                 log ERROR "Could not determine PHY for ${device}"
546                 return ${EXIT_ERROR}
547         fi
548
549         phy_supports_acs "${phy}"
550 }
551
552 wireless_supports_dfs() {
553         local device="${1}"
554         assert isset device
555
556         local phy="$(device_get_phy "${device}")"
557         if ! isset phy; then
558                 log ERROR "Could not determine PHY for ${device}"
559                 return ${EXIT_ERROR}
560         fi
561
562         phy_supports_dfs "${phy}"
563 }