]> git.ipfire.org Git - people/stevee/network.git/blob - src/functions/functions.ipv6
9026cd88c866ec6ceb07de3aed782f8d71edae32
[people/stevee/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_prefix_size_is_valid_for_delegation() {
131 local prefix_size="${1}"
132 assert isinteger prefix_size
133
134 # For prefix delegation, the prefix must be between /48 and /64
135 # (RFC3769, section 3.1)
136 [[ ${prefix_size} -lt 48 ]] && return ${EXIT_FALSE}
137 [[ ${prefix_size} -gt 64 ]] && return ${EXIT_FALSE}
138
139 return ${EXIT_TRUE}
140 }
141
142 ipv6_get_prefix() {
143 ip_get_prefix "$@"
144 }
145
146 ipv6_split_prefix() {
147 ip_split_prefix "$@"
148 }
149
150 ipv6_address_add() {
151 local address="${1}"
152 assert isset address
153
154 local device="${2}"
155 assert device_exists "${device}"
156 shift 2
157
158 local scope="global"
159 local preferred_lft valid_lft
160
161 # Enable to wait until DAD has finished and return
162 # an error if it has failed
163 local wait_for_dad="true"
164
165 local arg
166 while read arg; do
167 case "${arg}" in
168 --preferred-lifetime=*)
169 preferred_lft="$(cli_get_val "${arg}")"
170 ;;
171 --valid-lifetime=*)
172 valid_lft="$(cli_get_val "${arg}")"
173 ;;
174 --no-wait-for-dad)
175 wait_for_dad="false"
176 ;;
177 esac
178 done <<< "$(args $@)"
179
180 local cmd="ip addr add ${address} dev ${device} scope ${scope}"
181
182 # Preferred lifetime
183 if isinteger preferred_lft; then
184 list_append cmd "preferred_lft ${preferred_lft}"
185 fi
186
187 # Valid lifetime
188 if isinteger valid_lft; then
189 list_append cmd "valid_lft ${valid_lft}"
190 fi
191
192 cmd_quiet "${cmd}" || return ${EXIT_ERROR}
193
194 if enabled wait_for_dad; then
195 log DEBUG "Waiting for DAD to complete..."
196
197 ipv6_wait_for_dad "${address}" "${device}"
198 local ret="${?}"
199
200 case "${ret}" in
201 # DAD OK
202 ${EXIT_DAD_OK})
203 log DEBUG "DAD successfully completed"
204 return ${EXIT_OK}
205 ;;
206
207 # DAD failed
208 ${EXIT_DAD_FAILED})
209 log ERROR "DAD failed"
210
211 # Remove the IP address again
212 ipv6_address_del "${address}" "${device}"
213
214 return ${EXIT_ERROR}
215 ;;
216
217 # Any unknown errors
218 *)
219 log ERROR "DAD failed with unhandled error: ${ret}"
220 return ${EXIT_ERROR}
221 ;;
222 esac
223 fi
224
225 return ${EXIT_OK}
226 }
227
228 ipv6_address_del() {
229 local address="${1}"
230 local device="${2}"
231
232 ip_address_del "${device}" "${address}"
233 }
234
235 ipv6_address_flush() {
236 local device="${1}"
237 assert isset device
238
239 log DEBUG "Flushing all IPv6 addresses on ${device}"
240
241 # Remove any stale addresses from aborted clients
242 cmd_quiet ip -6 addr flush dev "${device}" scope global permanent
243 cmd_quiet ip -6 addr flush dev "${device}" scope global dynamic
244 }
245
246 ipv6_address_change_lifetime() {
247 local address="${1}"
248 assert isset address
249
250 local device="${2}"
251 assert device_exists "${device}"
252 shift 2
253
254 local preferred_lft
255 local valid_lft
256
257 local arg
258 while read arg; do
259 case "${arg}" in
260 --preferred-lifetime=*)
261 preferred_lft="$(cli_get_val "${arg}")"
262 ;;
263 --valid-lifetime=*)
264 valid_lft="$(cli_get_val "${arg}")"
265 ;;
266 esac
267 done <<< "$(args $@)"
268
269 local cmd="ip -6 addr change ${address} dev ${device} scope global"
270
271 if isinteger preferred_lft; then
272 list_append cmd "preferred_lft" "${preferred_lft}"
273 fi
274
275 if isinteger valid_lft; then
276 list_append cmd "valid_lft" "${valid_lft}"
277 fi
278
279 if ! cmd_quiet "${cmd}"; then
280 log ERROR "Could not change lifetimes of ${address} (${device})"
281 return ${EXIT_ERROR}
282 fi
283
284 log DEBUG "Changed lifetimes of ${address} (${device}) to:"
285 if isset preferred_lft; then
286 log DEBUG " preferred: ${preferred_lft}"
287 fi
288
289 if isset valid_lft; then
290 log DEBUG " valid: ${valid_lft}"
291 fi
292
293 return ${EXIT_OK}
294 }
295
296 ipv6_get_dad_status() {
297 local address="${1}"
298 assert isset address
299
300 local device="${2}"
301 assert isset device
302
303 # Strip prefix from address
304 address="$(ipv6_split_prefix "${address}")"
305
306 local output="$(ip -o addr show dev "${device}" to "${address}")"
307 if ! isset output; then
308 return ${EXIT_ERROR}
309 fi
310
311 # Abort if DAD failed
312 if [[ ${output} =~ "dadfailed" ]]; then
313 return ${EXIT_DAD_FAILED}
314 fi
315
316 # Wait a little more if DAD is still in progress
317 if [[ ${output} =~ "tentative" ]]; then
318 return ${EXIT_DAD_TENTATIVE}
319 fi
320
321 # DAD has successfully completed
322 return ${EXIT_DAD_OK}
323 }
324
325 ipv6_wait_for_dad() {
326 local address="${1}"
327 assert isset address
328
329 local device="${2}"
330 assert isset device
331
332 # Strip prefix from address
333 address="$(ipv6_split_prefix "${address}")"
334
335 local i
336 for i in {0..10}; do
337 # Check DAD status
338 ipv6_get_dad_status "${address}" "${interface}"
339 local ret="${?}"
340
341 case "${ret}" in
342 # DAD is still in progress. Give it a moment to settle...
343 ${EXIT_DAD_TENTATIVE})
344 sleep 0.5
345 continue
346 ;;
347
348 # Raise all other error codes
349 ${EXIT_DAD_OK}|${EXIT_DAD_FAILED}|*)
350 return ${ret}
351 ;;
352 esac
353 done
354
355 return ${EXIT_ERROR}
356 }
357
358 ipv6_device_get_addresses() {
359 local device="${1}"
360 assert isset device
361 shift
362
363 local scope
364
365 local arg
366 while read arg; do
367 case "${arg}" in
368 --scope=*)
369 scope="$(cli_get_val "${arg}")"
370 ;;
371 esac
372 done <<< "$(args $@)"
373
374 local cmd="ip -o addr show dev ${device}"
375 if isset scope; then
376 assert isoneof scope global dynamic link
377 list_append cmd "scope ${scope}"
378 fi
379
380 local addresses
381 local line args
382 while read line; do
383 args=( ${line} )
384
385 local i
386 for (( i=0; i < ${#args[@]} - 1; i++ )); do
387 if [ "${args[${i}]}" = "inet6" ]; then
388 list_append_one addresses "${args[$(( ${i} + 1 ))]}"
389 break
390 fi
391 done
392 done <<< "$(${cmd})"
393
394 list_sort ${addresses}
395 }
396
397 ipv6_implode() {
398 local address=${1}
399 assert isset address
400
401 local ADDRESS6_IMPL
402 eval $(ipcalc -6 -i ${address} 2>/dev/null)
403 assert isset ADDRESS6_IMPL
404
405 print "${ADDRESS6_IMPL}"
406 }
407
408 ipv6_explode() {
409 local address=${1}
410 assert isset address
411
412 # Nothing to do if the length of the address is 39.
413 if [ ${#address} -eq 39 ]; then
414 print "${address}"
415 return ${EXIT_OK}
416 fi
417
418 local ADDRESS6_EXPL
419 eval $(ipcalc -6 -e ${address} 2>/dev/null)
420 assert isset ADDRESS6_EXPL
421
422 print "${ADDRESS6_EXPL}"
423 }
424
425 ipv6_addr_eq() {
426 local addr1=${1}
427 assert isset addr1
428
429 local addr2=${2}
430 assert isset addr2
431
432 local addr
433 for addr in addr1 addr2; do
434 printf -v ${addr} "%s" $(ipv6_explode ${!addr})
435 done
436
437 [[ "${addr1}" = "${addr2}" ]] \
438 && return ${EXIT_TRUE} || return ${EXIT_FALSE}
439 }
440
441 ipv6_addr_gt() {
442 local addr1=${1}
443 assert isset addr1
444
445 local addr2=${2}
446 assert isset addr2
447
448 local addr
449 for addr in addr1 addr2; do
450 printf -v ${addr} "%s" $(ipv6_explode ${!addr})
451
452 # Remove all colons
453 printf -v ${addr} "${!addr//:/}"
454 done
455
456 local i addr1_oct addr2_oct
457 for i in 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30; do
458 addr1_oct="0x${addr1:${i}:2}"
459 addr2_oct="0x${addr2:${i}:2}"
460
461 [[ ${addr1_oct} -gt ${addr2_oct} ]] && return ${EXIT_TRUE}
462 done
463
464 return ${EXIT_FALSE}
465 }
466
467 ipv6_hash() {
468 local address=${1}
469
470 assert isset address
471
472 # Explode address
473 address=$(ipv6_explode ${address})
474
475 echo "${address//:/}"
476 }
477
478 ipv6_get_network() {
479 local addr=${1}
480 assert isset addr
481
482 # Check if a prefix (e.g. /64) is provided.
483 local prefix=$(ip_get_prefix ${addr})
484 assert ipv6_prefix_is_valid ${prefix}
485
486 local PREFIX6
487 eval $(ipcalc --ipv6 -p ${addr})
488 assert isset PREFIX6
489
490 print "${PREFIX6}/${prefix}"
491 }
492
493 ipv6_6rd_format_address() {
494 local isp_prefix="${1}"
495 assert ipv6_is_valid "${isp_prefix}"
496
497 local client_address="${2}"
498 assert ipv4_is_valid "${client_address}"
499
500 local prefix="$(ipv6_get_prefix "${isp_prefix}")"
501 isp_prefix="$(ipv6_split_prefix "${isp_prefix}")"
502
503 # This only works for prefix lengths up to 32 bit.
504 assert [ "${prefix}" -le 32 ]
505 assert [ "${prefix}" -gt 0 ]
506
507 # Explode the address and throw away the second 32 bit.
508 local address="$(ipv6_explode "${isp_prefix}")"
509
510 client_address="$(ipv6_6rd_format_client_address ${client_address})"
511 assert isset client_address
512
513 local block1="0x${address:0:4}"
514 local block2="0x${address:5:4}"
515 local block3="0x${address:10:4}"
516 local block4="0x${address:15:4}"
517
518 address="$(( (${block1} << 48) + (${block2} << 32) + (${block3} << 16) + ${block4} ))"
519 assert [ "${address}" -gt 0 ]
520
521 block1="0x${client_address:0:4}"
522 block2="0x${client_address:5:4}"
523
524 client_address="$(( (${block1} << 48) + (${block2} << 32) ))"
525
526 # Fix for numbers that are interpreted by bash as negative
527 # numbers and therefore filled up with ones when shifted to
528 # the right. Weird.
529 if [ "${client_address}" -gt 0 ]; then
530 client_address="$(( ${client_address} >> ${prefix} ))"
531 else
532 local bitmask="$(( 1 << 63 ))"
533 client_address="$(( ${client_address} >> 1 ))"
534 client_address="$(( ${client_address} ^ ${bitmask} ))"
535 client_address="$(( ${client_address} >> $(( ${prefix} - 1 )) ))"
536 fi
537 assert [ "${client_address}" -gt 0 ]
538
539 # XOR everything together
540 address="$(( ${address} ^ ${client_address} ))"
541 prefix="$(( ${prefix} + 32 ))"
542
543 local block formatted_address=":"
544 while [ ${address} -gt 0 ]; do
545 printf -v block "%x" "$(( ${address} & 0xffff ))"
546 formatted_address="${block}:${formatted_address}"
547
548 address="$(( ${address} >> 16 ))"
549 done
550
551 assert ipv6_is_valid "${formatted_address}"
552
553 # Implode the output IP address.
554 formatted_address="$(ipv6_implode "${formatted_address}")"
555
556 print "${formatted_address}/${prefix}"
557 }
558
559 ipv6_6rd_format_client_address() {
560 local address="${1}"
561 assert isset address
562
563 print "%02x%02x:%02x%02x" ${address//\./ }
564 }