]> git.ipfire.org Git - people/stevee/network.git/commitdiff
dhclient: Add support for DHCPv6 and PD
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 31 Jan 2015 13:27:23 +0000 (13:27 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 31 Jan 2015 13:27:23 +0000 (13:27 +0000)
src/dhclient-script
src/functions/functions.constants
src/functions/functions.dhclient
src/functions/functions.ipv6

index 16e765d9d75e1d3c5fca4dc6c3afdd29f461cea9..5a6e8a3954c9bcc77ec77376ec4b1d176e4f05e2 100644 (file)
@@ -15,6 +15,14 @@ assert device_exists ${interface}
 basename="$(basename $0)"
 log DEBUG "${basename} called for interface=${interface} reason=${reason}"
 
+# Log all information from dhclient
+if enabled DEBUG; then
+       while read line; do
+               [[ ${line} =~ ^(cur|old|new)_ ]] || continue
+               log DEBUG "  ${line}"
+       done <<< "$(printenv | sort)"
+fi
+
 # Main pitchfork.
 case "${reason}" in
        MEDIUM)
@@ -22,6 +30,106 @@ case "${reason}" in
                exit 0
                ;;
 
+       # IPv6
+
+       PREINIT6)
+               if ! device_is_up "${interface}"; then
+                       log WARNING "Device '${interface}' was not brought up before starting the DHCP client"
+                       device_set_up "${interface}"
+               fi
+
+               # Flush all other aborted IP addresses
+               ipv6_address_flush "${interface}"
+
+               # Wait until DAD for the link-local address has finished
+               for address in $(ipv6_device_get_addresses "${interface}" --scope="link"); do
+                       ipv6_wait_for_dad "${address}" "${interface}" && exit ${EXIT_OK}
+               done
+
+               log ERROR "There is no active link-local address on ${interface}"
+
+               exit ${EXIT_ERROR}
+               ;;
+
+       BOUND6)
+               # We will be called twice. Once for the assigned address and once for an assigned prefix.
+
+               # Handle temporarily-assigned address
+               if isset new_ip6_address && isset new_ip6_prefixlen; then
+                       ipv6_address_add "${new_ip6_address}/${new_ip6_prefixlen}" "${interface}" \
+                               --valid-lifetime="${new_max_life}" --preferred-lifetime="${new_preferred_life}"
+
+                       # Save configuration
+                       routing_db_set "${interface}" "ipv6" "local-ip-address" "${new_ip6_address}/${new_ip6_prefixlen}"
+                       routing_db_set "${interface}" "ipv6" "active" 1
+                       #routing_db_set "${interface}" "ipv6" "domain-name" "${new_
+
+                       exit 0
+
+               # Handle Prefix Delegation
+               elif isset new_ip6_prefix; then
+                       routing_db_set "${interface}" "ipv6" "delegated-prefix" "${new_ip6_prefix}"
+
+                       exit 0
+               fi
+               ;;
+
+       RENEW6|REBIND6)
+               # Will be called twice like BOUND6.
+
+               if isset new_ip6_address && isset new_ip6_prefixlen; then
+                       # Update nameservers if those have changed
+                       if [[ "${old_dhcp6_name_servers}" != "${new_dhcp6_name_servers}" ]]; then
+                               routing_db_set "${interface}" "ipv6" "domain-name-servers" "${new_dhcp6_name_servers}"
+                               dns_generate_resolvconf
+                       fi
+
+                       # Update the lifetime if the address has not changed
+                       if [[ "${old_ip6_address}" = "${new_ip6_address}" ]]; then
+                               ipv6_address_change_lifetime "${new_ip6_address}/${new_ip6_prefixlen}" "${interface}" \
+                                       --valid-lifetime="${new_max_life}" --preferred-lifetime="${new_preferred_life}"
+
+                               exit ${EXIT_OK}
+                       fi
+
+                       # Remove any old IP addresses
+                       if [ -n "${old_ip6_address}" ]; then
+                               ipv6_address_del "${old_ip6_address}/${old_ip6_prefixlen}" "${interface}"
+                       fi
+
+                       # Add the new one
+                       ipv6_address_add "${new_ip6_address}/${new_ip6_prefixlen}" "${interface}" \
+                               --valid-lifetime="${new_max_life}" --preferred-lifetime="${new_preferred_life}"
+
+                       exit ${EXIT_OK}
+
+               # Handle Prefix Delegation
+               elif isset new_ip6_prefix || isset old_ip6_prefix; then
+                       if [[ "${old_ip6_prefix}" = "${new_ip6_prefix}" ]]; then
+                               # TODO What do we need to do if the prefix hasn't changed?
+                               exit ${EXIT_OK}
+                       fi
+
+                       log DEBUG "The delegated prefix has changed from ${old_ip6_prefix} to ${new_ip6_prefix}"
+                       routing_db_set "${interface}" "ipv6" "delegated-prefix" "${new_ip6_prefix}"
+
+                       exit ${EXIT_OK}
+               fi
+               ;;
+
+       DEPREF6)
+               # Check if all necessary information is there
+               if ! isset cur_ip6_address || ! isset cur_ip6_prefixlen; then
+                       exit ${EXIT_ERROR}
+               fi
+
+               # Set lifetime to zero
+               ipv6_address_change_lifetime "${cur_ip6_address}/${cur_ip6_prefixlen}" "${interface}" \
+                       --preferred-lifetime=0 || exit ${EXIT_ERROR}
+               ;;
+
+       # IPv4
+
        PREINIT)
                # Bring up the device if it hasn't been done before.
                if ! device_is_up ${interface}; then
@@ -63,7 +171,6 @@ case "${reason}" in
                exit 0
                ;;
 
-
        BOUND|RENEW|REBIND|REBOOT)
                # Check if the IP address has changed. If so, delete all routes and stuff.
                if [ -n "${old_ip_address}" -a "${old_ip_address}" != "${new_ip_address}" ]; then
@@ -127,6 +234,10 @@ case "${reason}" in
 
                exit 0
                ;;
+
+       *)
+               log ERROR "Unhandled reason: ${reason}"
+               ;;
 esac
 
 exit 1
index 6c11c4ac7e9cfdeb8acd6e48ebc0aad5c132f8ba..4233f76f95b98be24413ce6e815d12214c760e73 100644 (file)
@@ -60,6 +60,11 @@ EXIT_TRUE=0
 EXIT_FALSE=1
 EXIT_UNKNOWN=2
 
+# Exit codes for IPv6 duplicate address detection (DAD)
+EXIT_DAD_OK=0
+EXIT_DAD_FAILED=8
+EXIT_DAD_TENTATIVE=8
+
 STATUS_UP=0
 STATUS_DOWN=1
 STATUS_NOCARRIER=2
index 29a6d664c00cd293c05418b5a9711d69b99dd1f1..40cc61c52a5e760da5b34978ecc788a88e1fb992 100644 (file)
@@ -82,7 +82,6 @@ dhclient_write_config() {
        assert isset file
 
        local hostname=${HOSTNAME%%.*}
-       local prefix_delegation="false"
        local vendor=$(distro_get_pretty_name)
 
        while [ $# -gt 0 ]; do
@@ -90,9 +89,6 @@ dhclient_write_config() {
                        --hostname=*)
                                hostname=$(cli_get_val ${1})
                                ;;
-                       --prefix-delegation=*)
-                               prefix_delegation="$(cli_get_bool "${1}")"
-                               ;;
                        --vendor=*)
                                vendor=$(cli_get_val ${1})
                                ;;
@@ -131,14 +127,6 @@ dhclient_write_config() {
                        print
                fi
 
-               # Prefix delegation (IPv6).
-               if enabled prefix_delegation; then
-                       print " # Prefix delegation"
-                       print " also request dhcp6.ia-pd 1;"
-                       print " send dhcp6.ia-na 1;"
-                       print
-               fi
-
                echo "}"
        ) >>${file}
 
index 2667ae0b0036d912fc306bd224295c585b276b28..32ba81ae945d3966061f6d01bf2ca958b764a823 100644 (file)
@@ -83,6 +83,252 @@ function ipv6_split_prefix() {
        ip_split_prefix "$@"
 }
 
+function ipv6_address_add() {
+       local address="${1}"
+       assert isset address
+
+       local device="${2}"
+       assert device_exists "${device}"
+       shift 2
+
+       local scope="global"
+       local preferred_lft valid_lft
+
+       # Enable to wait until DAD has finished and return
+       # an error if it has failed
+       local wait_for_dad="true"
+
+       local arg
+       while read arg; do
+               case "${arg}" in
+                       --preferred-lifetime=*)
+                               preferred_lft="$(cli_get_val "${arg}")"
+                               ;;
+                       --valid-lifetime=*)
+                               valid_lft="$(cli_get_val "${arg}")"
+                               ;;
+                       --no-wait-for-dad)
+                               wait_for_dad="false"
+                               ;;
+               esac
+       done <<< "$(args $@)"
+
+       local cmd="ip addr add ${address} dev ${device} scope ${scope}"
+
+       # Preferred lifetime
+       if isinteger preferred_lft; then
+               list_append cmd "preferred_lft ${preferred_lft}"
+       fi
+
+       # Valid lifetime
+       if isinteger valid_lft; then
+               list_append cmd "valid_lft ${valid_lft}"
+       fi
+
+       cmd_quiet "${cmd}" || return ${EXIT_ERROR}
+
+       if enabled wait_for_dad; then
+               log DEBUG "Waiting for DAD to complete..."
+
+               ipv6_wait_for_dad "${address}" "${device}"
+               local ret="${?}"
+
+               case "${ret}" in
+                       # DAD OK
+                       ${EXIT_DAD_OK})
+                               log DEBUG "DAD successfully completed"
+                               return ${EXIT_OK}
+                               ;;
+
+                       # DAD failed
+                       ${EXIT_DAD_FAILED})
+                               log ERROR "DAD failed"
+
+                               # Remove the IP address again
+                               ipv6_address_del "${address}" "${device}"
+
+                               return ${EXIT_ERROR}
+                               ;;
+
+                       # Any unknown errors
+                       *)
+                               log ERROR "DAD failed with unhandled error: ${ret}"
+                               return ${EXIT_ERROR}
+                               ;;
+               esac
+       fi
+
+       return ${EXIT_OK}
+}
+
+function ipv6_address_del() {
+       local address="${1}"
+       local device="${2}"
+
+       ip_address_del "${device}" "${address}"
+}
+
+function ipv6_address_flush() {
+       local device="${1}"
+       assert isset device
+
+       log DEBUG "Flushing all IPv6 addresses on ${device}"
+
+       # Remove any stale addresses from aborted clients
+       cmd_quiet ip -6 addr flush dev "${device}" scope global permanent
+}
+
+function ipv6_address_change_lifetime() {
+       local address="${1}"
+       assert isset address
+
+       local device="${2}"
+       assert device_exists "${device}"
+       shift 2
+
+       local preferred_lft
+       local valid_lft
+
+       local arg
+       while read arg; do
+               case "${arg}" in
+                       --preferred-lifetime=*)
+                               preferred_lft="$(cli_get_val "${arg}")"
+                               ;;
+                       --valid-lifetime=*)
+                               valid_lft="$(cli_get_val "${arg}")"
+                               ;;
+               esac
+       done <<< "$(args $@)"
+
+       local cmd="ip -6 addr change ${address} dev ${device} scope global"
+
+       if isinteger preferred_lft; then
+               list_append cmd "preferred_lft" "${preferred_lft}"
+       fi
+
+       if isinteger valid_lft; then
+               list_append cmd "valid_lft" "${valid_lft}"
+       fi
+
+       if ! cmd_quiet "${cmd}"; then
+               log ERROR "Could not change lifetimes of ${address} (${device})"
+               return ${EXIT_ERROR}
+       fi
+
+       log DEBUG "Changed lifetimes of ${address} (${device}) to:"
+       if isset preferred_lft; then
+               log DEBUG "  preferred: ${preferred_lft}"
+       fi
+
+       if isset valid_lft; then
+               log DEBUG "  valid: ${valid_lft}"
+       fi
+
+       return ${EXIT_OK}
+}
+
+function ipv6_get_dad_status() {
+       local address="${1}"
+       assert isset address
+
+       local device="${2}"
+       assert isset device
+
+       # Strip prefix from address
+       address="$(ipv6_split_prefix "${address}")"
+
+       local output="$(ip -o addr show dev "${device}" to "${address}")"
+       if ! isset output; then
+               return ${EXIT_ERROR}
+       fi
+
+       # Abort if DAD failed
+       if [[ ${output} =~ "dadfailed" ]]; then
+               return ${EXIT_DAD_FAILED}
+       fi
+
+       # Wait a little more if DAD is still in progress
+       if [[ ${output} =~ "tentative" ]]; then
+               return ${EXIT_DAD_TENTATIVE}
+       fi
+
+       # DAD has successfully completed
+       return ${EXIT_DAD_OK}
+}
+
+function ipv6_wait_for_dad() {
+       local address="${1}"
+       assert isset address
+
+       local device="${2}"
+       assert isset device
+
+       # Strip prefix from address
+       address="$(ipv6_split_prefix "${address}")"
+
+       local i
+       for i in {0..10}; do
+               # Check DAD status
+               ipv6_get_dad_status "${address}" "${interface}"
+               local ret="${?}"
+
+               case "${ret}" in
+                       # DAD is still in progress. Give it a moment to settle...
+                       ${EXIT_DAD_TENTATIVE})
+                               sleep 0.5
+                               continue
+                               ;;
+
+                       # Raise all other error codes
+                       ${EXIT_DAD_OK}|${EXIT_DAD_FAILED}|*)
+                               return ${ret}
+                               ;;
+               esac
+       done
+
+       return ${EXIT_ERROR}
+}
+
+function ipv6_device_get_addresses() {
+       local device="${1}"
+       assert isset device
+       shift
+
+       local scope
+
+       local arg
+       while read arg; do
+               case "${arg}" in
+                       --scope=*)
+                               scope="$(cli_get_val "${arg}")"
+                               ;;
+               esac
+       done <<< "$(args $@)"
+
+       local cmd="ip -o addr show dev ${device}"
+       if isset scope; then
+               assert isoneof scope global dynamic link
+               list_append cmd "scope ${scope}"
+       fi
+
+       local addresses
+       local line args
+       while read line; do
+               args=( ${line} )
+
+               local i
+               for (( i=0; i < ${#args[@]} - 1; i++ )); do
+                       if [ "${args[${i}]}" = "inet6" ]; then
+                               list_append_one addresses "${args[$(( ${i} + 1 ))]}"
+                               break
+                       fi
+               done
+       done <<< "$(${cmd})"
+
+       list_sort ${addresses}
+}
+
 function ipv6_implode() {
        local address=${1}
        assert isset address