From: Michael Tremer Date: Sat, 2 May 2015 15:26:31 +0000 (+0000) Subject: dhcp: Add support for DHCPv6 over Ethernet X-Git-Tag: 007~31^2~5 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7ad5252c5573494c246fa6fc059c2a1564faedcf;p=people%2Fms%2Fnetwork.git dhcp: Add support for DHCPv6 over Ethernet --- diff --git a/Makefile.am b/Makefile.am index bf3886e5..2bb27e2f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -182,6 +182,7 @@ dist_hooks_configs_SCRIPTS = \ src/hooks/configs/ipv4-dhcp \ src/hooks/configs/ipv4-static \ src/hooks/configs/ipv6-auto \ + src/hooks/configs/ipv6-dhcp \ src/hooks/configs/ipv6-static \ src/hooks/configs/pppoe-server diff --git a/src/dhclient-helper b/src/dhclient-helper index 8401146a..7029b575 100644 --- a/src/dhclient-helper +++ b/src/dhclient-helper @@ -16,6 +16,8 @@ assert zone_exists ${interface} case "${action}" in start) + leases_file="" + # Create dhclient configuration file. case "${proto}" in ipv4) @@ -23,12 +25,15 @@ case "${action}" in ;; ipv6) config_file="${RUN_DIR}/dhclient/${interface}/dhclient6.conf" + leases_file="/var/lib/dhclient/dhclient6-${interface}.leases" ;; esac assert isset config_file - dhclient_write_config ${interface} ${config_file} \ - --hostname="${HOSTNAME%%.*}" + # Update the dhclient configuration files + dhclient_write_config "${interface}" \ + --config-file="${config_file}" \ + --leases-file="${leases_file}" || exit $? exit ${EXIT_OK} ;; diff --git a/src/functions/functions.dhclient b/src/functions/functions.dhclient index 40cc61c5..db23aff6 100644 --- a/src/functions/functions.dhclient +++ b/src/functions/functions.dhclient @@ -19,6 +19,8 @@ # # ############################################################################### +NETWORK_DHCP_DUID_FILE="${NETWORK_CONFIG_DIR}/dhcp-duid" + dhclient_start() { local interface=${1} local proto=${2} @@ -74,23 +76,30 @@ dhclient_proto2service() { } dhclient_write_config() { - local interface=${1} - local file=${2} - shift 2 + local interface="${1}" + shift assert isset interface - assert isset file - local hostname=${HOSTNAME%%.*} - local vendor=$(distro_get_pretty_name) + local hostname + local vendor + + local config_file + local leases_file while [ $# -gt 0 ]; do case "${1}" in + --config-file=*) + config_file="$(cli_get_val "${1}")" + ;; --hostname=*) - hostname=$(cli_get_val ${1}) + hostname="$(cli_get_val "${1}")" + ;; + --leases-file=*) + leases_file="$(cli_get_val "${1}")" ;; --vendor=*) - vendor=$(cli_get_val ${1}) + vendor="$(cli_get_val "${1}")" ;; *) log WARNING $"Unknown configuration option passed: ${1}." @@ -99,8 +108,19 @@ dhclient_write_config() { shift done + assert isset config_file + + # Set default values + if ! isset hostname; then + hostname="$(config_hostname)" + fi + + if ! isset vendor; then + vendor="$(distro_get_pretty_name)" + fi + # Clear configuration file (if any). - mkdir -p $(dirname ${file}) 2>/dev/null + mkdir -p $(dirname ${config_file}) 2>/dev/null : > ${file} # Print the header. @@ -112,11 +132,11 @@ dhclient_write_config() { echo "# $(date -u)" echo "#" echo - ) >>${file} + ) >> "${config_file}" # Global options. - echo "send vendor-class-identifier \"${vendor}\";" >>${file} - echo + fwrite "${config_file}" "send vendor-class-identifier \"${vendor}\";" + fwrite "${config_file}" # empty line # Interface options. ( @@ -128,7 +148,104 @@ dhclient_write_config() { fi echo "}" - ) >>${file} + ) >> "${config_file}" + + # Update leases file + if isset leases_file; then + __dhclient_update_leases_file "${leases_file}" || return $? + fi + + return ${EXIT_OK} +} + +dhclient_get_duid() { + # If the DUID already exists, we do not do anything at all. + if [ -s "${NETWORK_DHCP_DUID_FILE}" ]; then + print "$(<${NETWORK_DHCP_DUID_FILE})" + return ${EXIT_OK} + fi + + # If no DUID exists, we will need to create a new one + local duid="$(dhclient_generate_duid)" + log DEBUG "Created new DHCP DUID: ${duid}" + + # Save the new DUID to file and return it + print "${duid}" > "${NETWORK_DHCP_DUID_FILE}" + + print "${duid}" + return ${EXIT_OK} +} + + +dhclient_generate_duid() { + # Find lowest MAC/link-local address + local address="$(ports_lowest_address)" + + # Use a random MAC address if no physical address could + # be found. + if ! isset address; then + log WARNING "Could not determine the lowest MAC/link-local address" + address="$(mac_generate)" + fi + log DEBUG "Using '${address}' to generate the DHCP DUID" + + print "00030001${address//\:/}" return ${EXIT_OK} } + +__dhclient_update_leases_file() { + local file="${1}" + + local duid="$(dhclient_get_duid)" + + if [ -e "${leases_file}" ]; then + local old_duid="$(__dhclient_get_duid_from_leases_file "${leases_file}")" + + if [ "${duid}" = "${old_duid}" ]; then + log DEBUG "DUID in leases file matches. Nothing to do" + return ${EXIT_OK} + fi + fi + + # If the leases file does not exist, yet, or the + # DUID in there is different, we will create/overwrite + # the leases file with the correct DUID. + + ( + printf "default-duid \"" + + local i=0 + while [ ${i} -lt ${#duid} ]; do + printf "\\\\\\\\" + printf "x${duid:${i}:2}" + i=$(( ${i} + 2 )) + done + + print "\";" + ) > "${leases_file}" + + return ${EXIT_OK} +} + +__dhclient_get_duid_from_leases_file() { + local file="${1}" + + # Do nothing if the leases file cannot be read + [ -r "${file}" ] || return ${EXIT_OK} + + local line + while read line; do + if [[ ${line} =~ ^default-duid ]]; then + line="${line/default-duid/}" + line="${line//\\\\x/}" + line="${line//;/}" + + line="$(strip "${line}")" + unquote "${line}" + return ${EXIT_OK} + fi + done < "${file}" + + return ${EXIT_ERROR} +} diff --git a/src/functions/functions.list b/src/functions/functions.list index 0f11c32c..5515edb7 100644 --- a/src/functions/functions.list +++ b/src/functions/functions.list @@ -142,3 +142,13 @@ function list_reverse() { print "${reversed}" return ${EXIT_OK} } + +function list_head() { + local arg + for arg in $@; do + print "${arg}" + return ${EXIT_OK} + done + + return ${EXIT_ERROR} +} diff --git a/src/functions/functions.ports b/src/functions/functions.ports index 1edd1c30..e3ea1f61 100644 --- a/src/functions/functions.ports +++ b/src/functions/functions.ports @@ -403,3 +403,27 @@ function port_uses_phy() { local port_phy="$(port_get_phy "${port}")" [ "${port_phy}" = "${phy}" ] } + +function ports_lowest_address() { + local address + local addresses + + local port + for port in $(port_list); do + # Skip all ports that do not exist + # any more or are not plugged in + device_exists "${port}" || continue + + # Skip all ports that are not proper ethernet devices + device_is_wireless "${port}" && continue + device_is_ethernet "${port}" || continue + + list_append addresses "$(device_get_address "${port}")" + done + + # Sort the list + addresses="$(list_sort ${addresses})" + + # Get the first element which is the lowest MAC address + list_head ${addresses} +} diff --git a/src/hooks/configs/ipv6-dhcp b/src/hooks/configs/ipv6-dhcp new file mode 100644 index 00000000..77437752 --- /dev/null +++ b/src/hooks/configs/ipv6-dhcp @@ -0,0 +1,95 @@ +#!/bin/bash +############################################################################### +# # +# IPFire.org - A linux based firewall # +# Copyright (C) 2010 Michael Tremer & Christian Schmidt # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +. /usr/lib/network/header-config + +HOOK_CONFIG_SETTINGS="HOOK" + +function hook_create() { + local zone="${1}" + shift + + zone_config_settings_write "${zone}" "${HOOK}" + + exit ${EXIT_OK} +} + +function hook_up() { + local zone="${1}" + local config="${2}" + shift 2 + + if ! device_exists "${zone}"; then + error "Zone '${zone}' doesn't exist." + exit ${EXIT_ERROR} + fi + + # Start dhclient for IPv6 on this zone. + dhclient_start "${zone}" "ipv6" + + exit ${EXIT_OK} +} + +function hook_down() { + local zone="${1}" + local config="${2}" + shift 2 + + if ! device_exists "${zone}"; then + error "Zone '${zone}' doesn't exist." + exit ${EXIT_ERROR} + fi + + # Stop dhclient for IPv6 on this zone. + dhclient_stop "${zone}" "ipv6" + + exit ${EXIT_OK} +} + +function hook_status() { + local zone="${1}" + local config="${2}" + shift 2 + + if ! device_exists "${zone}"; then + error "Zone '${zone}' doesn't exist." + exit ${EXIT_ERROR} + fi + + zone_config_settings_read "${zone}" "${config}" + + local status + if dhclient_status "${zone}" "ipv6"; then + status="${MSG_HOOK_UP}" + else + status="${MSG_HOOK_DOWN}" + fi + cli_statusline 3 "${HOOK}" "${status}" + + cli_print_fmt1 3 "IPv6 address" "$(routing_db_get "${zone}" "ipv6" "local-ip-address")" + local gateway=$(routing_db_get ${zone} ipv6 remote-ip-address) + if isset gateway; then + cli_print_fmt1 3 "Gateway" "${gateway}" + fi + cli_space + + exit ${EXIT_OK} +}