]>
Commit | Line | Data |
---|---|---|
0e035311 MT |
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 | ||
49ec20d8 | 22 | HOSTAPD_CONTROL_INTERFACE_DIR="/run/hostapd/ctrl" |
0e035311 | 23 | |
6c262922 MT |
24 | HOSTAPD_SUPPORTED_MODES="802.11a 802.11a/n 802.11ac 802.11g 802.11g/n" |
25 | ||
2e4e3c88 MT |
26 | HOSTAPD_SUPPORTED_PAIRWISE_CIPHERS=( |
27 | "GCMP-256" # Galois/counter mode protocol with 256 bit key | |
28 | "CCMP-256" # AES in Counter mode with CBC-MAC with 256 bit key | |
29 | "GCMP-128" # Galois/counter mode protocol with 128 bit key | |
30 | "CCMP-128" # AES in Counter mode with CBC-MAC with 128 bit key | |
31 | ) | |
32 | ||
33 | # This must be supported by all stations on the network and therefore | |
34 | # can effectively only be CCMP | |
35 | HOSTAPD_SUPPORTED_GROUP_CIPHERS=( | |
36 | "CCMP-128" | |
37 | ) | |
38 | ||
1c6a4e30 | 39 | hostapd_config_write() { |
0e035311 | 40 | local device=${1} |
49ec20d8 | 41 | assert isset device |
0e035311 | 42 | |
49ec20d8 MT |
43 | local file=${2} |
44 | assert isset file | |
45 | ||
46 | # Shift the device and file argument. | |
47 | shift 2 | |
0e035311 | 48 | |
2e4e3c88 MT |
49 | # Device must exist |
50 | if ! device_exists "${device}"; then | |
51 | error "Cannot write hostapd configuration for non-existant device: ${device}" | |
52 | return ${EXIT_ERROR} | |
53 | fi | |
54 | ||
55 | # Get the phy for device | |
56 | local phy="$(device_get_phy "${device}")" | |
57 | assert isset phy | |
58 | ||
0e035311 MT |
59 | local broadcast_ssid |
60 | local channel | |
f9e980d9 | 61 | local channel_bandwidth |
31670741 | 62 | local country_code="$(wireless_get_reg_domain)" |
7b297fb2 | 63 | local dfs="on" |
7842c2ce | 64 | local environment="${WIRELESS_DEFAULT_ENVIRONMENT}" |
34ca3936 | 65 | local mfp="off" |
0e035311 | 66 | local mode |
0a4c5aba | 67 | local secret |
0e035311 | 68 | local ssid |
19c166f8 | 69 | local wmm="1" |
0a4c5aba MT |
70 | local wpa2_personal="off" |
71 | local wpa3_personal="off" | |
0e035311 MT |
72 | |
73 | while [ $# -gt 0 ]; do | |
74 | case "${1}" in | |
75 | --broadcast-ssid=*) | |
2212045f | 76 | broadcast_ssid=$(cli_get_val "${1}") |
0e035311 MT |
77 | ;; |
78 | --channel=*) | |
2212045f | 79 | channel=$(cli_get_val "${1}") |
0e035311 | 80 | ;; |
f9e980d9 MT |
81 | --channel-bandwidth=*) |
82 | channel_bandwidth="$(cli_get_val "${1}")" | |
83 | ;; | |
7b297fb2 MT |
84 | --dfs=*) |
85 | dfs="$(cli_get_val "${1}")" | |
86 | ;; | |
0e035311 | 87 | --encryption=*) |
2212045f | 88 | encryption=$(cli_get_val "${1}") |
0e035311 | 89 | ;; |
7842c2ce MT |
90 | --environment=*) |
91 | environment="$(cli_get_val "${1}")" | |
92 | ;; | |
34ca3936 MT |
93 | --mfp=*) |
94 | mfp="$(cli_get_val "${1}")" | |
95 | ;; | |
4cfc085f | 96 | --mode=*) |
2212045f | 97 | mode=$(cli_get_val "${1}") |
6c262922 MT |
98 | |
99 | if ! isoneof mode ${HOSTAPD_SUPPORTED_MODES}; then | |
100 | error "Unsupported mode: ${mode}" | |
101 | return ${EXIT_ERROR} | |
102 | fi | |
4cfc085f | 103 | ;; |
0a4c5aba MT |
104 | --secret=*) |
105 | secret="$(cli_get_val "${1}")" | |
106 | ;; | |
4cfc085f | 107 | --ssid=*) |
2212045f | 108 | ssid=$(cli_get_val "${1}") |
4cfc085f | 109 | ;; |
19c166f8 MT |
110 | --wmm=*) |
111 | local val="$(cli_get_val "${1}")" | |
112 | if enabled val; then | |
113 | wmm="1" | |
114 | else | |
115 | wmm="0" | |
116 | fi | |
117 | ;; | |
0a4c5aba MT |
118 | --wpa2-personal=*) |
119 | wpa2_personal="$(cli_get_bool "${1}")" | |
120 | ;; | |
121 | --wpa3-personal=*) | |
122 | wpa3_personal="$(cli_get_bool "${1}")" | |
123 | ;; | |
0e035311 MT |
124 | *) |
125 | warning_log "Ignoring unknown argument '${1}'." | |
126 | ;; | |
127 | esac | |
128 | shift | |
129 | done | |
130 | ||
6c262922 MT |
131 | # Check if mode is set |
132 | if ! isset mode; then | |
133 | error "Mode is not set" | |
134 | return ${EXIT_ERROR} | |
135 | fi | |
136 | ||
0e035311 MT |
137 | assert isset broadcast_ssid |
138 | assert isbool broadcast_ssid | |
139 | ||
140 | assert isset channel | |
141 | assert isinteger channel | |
142 | ||
0e035311 MT |
143 | assert isset mode |
144 | assert isset ssid | |
145 | ||
7842c2ce MT |
146 | # Check wireless environment |
147 | if ! wireless_environment_is_valid "${environment}"; then | |
148 | error "Invalid wireless environment: ${environment}" | |
149 | return ${EXIT_ERROR} | |
150 | fi | |
151 | ||
1b4aa2ca MT |
152 | # With channel 0, ACS must be supported |
153 | if [ ${channel} -eq 0 ] && ! wireless_supports_acs "${device}"; then | |
154 | error "ACS requested, but not supported by ${device}" | |
155 | return ${EXIT_ERROR} | |
156 | fi | |
157 | ||
f9e980d9 MT |
158 | # Check channel bandwidth for validity |
159 | if isset channel_bandwidth && ! wireless_channel_bandwidth_is_valid "${mode}" "${channel_bandwidth}"; then | |
160 | error "Invalid channel bandwidth for ${mode}: ${channel_bandwidth}" | |
161 | return ${EXIT_ERROR} | |
162 | fi | |
163 | ||
34ca3936 MT |
164 | # Management Frame Proection |
165 | if ! isbool mfp; then | |
166 | error "Invalid value for --mfp: ${mfp}" | |
167 | return ${EXIT_ERROR} | |
168 | fi | |
169 | ||
0a4c5aba MT |
170 | # Check if secret is set for personal authentication |
171 | if ! isset secret && (enabled WPA3_PERSONAL || enabled WPA2_PERSONAL); then | |
172 | error "Secret not set but personal authentication enabled" | |
173 | return ${EXIT_ERROR} | |
174 | fi | |
175 | ||
6c262922 MT |
176 | # 802.11ac/n flags |
177 | local ieee80211ac | |
178 | local ieee80211n | |
179 | local vht_caps | |
f9e980d9 | 180 | local vht_oper_chwidth="0" |
6c262922 MT |
181 | local ht_caps |
182 | ||
183 | local hw_mode | |
184 | case "${mode}" in | |
185 | 802.11a) | |
186 | hw_mode="a" | |
187 | ;; | |
188 | ||
189 | 802.11a/n) | |
190 | hw_mode="a" | |
191 | ieee80211n="1" | |
192 | ||
193 | # Fetch HT caps | |
194 | ht_caps="$(wireless_get_ht_caps "${device}")" | |
195 | ;; | |
196 | ||
197 | 802.11g) | |
198 | hw_mode="g" | |
199 | ;; | |
200 | ||
201 | 802.11g/n) | |
202 | hw_mode="g" | |
203 | ieee80211n="1" | |
204 | ||
205 | # Fetch HT caps | |
206 | ht_caps="$(wireless_get_ht_caps "${device}")" | |
207 | ;; | |
208 | ||
209 | 802.11ac) | |
210 | hw_mode="a" | |
211 | ieee80211ac="1" | |
212 | ieee80211n="1" | |
213 | ||
214 | # Fetch VHT caps | |
215 | vht_caps="$(wireless_get_vht_caps "${device}")" | |
1526e219 | 216 | |
6c262922 MT |
217 | # Fetch HT caps |
218 | ht_caps="$(wireless_get_ht_caps "${device}")" | |
f9e980d9 MT |
219 | |
220 | case "${channel_bandwidth}" in | |
221 | 80) | |
222 | vht_oper_chwidth="1" | |
223 | ;; | |
224 | 160) | |
225 | vht_oper_chwidth="2" | |
226 | ;; | |
227 | 80+80) | |
228 | vht_oper_chwidth="3" | |
229 | ;; | |
230 | esac | |
6c262922 MT |
231 | ;; |
232 | esac | |
0e1c630c | 233 | |
2e4e3c88 MT |
234 | # Cryptography |
235 | local cipher | |
236 | ||
237 | # Get all supported pairwise ciphers | |
238 | local pairwise_ciphers=() | |
239 | for cipher in ${HOSTAPD_SUPPORTED_PAIRWISE_CIPHERS[*]}; do | |
240 | if phy_supports_cipher "${phy}" "${cipher}"; then | |
241 | pairwise_ciphers+=( "$(hostapd_cipher_name "${cipher}")" ) | |
242 | fi | |
243 | done | |
244 | ||
245 | # Get all supported group ciphers | |
246 | local group_ciphers=() | |
247 | for cipher in ${HOSTAPD_SUPPORTED_GROUP_CIPHERS[*]}; do | |
248 | if phy_supports_cipher "${phy}" "${cipher}"; then | |
249 | group_ciphers+=( "$(hostapd_cipher_name "${cipher}")" ) | |
250 | fi | |
251 | done | |
252 | ||
49ec20d8 MT |
253 | # Create configuration directory. |
254 | local config_dir=$(dirname ${file}) | |
255 | mkdir -p ${HOSTAPD_CONTROL_INTERFACE_DIR} ${config_dir} 2>/dev/null | |
256 | ||
257 | config_header "hostapd" > ${file} | |
258 | ||
259 | # Interface configuration | |
260 | ( | |
261 | print "# Interface configuration" | |
262 | print "driver=nl80211" | |
263 | print "interface=${device}" | |
264 | ||
265 | ) >> ${file} | |
266 | ||
267 | # Wireless configuration | |
0e035311 MT |
268 | local ignore_broadcast_ssid |
269 | if enabled broadcast_ssid; then | |
270 | ignore_broadcast_ssid="0" | |
271 | else | |
272 | ignore_broadcast_ssid="1" | |
273 | fi | |
274 | ||
49ec20d8 | 275 | ( |
b6ec3dd6 MT |
276 | # Advertise country code and maximum transmission power |
277 | print "ieee80211d=1" | |
7842c2ce MT |
278 | print "country_code=${country_code}" |
279 | ||
280 | # Wireless Environment | |
281 | case "${environment}" in | |
282 | indoor) | |
283 | print "country3=0x49" | |
284 | country3 | |
285 | ;; | |
286 | outdoor) | |
287 | print "country3=0x4f" | |
288 | ;; | |
289 | indoor+outdoor) | |
290 | print "country3=0x20" | |
291 | ;; | |
292 | esac | |
b6ec3dd6 | 293 | |
96026172 MT |
294 | # Always advertise TPC |
295 | print "local_pwr_constraint=3" | |
296 | print "spectrum_mgmt_required=1" | |
297 | ||
6c262922 | 298 | # Enable Radar Detection |
dc6d97fb | 299 | if enabled dfs && wireless_supports_dfs "${device}"; then |
7b297fb2 MT |
300 | print "ieee80211h=1" |
301 | else | |
302 | print "ieee80211h=0" | |
303 | fi | |
6c262922 MT |
304 | |
305 | print # empty line | |
306 | ||
49ec20d8 | 307 | print "# Wireless configuration" |
6c262922 MT |
308 | print "hw_mode=${hw_mode}" |
309 | ||
310 | if isset ieee80211ac; then | |
311 | print "ieee80211ac=${ieee80211ac}" | |
312 | fi | |
313 | ||
314 | if isset ieee80211n; then | |
315 | print "ieee80211n=${ieee80211n}" | |
316 | fi | |
317 | ||
49ec20d8 | 318 | print "channel=${channel}" |
49ec20d8 | 319 | print "ignore_broadcast_ssid=${ignore_broadcast_ssid}" |
0e035311 | 320 | |
4873f329 MT |
321 | print "ssid2=\"${ssid}\"" |
322 | print "utf8_ssid=1" | |
0e035311 | 323 | |
4d4bca7e MT |
324 | # Kick stations that are too far away |
325 | print "disassoc_low_ack=1" | |
326 | ||
fcdbed86 | 327 | # WMM & WMM-PS Unscheduled Automatic Power Save Delivery |
19c166f8 | 328 | print "wmm_enabled=${wmm}" |
fcdbed86 MT |
329 | print "uapsd_advertisement_enabled=1" |
330 | ||
331 | # Low Priority / AC_BK = Background | |
332 | print "wmm_ac_bk_cwmin=4" | |
333 | print "wmm_ac_bk_cwmax=10" | |
334 | print "wmm_ac_bk_aifs=7" | |
335 | print "wmm_ac_bk_txop_limit=0" | |
336 | print "wmm_ac_bk_acm=0" | |
337 | print "tx_queue_data3_aifs=7" | |
338 | print "tx_queue_data3_cwmin=15" | |
339 | print "tx_queue_data3_cwmax=1023" | |
340 | print "tx_queue_data3_burst=0" | |
341 | ||
342 | # Normal Priority / AC_BE = Best Effort | |
343 | print "wmm_ac_be_aifs=3" | |
344 | print "wmm_ac_be_cwmin=4" | |
345 | print "wmm_ac_be_cwmax=10" | |
346 | print "wmm_ac_be_txop_limit=0" | |
347 | print "wmm_ac_be_acm=0" | |
348 | print "tx_queue_data2_aifs=3" | |
349 | print "tx_queue_data2_cwmin=15" | |
350 | print "tx_queue_data2_cwmax=63" | |
351 | print "tx_queue_data2_burst=0" | |
352 | ||
353 | # High Priority / AC_VI = Video | |
354 | print "wmm_ac_vi_aifs=2" | |
355 | print "wmm_ac_vi_cwmin=3" | |
356 | print "wmm_ac_vi_cwmax=4" | |
357 | print "wmm_ac_vi_txop_limit=94" | |
358 | print "wmm_ac_vi_acm=0" | |
359 | print "tx_queue_data1_aifs=1" | |
360 | print "tx_queue_data1_cwmin=7" | |
361 | print "tx_queue_data1_cwmax=15" | |
362 | print "tx_queue_data1_burst=3.0" | |
363 | ||
364 | # Highest Priority / AC_VO = Voice | |
365 | print "wmm_ac_vo_aifs=2" | |
366 | print "wmm_ac_vo_cwmin=2" | |
367 | print "wmm_ac_vo_cwmax=3" | |
368 | print "wmm_ac_vo_txop_limit=47" | |
369 | print "wmm_ac_vo_acm=0" | |
370 | print "tx_queue_data0_aifs=1" | |
371 | print "tx_queue_data0_cwmin=3" | |
372 | print "tx_queue_data0_cwmax=7" | |
373 | print "tx_queue_data0_burst=1.5" | |
19c166f8 | 374 | |
1526e219 MT |
375 | # Enable VHT caps |
376 | if isset vht_caps; then | |
377 | print "vht_capab=${vht_caps}" | |
378 | fi | |
379 | ||
0e1c630c MT |
380 | # Enable HT caps |
381 | print "ht_capab=${ht_caps}" | |
382 | ||
f9e980d9 MT |
383 | # Wider Channels |
384 | print "vht_oper_chwidth=${vht_oper_chwidth}" | |
385 | ||
49ec20d8 | 386 | |
34ca3936 MT |
387 | |
388 | # 802.11w - Management Frame Protection (MFP) | |
389 | if enabled mfp; then | |
390 | print "ieee80211w=2" # required | |
391 | else | |
392 | print "ieee80211w=0" | |
393 | fi | |
49ec20d8 | 394 | ) >> ${file} |
0e035311 | 395 | |
49ec20d8 MT |
396 | # Control interface. |
397 | ( | |
398 | print "# Control interface" | |
399 | print "ctrl_interface=${HOSTAPD_CONTROL_INTERFACE_DIR}" | |
400 | print "ctrl_interface_group=0" | |
401 | ||
402 | ) >> ${file} | |
0e035311 | 403 | |
0a4c5aba MT |
404 | # Authentication Settings |
405 | local wpa | |
406 | local wpa_key_mgmt | |
407 | local wpa_passphrase | |
408 | local sae_password | |
409 | local wpa_strict_rekey | |
410 | ||
411 | # WPA3 Personal | |
412 | if enabled WPA3_PERSONAL; then | |
413 | # Enable RSN | |
414 | wpa="2" | |
415 | ||
416 | # Add WPA key management | |
417 | list_append wpa_key_mgmt "SAE" | |
418 | sae_password="${secret}" | |
419 | fi | |
420 | ||
421 | # WPA2 Personal | |
422 | if enabled WPA2_PERSONAL; then | |
423 | # Enable RSN | |
424 | wpa="2" | |
425 | ||
426 | # Add WPA key management | |
21ef3b74 | 427 | list_append wpa_key_mgmt "WPA-PSK-SHA256" |
0a4c5aba | 428 | wpa_passphrase="${secret}" |
0e035311 | 429 | |
0a4c5aba MT |
430 | # Enable WPA strict rekey |
431 | wpa_strict_rekey="1" | |
0e035311 MT |
432 | fi |
433 | ||
0a4c5aba MT |
434 | # Enable RSN ciphers when RSN is enabled |
435 | local rsn_pairwise | |
436 | local group_cipher | |
437 | if [ "${wpa}" = "2" ]; then | |
438 | rsn_pairwise="${pairwise_ciphers[*]}" | |
439 | group_cipher="${group_ciphers[*]}" | |
440 | fi | |
441 | ||
442 | local var | |
443 | for var in wpa wpa_key_mgmt wpa_passphrase sae_password \ | |
444 | rsn_pairwise group_cipher wpa_strict_rekey; do | |
445 | if [ -n "${!var}" ]; then | |
446 | print "${var}=${!var}" | |
447 | fi | |
448 | done >> "${file}" | |
449 | ||
7c91c167 MT |
450 | # Log configuration file |
451 | file_to_log DEBUG "${file}" | |
452 | ||
0e035311 MT |
453 | return ${EXIT_OK} |
454 | } | |
455 | ||
1c6a4e30 | 456 | hostapd_start() { |
0e035311 | 457 | local device=${1} |
0e035311 MT |
458 | assert isset device |
459 | ||
0e035311 MT |
460 | service_start "hostapd@${device}.service" |
461 | local ret=$? | |
462 | ||
49ec20d8 MT |
463 | if [ ${ret} -eq ${EXIT_OK} ]; then |
464 | log DEBUG "hostapd has been successfully started on '${device}'" | |
465 | else | |
466 | log ERROR "Could not start hostapd on '${device}': ${ret}" | |
467 | return ${EXIT_ERROR} | |
468 | fi | |
469 | ||
470 | return ${EXIT_OK} | |
0e035311 MT |
471 | } |
472 | ||
1c6a4e30 | 473 | hostapd_stop() { |
0e035311 MT |
474 | local device=${1} |
475 | assert isset device | |
476 | ||
477 | service_stop "hostapd@${device}.service" | |
0e035311 | 478 | } |
2e4e3c88 MT |
479 | |
480 | hostapd_cipher_name() { | |
481 | local cipher="${1}" | |
482 | ||
483 | case "${cipher}" in | |
484 | CCMP-128) | |
485 | print "CCMP" | |
486 | ;; | |
487 | ||
488 | GCMP-128) | |
489 | print "GCMP" | |
490 | ;; | |
491 | ||
492 | *) | |
493 | print "${cipher}" | |
494 | ;; | |
495 | esac | |
496 | } |