]> git.ipfire.org Git - people/ms/network.git/blob - src/functions/functions.ipv6
dhclient: Properly handle IPv6
[people/ms/network.git] / src / functions / functions.ipv6
1 #!/bin/bash
2 ###############################################################################
3 # #
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2010 Michael Tremer & Christian Schmidt #
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 IP_SUPPORTED_PROTOCOLS="${IP_SUPPORTED_PROTOCOLS} ipv6"
23
24 ipv6_device_autoconf_enable() {
25 local device="${1}"
26 assert device_exists "${device}"
27
28 sysctl_set "net.ipv6.conf.${device}.accept_ra" 1
29 sysctl_set "net.ipv6.conf.${device}.autoconf" 1
30
31 log INFO "Enabled IPv6 auto-configuration on '${device}'"
32
33 # Disable IPv6 forwarding which cannot be used when the
34 # device is using IPv6 auto-configuration.
35 ipv6_device_forwarding_disable "${device}"
36 }
37
38 ipv6_device_autoconf_disable() {
39 local device="${1}"
40 assert device_exists "${device}"
41
42 sysctl_set "net.ipv6.conf.${device}.accept_ra" 0
43 sysctl_set "net.ipv6.conf.${device}.autoconf" 0
44
45 log INFO "Disabled IPv6 auto-configuration on '${device}'"
46
47 # Enable IPv6 forwarding again
48 ipv6_device_forwarding_enable "${device}"
49
50 # Automatically disable privacy extensions
51 ipv6_device_privacy_extensions_disable "${device}"
52 }
53
54 ipv6_device_forwarding_enable() {
55 local device="${1}"
56 shift
57
58 local accept_ra=0
59
60 local arg
61 while read arg; do
62 case "${arg}" in
63 --accept-ra)
64 accept_ra=2
65 ;;
66 esac
67 done <<< "$(args $@)"
68
69 sysctl_set "net.ipv6.conf.${device}.forwarding" 1
70
71 log INFO "Enabled IPv6 forwarding on '${device}'"
72
73 # If forwarding is enabled, the kernel won't process
74 # any router advertisements any more, which is not good
75 # when we still want a default route when running in
76 # DHCP client mode on an uplink zone.
77 if [ ${accept_ra} -gt 0 ]; then
78 log INFO " and accepting router advertisements"
79
80 sysctl_set "net.ipv6.conf.${device}.accept_ra" 2
81 fi
82 }
83
84 ipv6_device_forwarding_disable() {
85 local device="${1}"
86
87 sysctl_set "net.ipv6.conf.${device}.forwarding" 0
88
89 log INFO "Disabled IPv6 forwarding on '${device}'"
90 }
91
92 # Enable IPv6 RFC3041 privacy extensions if desired
93 ipv6_device_privacy_extensions_enable() {
94 local device="${1}"
95 assert device_exists "${device}"
96
97 sysctl_set "net.ipv6.conf.${device}.use_tempaddr" 2
98 }
99
100 ipv6_device_privacy_extensions_disable() {
101 local device="${1}"
102 assert device_exists "${device}"
103
104 sysctl_set "net.ipv6.conf.${device}.use_tempaddr" 0
105 }
106
107 ipv6_is_valid() {
108 ipcalc --ipv6 -c $@ >/dev/null 2>&1
109
110 case "$?" in
111 0)
112 return ${EXIT_OK}
113 ;;
114 *)
115 return ${EXIT_ERROR}
116 ;;
117 esac
118 }
119
120 ipv6_prefix_is_valid() {
121 local prefix=${1}
122 assert isset prefix
123
124 [ ${prefix} -le 0 ] && return ${EXIT_FALSE}
125 [ ${prefix} -gt 128 ] && return ${EXIT_FALSE}
126
127 return ${EXIT_TRUE}
128 }
129
130 ipv6_get_prefix() {
131 ip_get_prefix "$@"
132 }
133
134 ipv6_split_prefix() {
135 ip_split_prefix "$@"
136 }
137
138 ipv6_address_add() {
139 local address="${1}"
140 assert isset address
141
142 local device="${2}"
143 assert device_exists "${device}"
144 shift 2
145
146 local scope="global"
147 local preferred_lft valid_lft
148
149 # Enable to wait until DAD has finished and return
150 # an error if it has failed
151 local wait_for_dad="true"
152
153 local arg
154 while read arg; do
155 case "${arg}" in
156 --preferred-lifetime=*)
157 preferred_lft="$(cli_get_val "${arg}")"
158 ;;
159 --valid-lifetime=*)
160 valid_lft="$(cli_get_val "${arg}")"
161 ;;
162 --no-wait-for-dad)
163 wait_for_dad="false"
164 ;;
165 esac
166 done <<< "$(args $@)"
167
168 local cmd="ip addr add ${address} dev ${device} scope ${scope}"
169
170 # Preferred lifetime
171 if isinteger preferred_lft; then
172 list_append cmd "preferred_lft ${preferred_lft}"
173 fi
174
175 # Valid lifetime
176 if isinteger valid_lft; then
177 list_append cmd "valid_lft ${valid_lft}"
178 fi
179
180 cmd_quiet "${cmd}" || return ${EXIT_ERROR}
181
182 if enabled wait_for_dad; then
183 log DEBUG "Waiting for DAD to complete..."
184
185 ipv6_wait_for_dad "${address}" "${device}"
186 local ret="${?}"
187
188 case "${ret}" in
189 # DAD OK
190 ${EXIT_DAD_OK})
191 log DEBUG "DAD successfully completed"
192 return ${EXIT_OK}
193 ;;
194
195 # DAD failed
196 ${EXIT_DAD_FAILED})
197 log ERROR "DAD failed"
198
199 # Remove the IP address again
200 ipv6_address_del "${address}" "${device}"
201
202 return ${EXIT_ERROR}
203 ;;
204
205 # Any unknown errors
206 *)
207 log ERROR "DAD failed with unhandled error: ${ret}"
208 return ${EXIT_ERROR}
209 ;;
210 esac
211 fi
212
213 return ${EXIT_OK}
214 }
215
216 ipv6_address_del() {
217 local address="${1}"
218 local device="${2}"
219
220 ip_address_del "${device}" "${address}"
221 }
222
223 ipv6_address_flush() {
224 local device="${1}"
225 assert isset device
226
227 log DEBUG "Flushing all IPv6 addresses on ${device}"
228
229 # Remove any stale addresses from aborted clients
230 cmd_quiet ip -6 addr flush dev "${device}" scope global permanent
231 cmd_quiet ip -6 addr flush dev "${device}" scope global dynamic
232 }
233
234 ipv6_address_change_lifetime() {
235 local address="${1}"
236 assert isset address
237
238 local device="${2}"
239 assert device_exists "${device}"
240 shift 2
241
242 local preferred_lft
243 local valid_lft
244
245 local arg
246 while read arg; do
247 case "${arg}" in
248 --preferred-lifetime=*)
249 preferred_lft="$(cli_get_val "${arg}")"
250 ;;
251 --valid-lifetime=*)
252 valid_lft="$(cli_get_val "${arg}")"
253 ;;
254 esac
255 done <<< "$(args $@)"
256
257 local cmd="ip -6 addr change ${address} dev ${device} scope global"
258
259 if isinteger preferred_lft; then
260 list_append cmd "preferred_lft" "${preferred_lft}"
261 fi
262
263 if isinteger valid_lft; then
264 list_append cmd "valid_lft" "${valid_lft}"
265 fi
266
267 if ! cmd_quiet "${cmd}"; then
268 log ERROR "Could not change lifetimes of ${address} (${device})"
269 return ${EXIT_ERROR}
270 fi
271
272 log DEBUG "Changed lifetimes of ${address} (${device}) to:"
273 if isset preferred_lft; then
274 log DEBUG " preferred: ${preferred_lft}"
275 fi
276
277 if isset valid_lft; then
278 log DEBUG " valid: ${valid_lft}"
279 fi
280
281 return ${EXIT_OK}
282 }
283
284 ipv6_get_dad_status() {
285 local address="${1}"
286 assert isset address
287
288 local device="${2}"
289 assert isset device
290
291 # Strip prefix from address
292 address="$(ipv6_split_prefix "${address}")"
293
294 local output="$(ip -o addr show dev "${device}" to "${address}")"
295 if ! isset output; then
296 return ${EXIT_ERROR}
297 fi
298
299 # Abort if DAD failed
300 if [[ ${output} =~ "dadfailed" ]]; then
301 return ${EXIT_DAD_FAILED}
302 fi
303
304 # Wait a little more if DAD is still in progress
305 if [[ ${output} =~ "tentative" ]]; then
306 return ${EXIT_DAD_TENTATIVE}
307 fi
308
309 # DAD has successfully completed
310 return ${EXIT_DAD_OK}
311 }
312
313 ipv6_wait_for_dad() {
314 local address="${1}"
315 assert isset address
316
317 local device="${2}"
318 assert isset device
319
320 # Strip prefix from address
321 address="$(ipv6_split_prefix "${address}")"
322
323 local i
324 for i in {0..10}; do
325 # Check DAD status
326 ipv6_get_dad_status "${address}" "${interface}"
327 local ret="${?}"
328
329 case "${ret}" in
330 # DAD is still in progress. Give it a moment to settle...
331 ${EXIT_DAD_TENTATIVE})
332 sleep 0.5
333 continue
334 ;;
335
336 # Raise all other error codes
337 ${EXIT_DAD_OK}|${EXIT_DAD_FAILED}|*)
338 return ${ret}
339 ;;
340 esac
341 done
342
343 return ${EXIT_ERROR}
344 }
345
346 ipv6_device_get_addresses() {
347 local device="${1}"
348 assert isset device
349 shift
350
351 local scope
352
353 local arg
354 while read arg; do
355 case "${arg}" in
356 --scope=*)
357 scope="$(cli_get_val "${arg}")"
358 ;;
359 esac
360 done <<< "$(args $@)"
361
362 local cmd="ip -o addr show dev ${device}"
363 if isset scope; then
364 assert isoneof scope global dynamic link
365 list_append cmd "scope ${scope}"
366 fi
367
368 local addresses
369 local line args
370 while read line; do
371 args=( ${line} )
372
373 local i
374 for (( i=0; i < ${#args[@]} - 1; i++ )); do
375 if [ "${args[${i}]}" = "inet6" ]; then
376 list_append_one addresses "${args[$(( ${i} + 1 ))]}"
377 break
378 fi
379 done
380 done <<< "$(${cmd})"
381
382 list_sort ${addresses}
383 }
384
385 ipv6_implode() {
386 local address=${1}
387 assert isset address
388
389 local ADDRESS6_IMPL
390 eval $(ipcalc -6 -i ${address} 2>/dev/null)
391 assert isset ADDRESS6_IMPL
392
393 print "${ADDRESS6_IMPL}"
394 }
395
396 ipv6_explode() {
397 local address=${1}
398 assert isset address
399
400 # Nothing to do if the length of the address is 39.
401 if [ ${#address} -eq 39 ]; then
402 print "${address}"
403 return ${EXIT_OK}
404 fi
405
406 local ADDRESS6_EXPL
407 eval $(ipcalc -6 -e ${address} 2>/dev/null)
408 assert isset ADDRESS6_EXPL
409
410 print "${ADDRESS6_EXPL}"
411 }
412
413 ipv6_addr_eq() {
414 local addr1=${1}
415 assert isset addr1
416
417 local addr2=${2}
418 assert isset addr2
419
420 local addr
421 for addr in addr1 addr2; do
422 printf -v ${addr} "%s" $(ipv6_explode ${!addr})
423 done
424
425 [[ "${addr1}" = "${addr2}" ]] \
426 && return ${EXIT_TRUE} || return ${EXIT_FALSE}
427 }
428
429 ipv6_addr_gt() {
430 local addr1=${1}
431 assert isset addr1
432
433 local addr2=${2}
434 assert isset addr2
435
436 local addr
437 for addr in addr1 addr2; do
438 printf -v ${addr} "%s" $(ipv6_explode ${!addr})
439 done
440
441 local i addr1_oct addr2_oct
442 for i in 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30; do
443 addr1_oct="0x${addr1:${i}:2}"
444 addr2_oct="0x${addr2:${i}:2}"
445
446 [[ ${addr1_oct} -gt ${addr2_oct} ]] && return ${EXIT_TRUE}
447 done
448
449 return ${EXIT_FALSE}
450 }
451
452 ipv6_hash() {
453 local address=${1}
454
455 assert isset address
456
457 # Explode address
458 address=$(ipv6_explode ${address})
459
460 echo "${address//:/}"
461 }
462
463 ipv6_get_network() {
464 local addr=${1}
465 assert isset addr
466
467 # Check if a prefix (e.g. /64) is provided.
468 local prefix=$(ip_get_prefix ${addr})
469 assert ipv6_prefix_is_valid ${prefix}
470
471 local PREFIX6
472 eval $(ipcalc --ipv6 -p ${addr})
473 assert isset PREFIX6
474
475 print "${PREFIX6}/${prefix}"
476 }
477
478 ipv6_6rd_format_address() {
479 local isp_prefix="${1}"
480 assert ipv6_is_valid "${isp_prefix}"
481
482 local client_address="${2}"
483 assert ipv4_is_valid "${client_address}"
484
485 local prefix="$(ipv6_get_prefix "${isp_prefix}")"
486 isp_prefix="$(ipv6_split_prefix "${isp_prefix}")"
487
488 # This only works for prefix lengths up to 32 bit.
489 assert [ "${prefix}" -le 32 ]
490 assert [ "${prefix}" -gt 0 ]
491
492 # Explode the address and throw away the second 32 bit.
493 local address="$(ipv6_explode "${isp_prefix}")"
494
495 client_address="$(ipv6_6rd_format_client_address ${client_address})"
496 assert isset client_address
497
498 local block1="0x${address:0:4}"
499 local block2="0x${address:5:4}"
500 local block3="0x${address:10:4}"
501 local block4="0x${address:15:4}"
502
503 address="$(( (${block1} << 48) + (${block2} << 32) + (${block3} << 16) + ${block4} ))"
504 assert [ "${address}" -gt 0 ]
505
506 block1="0x${client_address:0:4}"
507 block2="0x${client_address:5:4}"
508
509 client_address="$(( (${block1} << 48) + (${block2} << 32) ))"
510
511 # Fix for numbers that are interpreted by bash as negative
512 # numbers and therefore filled up with ones when shifted to
513 # the right. Weird.
514 if [ "${client_address}" -gt 0 ]; then
515 client_address="$(( ${client_address} >> ${prefix} ))"
516 else
517 local bitmask="$(( 1 << 63 ))"
518 client_address="$(( ${client_address} >> 1 ))"
519 client_address="$(( ${client_address} ^ ${bitmask} ))"
520 client_address="$(( ${client_address} >> $(( ${prefix} - 1 )) ))"
521 fi
522 assert [ "${client_address}" -gt 0 ]
523
524 # XOR everything together
525 address="$(( ${address} ^ ${client_address} ))"
526 prefix="$(( ${prefix} + 32 ))"
527
528 local block formatted_address=":"
529 while [ ${address} -gt 0 ]; do
530 printf -v block "%x" "$(( ${address} & 0xffff ))"
531 formatted_address="${block}:${formatted_address}"
532
533 address="$(( ${address} >> 16 ))"
534 done
535
536 assert ipv6_is_valid "${formatted_address}"
537
538 # Implode the output IP address.
539 formatted_address="$(ipv6_implode "${formatted_address}")"
540
541 print "${formatted_address}/${prefix}"
542 }
543
544 ipv6_6rd_format_client_address() {
545 local address="${1}"
546 assert isset address
547
548 print "%02x%02x:%02x%02x" ${address//\./ }
549 }