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