]> git.ipfire.org Git - people/stevee/network.git/commitdiff
dhcpd: Add basic DHCP server functionality for IPv6 and IPv4.
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 15 Sep 2012 18:39:18 +0000 (18:39 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 15 Sep 2012 18:39:18 +0000 (18:39 +0000)
functions.dhcpd [new file with mode: 0644]
functions.radvd
helpers/dhcpd-config-helper [new file with mode: 0755]
network

diff --git a/functions.dhcpd b/functions.dhcpd
new file mode 100644 (file)
index 0000000..75153ea
--- /dev/null
@@ -0,0 +1,1163 @@
+#!/bin/bash
+###############################################################################
+#                                                                             #
+# IPFire.org - A linux based firewall                                         #
+# Copyright (C) 2012  IPFire Network Development Team                         #
+#                                                                             #
+# 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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+DHCPV6D_CONFIG_FILE="/etc/dhcp/dhcpd6.conf"
+DHCPV4D_CONFIG_FILE="/etc/dhcp/dhcpd.conf"
+
+DHCPV6D_CONFIG_DIR="${NETWORK_CONFIG_DIR}/dhcpd/ipv6"
+DHCPV4D_CONFIG_DIR="${NETWORK_CONFIG_DIR}/dhcpd/ipv4"
+
+DHCPV6D_OPTIONS_FILE="${DHCPV6D_CONFIG_DIR}/options"
+DHCPV4D_OPTIONS_FILE="${DHCPV4D_CONFIG_DIR}/options"
+
+DHCPV6D_SETTINGS_FILE="${DHCPV6D_CONFIG_DIR}/settings"
+DHCPV4D_SETTINGS_FILE="${DHCPV4D_CONFIG_DIR}/settings"
+
+DHCPD_SETTINGS="\
+       AUTHORITATIVE
+"
+DHCPV6D_SETTINGS="\
+       ${DHCPD_SETTINGS} \
+       PREFERRED_LIFETIME
+       VALID_LIFETIME
+"
+DHCPV4D_SETTINGS="\
+       ${DHCPD_SETTINGS} \
+       DEFAULT_LEASE_TIME \
+       MAX_LEASE_TIME \
+       MIN_LEASE_TIME \
+"
+
+DHCPD_SUBNET_PREFIX="subnet-"
+#DHCPD_SUBNET_POOL_PREFIX="pool-"
+DHCPD_SUBNET_RANGE_PREFIX="range-"
+
+DHCPD_SUBNET_SETTINGS="ADDRESS PREFIX ROUTERS"
+DHCPV6D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS}"
+DHCPV4D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS}"
+
+DHCPD_SUBNET_RANGE_SETTINGS="START END"
+DHCPV6D_SUBNET_RANGE_SETTINGS="${DHCPD_SUBNET_RANGE_SETTINGS}"
+DHCPV4D_SUBNET_RANGE_SETTINGS="${DHCPD_SUBNET_RANGE_SETTINGS}"
+
+DHCPV6D_OPTIONS="\
+       domain-search \
+       name-servers \
+"
+DHCPV4D_OPTIONS="\
+       all-subnets-local \
+       arp-cache-timeout \
+       bootfile-name \
+       broadcast-address \
+       default-ip-ttl \
+       default-tcp-ttl \
+       dhcp-client-identifier \
+       dhcp-lease-time \
+       dhcp-max-message-size \
+       dhcp-rebinding-time \
+       dhcp-renewal-time \
+       domain-name \
+       domain-name-servers \
+       domain-search \
+       interface-mtu \
+       ntp-servers \
+       root-path \
+       routers \
+       tftp-server-name \
+"
+
+DHCPD_SUBNET_OPTIONS="${DHCPD_OPTIONS}"
+DHCPV6D_SUBNET_OPTIONS="${DHCPD_SUBNET_OPTIONS}"
+DHCPV4D_SUBNET_OPTIONS="${DHCPD_SUBNET_OPTIONS}"
+
+# Defaults for DHCPv6.
+DHCPV6D_PREFERRED_LIFETIME=""
+DHCPV6D_VALID_LIFETIME="43200"         # 12h
+
+# Defaults for DHCPv4.
+DHCPV4D_AUTHORITATIVE="true"
+DHCPV4D_DEFAULT_LEASE_TIME="43200"     # 12h
+DHCPV4D_MAX_LEASE_TIME="86400"         # 24h
+DHCPV4D_MIN_LEASE_TIME=""
+
+function dhcpd_service() {
+       case "${1}" in
+               ipv6)
+                       print "dhcpd6.service"
+                       ;;
+               ipv4)
+                       print "dhcpd.service"
+                       ;;
+               "")
+                       print "dhcpd6.service dhcp.service"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_start() {
+       local services=$(dhcpd_service $@)
+
+       local service
+       for service in ${services}; do
+               service_start ${service}
+       done
+}
+
+function dhcpd_stop() {
+       local services=$(dhcpd_service $@)
+
+       local service
+       for service in ${services}; do
+               service_stop ${service}
+       done
+}
+
+function dhcpd_restart() {
+       # DHCP does not support a reload, so
+       # we retsart it.
+       local services=$(dhcpd_service $@)
+
+       local service
+       for service in ${services}; do
+               service_restart ${service}
+       done
+}
+
+function dhcpd_reload() {
+       dhcpd_restart $@
+}
+
+function dhcpd_edit() {
+       local proto=${1}
+       assert isset proto
+       shift
+
+       local settings=$(dhcpd_settings ${proto})
+       assert isset settings
+
+       local ${settings}
+       dhcpd_global_settings_read ${proto}
+
+       case "${proto}" in
+               ipv6)
+                       _dhcpd_edit_ipv6 $@ || return $?
+                       ;;
+               ipv4)
+                       _dhcpd_edit_ipv4 $@ || return $?
+                       ;;
+       esac
+
+       dhcpd_global_settings_write ${proto}
+}
+
+function _dhcpd_edit_ipv4() {
+       local val
+
+       while [ $# -gt 0 ]; do
+               case "${1}" in
+                       --authoritative=*)
+                               val=$(cli_get_val ${1})
+
+                               if enabled val; then
+                                       AUTHORITATIVE="true"
+                               else
+                                       AUTHORITATIVE="false"
+                               fi
+                               ;;
+                       --default-lease-time=*)
+                               DEFAULT_LEASE_TIME=$(cli_get_val ${1})
+
+                               if ! isinteger DEFAULT_LEASE_TIME; then
+                                       error "Invalid value for --default-lease-time."
+                                       return ${EXIT_ERROR}
+                               fi
+                               ;;
+                       --max-lease-time=*)
+                               MAX_LEASE_TIME=$(cli_get_val ${1})
+
+                               if ! isinteger MAX_LEASE_TIME; then
+                                       error "Invalid value for --max-lease-time."
+                                       return ${EXIT_ERROR}
+                               fi
+                               ;;
+                       --min-lease-time=*)
+                               MIN_LEASE_TIME=$(cli_get_val ${1})
+
+                               if isset MIN_LEASE_TIME; then
+                                       if ! isinteger MIN_LEASE_TIME; then
+                                               error "Invalid value for --min-lease-time."
+                                               return ${EXIT_ERROR}
+                                       fi
+                               fi
+                               ;;
+                       *)
+                               error "Unrecognized argument: ${1}"
+                               return ${EXIT_ERROR}
+                               ;;
+               esac
+               shift
+       done
+
+       if [ ${MAX_LEASE_TIME} -le ${DEFAULT_LEASE_TIME} ]; then
+               error "The max. lease time must be higher than the default lease time."
+               return ${EXIT_ERROR}
+       fi
+}
+
+function _dhcpd_edit_ipv6() {
+       while [ $# -gt 0 ]; do
+               case "${1}" in
+                       --preferred-lifetime=*)
+                               PREFERRED_LIFETIME=$(cli_get_val ${1})
+
+                               if ! isinteger PREFERRED_LIFETIME; then
+                                       error "Invalid value for --preferred-lifetime."
+                                       return ${EXIT_ERROR}
+                               fi
+                               ;;
+                       --valid-lifetime=*)
+                               VALID_LIFETIME=$(cli_get_val ${1})
+
+                               if ! isinteger VALID_LIFETIME; then
+                                       error "Invalid value for --valid-lifetime."
+                                       return ${EXIT_ERROR}
+                               fi
+                               ;;
+                       *)
+                               error "Unrecognized argument: ${1}"
+                               return ${EXIT_ERROR}
+                               ;;
+               esac
+               shift
+       done
+}
+
+function dhcpd_settings_file() {
+       local proto=${1}
+       assert isset proto
+
+       case "${proto}" in
+               ipv6)
+                       print "${DHCPV6D_SETTINGS_FILE}"
+                       ;;
+               ipv4)
+                       print "${DHCPV4D_SETTINGS_FILE}"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_settings() {
+       local proto=${1}
+       assert isset proto
+
+       case "${proto}" in
+               ipv6)
+                       print "${DHCPV6D_SETTINGS}"
+                       ;;
+               ipv4)
+                       print "${DHCPV4D_SETTINGS}"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_options_file() {
+       local proto=${1}
+       assert isset proto
+
+       case "${proto}" in
+               ipv6)
+                       print "${DHCPV6D_OPTIONS_FILE}"
+                       ;;
+               ipv4)
+                       print "${DHCPV4D_OPTIONS_FILE}"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_options_list() {
+       local proto=${1}
+       assert isset proto
+
+       case "${proto}" in
+               ipv6)
+                       print "DHCPV6D_OPTIONS"
+                       ;;
+               ipv4)
+                       print "DHCPV4D_OPTIONS"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_options() {
+       local proto=${1}
+       assert isset proto
+
+       case "${proto}" in
+               ipv6)
+                       print "${DHCPV6D_OPTIONS}"
+                       ;;
+               ipv4)
+                       print "${DHCPV4D_OPTIONS}"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_global_settings_defaults() {
+       local proto=${1}
+       assert isset proto
+
+       local settings=$(dhcpd_settings ${proto})
+       assert isset settings
+
+       local prefix="DHCPV${proto/ipv/}D_"
+
+       local setting setting_default
+       for setting in ${settings}; do
+               setting_default="${prefix}${setting}"
+               printf -v ${setting} "%s" "${!setting_default}"
+       done
+}
+
+function dhcpd_global_settings_read() {
+       local proto=${1}
+       assert isset proto
+
+       local file=$(dhcpd_settings_file ${proto})
+       assert isset file
+
+       local settings=$(dhcpd_settings ${proto})
+       assert isset settings
+
+       dhcpd_global_settings_defaults ${proto}
+       config_read ${file} ${settings}
+}
+
+function dhcpd_global_settings_write() {
+       local proto=${1}
+       assert isset proto
+
+       local file=$(dhcpd_settings_file ${proto})
+       assert isset file
+
+       local settings=$(dhcpd_settings ${proto})
+       assert isset settings
+
+       config_write ${file} ${settings}
+}
+
+function dhcpd_global_options_read() {
+       local proto=${1}
+       assert isset proto
+
+       local options_file=$(dhcpd_options_file ${proto})
+       local options_list=$(dhcpd_options_list ${proto})
+
+       config_read_array ${options_file} options ${!options_list}
+
+       # Check if domain-name is set.
+       if [ -z "${options["domain-name"]}" ]; then
+               options["domain-name"]=$(config_domainname)
+       fi
+}
+
+function dhcpd_subnet_path() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local path
+       case "${proto}" in
+               ipv6)
+                       path=${DHCPV6D_CONFIG_DIR}
+                       ;;
+               ipv4)
+                       path=${DHCPV4D_CONFIG_DIR}
+                       ;;
+       esac
+       assert isset path
+
+       print "${path}/${DHCPD_SUBNET_PREFIX}${subnet_id}"
+       return ${EXIT_OK}
+}
+
+function dhcpd_subnet_exists() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local path=$(dhcpd_subnet_path ${proto} ${subnet_id})
+       assert isset path
+
+       [ -d "${path}" ] && return ${EXIT_TRUE} || return ${EXIT_FALSE}
+}
+
+function dhcpd_subnet_match() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet=${2}
+       assert isset subnet
+
+       local settings=$(dhcpd_subnet_settings ${proto})
+       assert isset settings
+
+       local subnet_id ${settings}
+       for subnet_id in $(dhcpd_subnet_list ${proto}); do
+               dhcpd_subnet_read ${proto} ${subnet_id}
+
+               ${proto}_addr_eq "${ADDRESS}/${PREFIX}" "${subnet}" \
+                       && return ${EXIT_TRUE}
+       done
+
+       return ${EXIT_FALSE}
+}
+
+function dhcpd_new_subnet_id() {
+       local proto=${1}
+       assert isset proto
+
+       local id=1
+       while :; do
+               if ! dhcpd_subnet_exists ${proto} ${id}; then
+                       print "${id}"
+                       return ${EXIT_OK}
+               fi
+
+               id=$(( ${id} + 1 ))
+       done
+
+       return ${EXIT_ERROR}
+}
+
+function dhcpd_subnet_new() {
+       local proto=${1}
+       assert isset proto
+       shift
+
+       # Allocate a new subnet id.
+       local subnet_id=$(dhcpd_new_subnet_id ${proto})
+       assert isinteger subnet_id
+
+       # Create directory structure.
+       local path=$(dhcpd_subnet_path ${proto} ${subnet_id})
+       assert isset path
+
+       mkdir -p ${path}
+       touch ${path}/settings
+
+       dhcpd_subnet_edit ${proto} ${subnet_id} $@
+       local ret=$?
+
+       # Remove the new subnet, when the edit method returned
+       # an error.
+       if [ ${ret} -ne ${EXIT_OK} ]; then
+               dhcpd_subnet_remove ${proto} ${subnet_id}
+       fi
+}
+
+function dhcpd_subnet_edit() {
+       local proto=${1}
+       assert isset proto
+       shift
+
+       local id=${1}
+       assert isset id
+       shift
+
+       local settings
+       case "${proto}" in
+               ipv6)
+                       settings=${DHCPV6D_SUBNET_SETTINGS}
+                       ;;
+               ipv4)
+                       settings=${DHCPV4D_SUBNET_SETTINGS}
+                       ;;
+       esac
+       assert isset settings
+       local ${settings}
+
+       # Read current settings.
+       dhcpd_subnet_read ${proto} ${id} || :
+
+       while [ $# -gt 0 ]; do
+               case "${1}" in
+                       --address=*)
+                               ADDRESS=$(cli_get_val ${1})
+
+                               local prefix=$(ip_get_prefix ${ADDRESS})
+                               if isset prefix; then
+                                       PREFIX=${prefix}
+                                       ADDRESS=$(ip_split_prefix ${ADDRESS})
+                               fi
+                               ;;
+                       --prefix=*)
+                               PREFIX=$(cli_get_val ${1})
+                               ;;
+                       --routers=*)
+                               ROUTERS=$(cli_get_val ${1})
+                               ;;
+                       *)
+                               error "Unknown argument: ${1}"
+                               return ${EXIT_ERROR}
+                               ;;
+               esac
+               shift
+       done
+
+       case "${proto}" in
+               ipv6)
+                       if ! ipv6_is_valid ${ADDRESS}; then
+                               error "'${ADDRESS}' is not a valid IPv6 address."
+                               return ${EXIT_ERROR}
+                       fi
+
+                       if ! ipv6_prefix_is_valid ${PREFIX}; then
+                               error "'${PREFIX}' is not  a valid IPv6 prefix."
+                               return ${EXIT_ERROR}
+                       fi
+                       ;;
+               ipv4)
+                       if ! ipv4_is_valid ${ADDRESS}; then
+                               error "'${ADDRESS}' is not a valid IPv4 address."
+                               return ${EXIT_ERROR}
+                       fi
+
+                       if ! ipv4_prefix_is_valid ${PREFIX}; then
+                               error "'${PREFIX}' is not a valid IPv4 prefix."
+                               return ${EXIT_ERROR}
+                       fi
+                       ;;
+       esac
+
+       # XXX Check for subnet collisions!
+
+       local file="$(dhcpd_subnet_path ${proto} ${id})/settings"
+       config_write ${file} ${settings}
+}
+
+function dhcpd_subnet_remove() {
+       local proto=${1}
+       assert isset proto
+
+       local id=${2}
+       assert isset id
+
+       local path=$(dhcpd_subnet_path ${proto} ${id})
+       assert isset path
+
+       # Remove everything of this subnet.
+       rm -rf ${path}
+}
+
+function dhcpd_subnet_list() {
+       local proto=${1}
+       assert isset proto
+
+       local path=$(dhcpd_subnet_path ${proto} 0)
+       path=$(dirname ${path})
+
+       # Return an error of the directory does not exist.
+       [ -d "${path}" ] || return ${EXIT_ERROR}
+
+       local p
+       for p in ${path}/${DHCPD_SUBNET_PREFIX}*; do
+               [ -d "${p}" ] || continue
+
+               p=$(basename ${p})
+               print "${p:${#DHCPD_SUBNET_PREFIX}}"
+       done
+}
+
+function dhcpd_subnet_read() {
+       local proto=${1}
+       assert isset proto
+
+       local id=${2}
+       assert isset id
+
+       local file="$(dhcpd_subnet_path ${proto} ${id})/settings"
+       config_read ${file}
+}
+
+function dhcpd_subnet_range_path() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isinteger subnet_id
+
+       local range_id=${3}
+       assert isinteger range_id
+
+       print "$(dhcpd_subnet_path ${proto} ${subnet_id})/${DHCPD_SUBNET_RANGE_PREFIX}${range_id}"
+       return ${EXIT_OK}
+}
+
+function dhcpd_subnet_range_settings() {
+       local proto=${1}
+
+       case "${proto}" in
+               ipv6)
+                       print "${DHCPV6D_SUBNET_RANGE_SETTINGS}"
+                       ;;
+               ipv4)
+                       print "${DHCPV4D_SUBNET_RANGE_SETTINGS}"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_subnet_new_range_id() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local id=1 path
+       while :; do
+               path=$(dhcpd_subnet_range_path ${proto} ${subnet_id} ${id})
+               if [ ! -f "${path}" ]; then
+                       print "${id}"
+                       return ${EXIT_OK}
+               fi
+
+               id=$(( ${id} + 1 ))
+       done
+
+       return ${EXIT_ERROR}
+}
+
+function dhcpd_subnet_range_new() {
+       local proto=${1}
+       assert isset proto
+       shift
+
+       local subnet_id=${1}
+       assert isset subnet_id
+       shift
+
+       # Allocate a new range id.
+       local range_id=$(dhcpd_subnet_new_range_id ${proto} ${subnet_id})
+       assert isinteger range_id
+
+       local path=$(dhcpd_subnet_range_path ${proto} ${subnet_id} ${range_id})
+       assert isset path
+
+       # Create file (as a placeholder).
+       touch ${path}
+
+       dhcpd_subnet_range_edit ${proto} ${subnet_id} ${range_id} $@
+       local ret=$?
+
+       if [ ${ret} -ne ${EXIT_OK} ]; then
+               dhcpd_subnet_range_remove ${proto} ${subnet_id} ${range_id}
+               return ${EXIT_ERROR}
+       fi
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_subnet_range_edit() {
+       local proto=${1}
+       assert isset proto
+       shift
+
+       local subnet_id=${1}
+       assert isset subnet_id
+       shift
+
+       local range_id=${1}
+       assert isset range_id
+       shift
+
+       local ip_encode ip_is_valid
+       local settings
+       case "${proto}" in
+               ipv6)
+                       ip_encode="ipv6_encode"
+                       ip_is_valid="ipv6_is_valid"
+                       settings=${DHCPV6D_SUBNET_RANGE_SETTINGS}
+                       ;;
+               ipv4)
+                       ip_encode="ipv4_encode"
+                       ip_is_valid="ipv4_is_valid"
+                       settings=${DHCPV4D_SUBNET_RANGE_SETTINGS}
+                       ;;
+       esac
+       assert isset settings
+       local ${settings}
+
+       while [ $# -gt 0 ]; do
+               case "${1}" in
+                       --start=*)
+                               START=$(cli_get_val ${1})
+                               ;;
+                       --end=*)
+                               END=$(cli_get_val ${1})
+                               ;;
+                       *)
+                               error "Unknown argument: ${1}"
+                               return ${EXIT_ERROR}
+                               ;;
+               esac
+               shift
+       done
+
+       if ! isset START; then
+               error "You need to set the start of the IP range with --start=..."
+               return ${EXIT_ERROR}
+       fi
+
+       if ! isset END; then
+               error "You need to set the end of the IP range with --end=..."
+               return ${EXIT_ERROR}
+       fi
+
+       local var
+       for var in START END; do
+               if ! ${ip_is_valid} ${!var}; then
+                       error "'${!var}' is not a valid IP address."
+                       return ${EXIT_ERROR}
+               fi
+       done
+
+       # XXX currently, this check can only be performed for IPv4
+       if [ "${proto}" = "ipv4" ]; then
+               # Check if the end address is greater than the start address.
+               local start_encoded=$(${ip_encode} ${START})
+               local end_encoded=$(${ip_encode} ${END})
+
+               if [ ${start_encoded} -ge ${end_encoded} ]; then
+                       error "The start address of the range must be greater than the end address."
+                       return ${EXIT_ERROR}
+               fi
+       fi
+
+       # Write the configuration to file.
+       local file=$(dhcpd_subnet_range_path ${proto} ${subnet_id} ${range_id})
+       assert isset file
+
+       config_write ${file} ${settings}
+}
+
+function dhcpd_subnet_range_remove() {
+       local path=$(dhcpd_subnet_range_path $@)
+       assert isset path
+
+       rm -f ${path}
+}
+
+function dhcpd_subnet_range_list() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local path=$(dhcpd_subnet_range_path ${proto} ${subnet_id} 0)
+       path=$(dirname ${path})
+
+       local p
+       for p in ${path}/${DHCPD_SUBNET_RANGE_PREFIX}*; do
+               [ -r "${p}" ] || continue
+
+               p=$(basename ${p})
+               print "${p:${#DHCPD_SUBNET_RANGE_PREFIX}}"
+       done
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_subnet_range_read() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local range_id=${3}
+       assert isset range_id
+
+       local file=$(dhcpd_subnet_range_path ${proto} ${subnet_id} ${range_id})
+       config_read ${file}
+}
+
+function dhcpd_subnet_settings() {
+       local proto=${1}
+
+       case "${proto}" in
+               ipv6)
+                       print "${DHCPV6D_SUBNET_SETTINGS}"
+                       ;;
+               ipv4)
+                       print "${DHCPV4D_SUBNET_SETTINGS}"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_subnet_options_file() {
+       local path=$(dhcpd_subnet_path $@)
+       assert isset path
+
+       print "${path}/options"
+}
+
+function dhcpd_subnet_options_list() {
+       local proto=${1}
+
+       case "${proto}" in
+               ipv6)
+                       print "DHCPV6D_SUBNET_OPTIONS"
+                       ;;
+               ipv4)
+                       print "DHCPV4D_SUBNET_OPTIONS"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_subnet_options() {
+       local proto=${1}
+
+       case "${proto}" in
+               ipv6)
+                       print "${DHCPV6D_SUBNET_OPTIONS}"
+                       ;;
+               ipv4)
+                       print "${DHCPV4D_SUBNET_OPTIONS}"
+                       ;;
+       esac
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_subnet_options_read() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local options_file=$(dhcpd_subnet_options_file ${proto} ${subnet_id})
+       local options_list=$(dhcpd_subnet_options_list ${proto})
+
+       _dhcpd_read_options ${options_file} ${options_list}
+}
+
+# Helper functions to create a DHCP configuration file.
+function _dhcpd_write_options() {
+       local proto=${1}
+       assert isset proto
+
+       local file=${2}
+       assert isset file
+
+       local options_list=${3}
+       assert isset options_list
+
+       local ident=${4}
+
+       # Dump options array.
+       local key val fmt
+       for key in ${!options_list}; do
+               val=${options[${key}]}
+
+               # Prepend dhcp6 on IPv6 options.
+               if [ "${proto}" = "ipv6" ]; then
+                       key="dhcp6.${key}"
+               fi
+
+               if isset val; then
+                       if isinteger val; then
+                               fmt="option %s %d;"
+                       elif isipaddress val; then
+                               fmt="option %s %s;"
+                       else
+                               fmt="option %s \"%s\";"
+                       fi
+                       print "${ident}${fmt}" "${key}" "${val}"
+               fi
+       done >> ${file}
+
+       # Append an empty line when options have been written.
+       if [ -n "${!options[@]}" ]; then
+               print >> ${file}
+       fi
+}
+
+function _dhcpd_read_options() {
+       local file=${1}
+       assert isset file
+
+       local options_list=${2}
+       assert isset options_list
+
+       config_read_array ${file} options ${!options_list}
+}
+
+function _dhcpd_write_subnet() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local file=${3}
+       assert isset file
+
+       # Check which settings we do expect.
+       local settings
+       case "${proto}" in
+               ipv6)
+                       settings=${DHCPV6D_SUBNET_SETTINGS}
+                       ;;
+               ipv4)
+                       settings=${DHCPV4D_SUBNET_SETTINGS}
+                       ;;
+       esac
+       assert isset settings
+       local ${settings}
+
+       # Read configuration settings.
+       dhcpd_subnet_read ${proto} ${subnet_id}
+
+       print "# Subnet declaration for subnet id ${subnet_id}." >> ${file}
+       case "${proto}" in
+               ipv6)
+                       print "subnet6 ${ADDRESS}/${PREFIX} {" >> ${file}
+                       ;;
+               ipv4)
+                       local netmask=$(ipv4_get_netmask ${ADDRESS}/${PREFIX})
+                       print "subnet ${ADDRESS} netmask ${netmask} {" >> ${file}
+                       ;;
+       esac
+
+       # Add options.
+       _dhcpd_write_subnet_options ${proto} ${subnet_id} ${file}
+
+       # Add the ranges.
+       local range_id
+       for range_id in $(dhcpd_subnet_range_list ${proto} ${subnet_id} ${range_id}); do
+               _dhcpd_write_subnet_range ${proto} ${subnet_id} ${range_id} ${file}
+       done
+
+       # End this subnet block.
+       print "}\n" >> ${file}
+
+       return ${EXIT_OK}
+}
+
+function _dhcpd_write_subnet_options() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local file=${3}
+       assert isset file
+
+       local settings
+       local options_file="$(dhcpd_subnet_path ${proto} ${subnet_id})/options"
+       local options_list
+       case "${proto}" in
+               ipv6)
+                       settings=${DHCPV6D_SUBNET_SETTINGS}
+                       options_list="DHCPV6D_OPTIONS"
+                       ;;
+               ipv4)
+                       settings=${DHCPV4D_SUBNET_SETTINGS}
+                       options_list="DHCPV4D_OPTIONS"
+                       ;;
+       esac
+       assert isset settings
+       assert isset options_list
+
+       local ${settings}
+       dhcpd_subnet_read ${proto} ${subnet_id}
+
+       local -A options
+       _dhcpd_read_options ${options_file} ${options_list}
+
+       # Fill in router, if not already set.
+       if [ -z "${options["routers"]}" ]; then
+               options["routers"]=$(_dhcpd_search_routers ${proto} "${ADDRESS}/${PREFIX}")
+       fi
+
+       _dhcpd_write_options ${proto} ${file} ${options_list} "\t"
+}
+
+function _dhcpd_search_routers() {
+       local proto=${1}
+       assert isset proto
+
+       # Do nothing for IPv6 (yet?).
+       [ "${proto}" = "ipv6" ] && return ${EXIT_OK}
+
+       local subnet=${2}
+       assert isset subnet
+
+       local routers
+
+       local zone addr
+       for zone in $(zones_get_all); do
+               addr=$(routing_db_get ${zone} ${proto} local-ip-address)
+               isset addr || continue
+
+               if ipv4_in_subnet ${addr} ${subnet}; then
+                       list_append routers $(ip_split_prefix ${addr})
+               fi
+       done
+
+       list_join routers ", "
+}
+
+function _dhcpd_write_subnet_range() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local range_id=${3}
+       assert isset range_id
+
+       local file=${4}
+       assert isset file
+
+       local settings=$(dhcpd_subnet_range_settings ${proto})
+       assert isset settings
+
+       # Read the configuration settings.
+       local ${settings}
+       dhcpd_subnet_range_read ${proto} ${subnet_id} ${range_id}
+
+       # Print the range line.
+       print " # Range id ${range_id}." >> ${file}
+
+       case "${proto}" in
+               ipv6)
+                       print " range6 ${START} ${END};" >> ${file}
+                       ;;
+               ipv4)
+                       print " range ${START} ${END};" >> ${file}
+                       ;;
+       esac
+       print >> ${file}
+
+       return ${EXIT_OK}
+}
+
+function dhcpd_write_config() {
+       local proto=${1}
+       assert isset proto
+
+       local file options_list
+       case "${proto}" in
+               ipv6)
+                       file=${DHCPV6D_CONFIG_FILE}
+                       options_list="DHCPV6D_OPTIONS"
+                       ;;
+               ipv4)
+                       file=${DHCPV4D_CONFIG_FILE}
+                       options_list="DHCPV4D_OPTIONS"
+                       ;;
+       esac
+       assert isset file
+       assert isset options_list
+
+       # Writing header.
+       config_header "DHCP ${proto} daemon configuration file" > ${file}
+
+       # Authoritative.
+       if enabled AUTHORITATIVE; then
+               (
+                       print "# This is an authoritative DHCP server for this network."
+                       print "authoritative;\n"
+               ) >> ${file}
+       else
+               (
+                       print "# This is NOT an authoritative DHCP server for this network."
+                       print "not authoritative;\n"
+               ) >> ${file}
+       fi
+
+       case "${proto}" in
+               ipv6)
+                       # Lease times.
+                       if ininteger VALID_LIFETIME; then
+                               print "default-lease-time %d;" "${VALID_LIFETIME}" >> ${file}
+                       fi
+
+                       if isinteger PREFERRED_LIFETIME; then
+                               print "preferred-lifetime %d;" "${PREFERRED_LIFETIME}" >> ${file}
+                       fi
+                       ;;
+               ipv4)
+                       # Lease times.
+                       if isinteger DEFAULT_LEASE_TIME; then
+                               print "default-lease-time %d;" "${DEFAULT_LEASE_TIME}" >> ${file}
+                       fi
+
+                       if isinteger MAX_LEASE_TIME; then
+                               print "max-lease-time %d;" "${MAX_LEASE_TIME}" >> ${file}
+                       fi
+
+                       if isinteger MIN_LEASE_TIME; then
+                               print "min-lease-time %d;" "${MIN_LEASE_TIME}" >> ${file}
+                       fi
+                       ;;
+       esac
+
+       # Write the options to file.
+       local -A options
+       dhcpd_global_options_read ${proto}
+       _dhcpd_write_options ${proto} ${file} ${options_list}
+
+       # Add all subnet declarations.
+       local subnet_id
+       for subnet_id in $(dhcpd_subnet_list ${proto}); do
+               _dhcpd_write_subnet ${proto} ${subnet_id} ${file}
+       done
+
+       return ${EXIT_OK}
+}
index 0126a67a8a32c0665aa47291b5d6f2b523e59755..804398e522fe09e25486c1d6f2ef91d6401f6c21 100644 (file)
@@ -59,21 +59,41 @@ function __radvd_config_interface() {
        [ "${active}" = "0" ] && return ${EXIT_OK}
 
        # Skip if there is no prefix or prefix is link-local.
-       local prefix=$(routing_db_get ${zone} ipv6 local-ip-address)
-       if [ -z "${prefix}" ] || [ "${prefix:0:5}" = "fe80:" ]; then
+       local addr=$(routing_db_get ${zone} ipv6 local-ip-address)
+       if [ -z "${addr}" ] || [ "${addr:0:5}" = "fe80:" ]; then
                return ${EXIT_OK}
        fi
+       local prefix=$(ipv6_get_network ${addr})
+
+       # Check if the subnet is configured by the DHCP server.
+       local dhcpd="false"
+       if dhcpd_subnet_match ipv6 "${prefix}"; then
+               dhcpd="true"
+       fi
 
        print "interface ${zone} {"
        print " AdvSendAdvert on;"
        print " MinRtrAdvInterval 3;"
        print " MaxRtrAdvInterval 10;"
        print " IgnoreIfMissing on;"
+
+       if enabled dhcpd; then
+               print " AdvManagedFlag on;"
+               print " AdvOtherConfigFlag on;"
+       fi
+
        print
        print " prefix ${prefix} {"
        print "         AdvOnLink on;"
-       print "         AdvAutonomous on;"
-       print "         AdvRouterAddr on;"
+
+       if enabled dhcpd; then
+               print "         AdvRouterAddr off;"
+               print "         AdvAutonomous off;"
+       else
+               print "         AdvRouterAddr on;"
+               print "         AdvAutonomous on;"
+       fi
+
        print " };"
        print
 
diff --git a/helpers/dhcpd-config-helper b/helpers/dhcpd-config-helper
new file mode 100755 (executable)
index 0000000..80692b0
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/bash
+###############################################################################
+#                                                                             #
+# IPFire.org - A linux based firewall                                         #
+# Copyright (C) 2012  IPFire Network Development Team                         #
+#                                                                             #
+# 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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+###############################################################################
+
+. /usr/lib/network/functions
+
+action=${1}
+assert isset action
+
+proto=${2}
+assert isset proto
+assert isoneof proto ${IP_SUPPORTED_PROTOCOLS}
+
+case "${action}" in
+       create)
+               # Write the configuration file for the given
+               # protocol.
+               dhcpd_write_config ${proto}
+               ;;
+
+       *)
+               log ERROR "Unknown action passed: ${action}"
+               exit ${EXIT_ERROR}
+               ;;
+esac
+
+exit ${EXIT_OK}
diff --git a/network b/network
index 1c642721b6989977235bbc882021c055330129b3..d8e6e72916d92c69c0f506aa8d613524ca6e863e 100755 (executable)
--- a/network
+++ b/network
@@ -595,6 +595,305 @@ function cli_route() {
        exit ${EXIT_OK}
 }
 
+function cli_dhcpd() {
+       local proto=${1}
+       shift
+
+       if cli_help_requested $@; then
+               cli_show_man network-dhcp
+               exit ${EXIT_OK}
+       fi
+
+       local action=${1}
+       shift
+
+       case "${action}" in
+               edit)
+                       dhcpd_edit ${proto} $@
+                       ;;
+               start)
+                       dhcpd_start ${proto}
+                       ;;
+               stop)
+                       dhcpd_stop ${proto}
+                       ;;
+               restart|reload)
+                       dhcpd_reload ${proto}
+                       ;;
+               subnet)
+                       cli_dhcpd_subnet ${proto} $@
+                       ;;
+               show|"")
+                       cli_dhcpd_show ${proto} $@
+                       ;;
+               *)
+                       error "Unrecognized action: ${action}"
+                       cli_run_help network dhcpvN
+
+                       exit ${EXIT_ERROR}
+                       ;;
+       esac
+
+       exit ${EXIT_OK}
+}
+
+function cli_dhcpd_show() {
+       local proto=${1}
+       assert isset proto
+
+       local settings=$(dhcpd_settings ${proto})
+       assert isset settings
+
+       local ${settings}
+       dhcpd_global_settings_read ${proto}
+
+       cli_headline 1 "Dynamic Host Configuration Protocol Daemon for ${proto/ip/IP}"
+
+       case "${proto}" in
+               ipv6)
+                       cli_headline 2 "Lease times"
+                       if isinteger VALID_LIFETIME; then
+                               cli_print_fmt1 2 "Valid lifetime" "${VALID_LIFETIME}s"
+                       fi
+
+                       if isinteger PREFERRED_LIFETIME; then
+                               cli_print_fmt1 2 "Preferred lifetime" "${PREFERRED_LIFETIME}s"
+                       fi
+
+                       cli_space
+                       ;;
+               ipv4)
+                       cli_print_fmt1 1 "Authoritative" $(cli_print_enabled AUTHORITATIVE)
+                       cli_space
+
+                       cli_headline 2 "Lease times"
+                       cli_print_fmt1 2 "Default lease time" "${DEFAULT_LEASE_TIME}s"
+                       cli_print_fmt1 2 "Max. lease time" "${MAX_LEASE_TIME}s"
+
+                       if isset MIN_LEASE_TIME; then
+                               cli_print_fmt1 2 "Min. lease time" "${MIN_LEASE_TIME}s"
+                       fi
+
+                       cli_space
+                       ;;
+       esac
+
+       # Read the options.
+       local -A options
+       dhcpd_global_options_read ${proto} ${subnet_id}
+
+       # Print the options if any.
+       if [ ${#options[*]} -gt 0 ]; then
+               cli_headline 2 "Options"
+
+               local option
+               for option in $(dhcpd_options ${proto}); do
+                       [ -n "${options[${option}]}" ] || continue
+
+                       cli_print_fmt1 2 \
+                               "${option}" "${options[${option}]}"
+               done
+               cli_space
+       fi
+
+       # Subnets.
+       local subnets=$(dhcpd_subnet_list ${proto})
+       if [ -n "${subnets}" ]; then
+               cli_headline 2 "Subnets"
+               local subnet_id
+               for subnet_id in ${subnets}; do
+                       cli_dhcpd_subnet_show ${proto} ${subnet_id} 2
+               done
+       fi
+}
+
+function cli_dhcpd_subnet() {
+       local proto=${1}
+       shift
+
+       if cli_help_requested $@; then
+               cli_show_man network-dhcp-subnet
+               exit ${EXIT_OK}
+       fi
+
+       local action=${1}
+       shift
+
+       case "${action}" in
+               new)
+                       dhcpd_subnet_new ${proto} $@
+                       ;;
+               remove)
+                       dhcpd_subnet_remove ${proto} $@
+                       ;;
+               [0-9]*)
+                       local subnet_id=${action}
+
+                       if ! dhcpd_subnet_exists ${proto} ${subnet_id}; then
+                               error "The given subnet with ID ${subnet_id} does not exist."
+                               return ${EXIT_ERROR}
+                       fi
+
+                       # Update the action.
+                       action=${1}
+                       shift
+
+                       case "${action}" in
+                               edit)
+                                       dhcpd_subnet_edit ${proto} ${subnet_id} $@
+                                       local ret=$?
+
+                                       if [ ${ret} -eq ${EXIT_OK} ]; then
+                                               dhcpd_reload ${proto}
+                                       fi
+                                       exit ${ret}
+                                       ;;
+                               range)
+                                       cli_dhcpd_subnet_range ${proto} ${subnet_id} $@
+                                       exit $?
+                                       ;;
+                               show)
+                                       cli_dhcpd_subnet_show ${proto} ${subnet_id} $@
+                                       exit $?
+                                       ;;
+                               options)
+                                       cli_dhcpd_subnet_options ${proto} ${subnet_id} $@
+                                       exit $?
+                                       ;;
+                               *)
+                                       error "Unrecognized action: ${action}"
+                                       cli_run_help network dhcpvN subnet
+                                       exit ${EXIT_ERROR}
+                                       ;;
+                       esac
+                       ;;
+               show)
+                       local subnet_id
+                       for subnet_id in $(dhcpd_subnet_list ${proto}); do
+                               cli_dhcpd_subnet_show ${proto} ${subnet_id}
+                       done
+                       ;;
+               *)
+                       error "Unrecognized action: ${action}"
+                       cli_run_help network dhcpvN subnet
+
+                       exit ${EXIT_ERROR}
+                       ;;
+       esac
+
+       exit ${EXIT_OK}
+}
+
+function cli_dhcpd_subnet_range() {
+       local proto=${1}
+       assert isset proto
+       shift
+
+       local subnet_id=${1}
+       assert isset subnet_id
+       shift
+
+       local action=${1}
+       shift
+
+       case "${action}" in
+               new)
+                       dhcpd_subnet_range_new ${proto} ${subnet_id} $@
+                       exit $?
+                       ;;
+               remove)
+                       dhcpd_subnet_range_remove ${proto} ${subnet_id} $@
+                       exit $?
+                       ;;
+               *)
+                       error "Unrecognized action: ${action}"
+                       cli_run_help network dhcpvN subnet range
+                       exit ${EXIT_ERROR}
+                       ;;
+       esac
+}
+
+function cli_dhcpd_subnet_show() {
+       local proto=${1}
+       assert isset proto
+
+       local subnet_id=${2}
+       assert isset subnet_id
+
+       local level=${3}
+       isset level || level=0
+
+       local $(dhcpd_subnet_settings ${proto})
+
+       # Read in configuration settings.
+       dhcpd_subnet_read ${proto} ${subnet_id}
+
+       cli_headline $(( ${level} + 1 )) \
+               "DHCP${proto/ip/} subnet declaration #${subnet_id}"
+       cli_print_fmt1 $(( ${level} + 1 )) \
+               "Subnet" "${ADDRESS}/${PREFIX}"
+       cli_space
+
+       # Read the options.
+       local -A options
+       dhcpd_subnet_options_read ${proto} ${subnet_id}
+
+       # Print the options if any.
+       if [ ${#options[*]} -gt 0 ]; then
+               cli_headline $(( ${level} + 2 )) "Options"
+
+               local option
+               for option in $(dhcpd_subnet_options ${proto}); do
+                       [ -n "${options[${option}]}" ] || continue
+
+                       cli_print_fmt1 $(( ${level} + 2 )) \
+                               "${option}" "${options[${option}]}"
+               done
+               cli_space
+       fi
+
+       # Ranges.
+       cli_headline $(( ${level} + 2 )) "Ranges"
+
+       local ranges=$(dhcpd_subnet_range_list ${proto} ${subnet_id})
+       if isset ranges; then
+               local range_id $(dhcpd_subnet_range_settings ${proto})
+               for range_id in ${ranges}; do
+                       dhcpd_subnet_range_read ${proto} ${subnet_id} ${range_id}
+
+                       cli_print $(( ${level} + 2 )) \
+                               "#%d: %s - %s" ${range_id} ${START} ${END}
+               done
+       else
+               cli_print $(( ${level} + 2 )) "No ranges have been defined."
+       fi
+
+       cli_space
+}
+
+function cli_dhcpd_options() {
+       local proto=${1}
+       assert isset proto
+       shift
+
+       local subnet_id=${1}
+       assert isset subnet_id
+       shift
+
+       local valid_options=$(dhcpd_subnet_options ${proto})
+
+       local key val
+       while [ $# -gt 0 ]; do
+               case "${1}" in
+                       *=*)
+                               key=$(cli_get_key ${1})
+                               val=$(cli_get_val ${1})
+
+                               dhcpd_subnet_option_set ${proto} ${subnet_id} ${key} ${val}
+               esac
+       done
+}
+
 function cli_start() {
        if cli_help_requested $@; then
                cli_show_man network
@@ -793,6 +1092,11 @@ case "${action}" in
                cli_${action} $@
                ;;
 
+       # DHCP server configuration (automatically detects which protocol to use).
+       dhcpv6|dhcpv4)
+               cli_dhcpd ${action/dhcp/ip} $@
+               ;;
+
        ""|help|--help|-h)
                cli_help $@
                ;;