2 ###############################################################################
4 # IPFire.org - A linux based firewall #
5 # Copyright (C) 2012 IPFire Network Development Team #
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. #
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. #
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/>. #
20 ###############################################################################
22 DHCPV6D_CONFIG_FILE
="/etc/dhcp/dhcpd6.conf"
23 DHCPV4D_CONFIG_FILE
="/etc/dhcp/dhcpd.conf"
25 DHCPV6D_CONFIG_DIR
="${NETWORK_CONFIG_DIR}/dhcpd/ipv6"
26 DHCPV4D_CONFIG_DIR
="${NETWORK_CONFIG_DIR}/dhcpd/ipv4"
28 DHCPV6D_OPTIONS_FILE
="${DHCPV6D_CONFIG_DIR}/options"
29 DHCPV4D_OPTIONS_FILE
="${DHCPV4D_CONFIG_DIR}/options"
31 DHCPV6D_SETTINGS_FILE
="${DHCPV6D_CONFIG_DIR}/settings"
32 DHCPV4D_SETTINGS_FILE
="${DHCPV4D_CONFIG_DIR}/settings"
49 DHCPD_SUBNET_PREFIX
="subnet-"
50 #DHCPD_SUBNET_POOL_PREFIX="pool-"
51 DHCPD_SUBNET_RANGE_PREFIX
="range-"
53 DHCPD_SUBNET_SETTINGS
="ADDRESS PREFIX ROUTERS"
54 DHCPV6D_SUBNET_SETTINGS
="${DHCPD_SUBNET_SETTINGS}"
55 DHCPV4D_SUBNET_SETTINGS
="${DHCPD_SUBNET_SETTINGS}"
57 DHCPD_SUBNET_RANGE_SETTINGS
="START END"
58 DHCPV6D_SUBNET_RANGE_SETTINGS
="${DHCPD_SUBNET_RANGE_SETTINGS}"
59 DHCPV4D_SUBNET_RANGE_SETTINGS
="${DHCPD_SUBNET_RANGE_SETTINGS}"
72 dhcp-client-identifier \
74 dhcp-max-message-size \
87 DHCPD_SUBNET_OPTIONS
="${DHCPD_OPTIONS}"
88 DHCPV6D_SUBNET_OPTIONS
="${DHCPD_SUBNET_OPTIONS}"
89 DHCPV4D_SUBNET_OPTIONS
="${DHCPD_SUBNET_OPTIONS}"
91 # Defaults for DHCPv6.
92 DHCPV6D_PREFERRED_LIFETIME
=""
93 DHCPV6D_VALID_LIFETIME
="43200" # 12h
95 # Defaults for DHCPv4.
96 DHCPV4D_AUTHORITATIVE
="true"
97 DHCPV4D_DEFAULT_LEASE_TIME
="43200" # 12h
98 DHCPV4D_MAX_LEASE_TIME
="86400" # 24h
99 DHCPV4D_MIN_LEASE_TIME
=""
101 function dhcpd_service
() {
104 print
"dhcpd6.service"
107 print
"dhcpd.service"
110 print
"dhcpd6.service dhcp.service"
117 function dhcpd_start
() {
118 local services
=$
(dhcpd_service $@
)
121 for service
in ${services}; do
122 service_start
${service}
126 function dhcpd_stop
() {
127 local services
=$
(dhcpd_service $@
)
130 for service
in ${services}; do
131 service_stop
${service}
135 function dhcpd_restart
() {
136 # DHCP does not support a reload, so
138 local services
=$
(dhcpd_service $@
)
141 for service
in ${services}; do
142 service_restart
${service}
146 function dhcpd_reload
() {
150 function dhcpd_edit
() {
155 local settings
=$
(dhcpd_settings
${proto})
156 assert isset settings
159 dhcpd_global_settings_read
${proto}
163 _dhcpd_edit_ipv6 $@ ||
return $?
166 _dhcpd_edit_ipv4 $@ ||
return $?
170 dhcpd_global_settings_write
${proto}
173 function _dhcpd_edit_ipv4
() {
176 while [ $# -gt 0 ]; do
179 val
=$
(cli_get_val
${1})
184 AUTHORITATIVE
="false"
187 --default-lease-time=*)
188 DEFAULT_LEASE_TIME
=$
(cli_get_val
${1})
190 if ! isinteger DEFAULT_LEASE_TIME
; then
191 error
"Invalid value for --default-lease-time."
196 MAX_LEASE_TIME
=$
(cli_get_val
${1})
198 if ! isinteger MAX_LEASE_TIME
; then
199 error
"Invalid value for --max-lease-time."
204 MIN_LEASE_TIME
=$
(cli_get_val
${1})
206 if isset MIN_LEASE_TIME
; then
207 if ! isinteger MIN_LEASE_TIME
; then
208 error
"Invalid value for --min-lease-time."
214 error
"Unrecognized argument: ${1}"
221 if [ ${MAX_LEASE_TIME} -le ${DEFAULT_LEASE_TIME} ]; then
222 error
"The max. lease time must be higher than the default lease time."
227 function _dhcpd_edit_ipv6
() {
228 while [ $# -gt 0 ]; do
230 --preferred-lifetime=*)
231 PREFERRED_LIFETIME
=$
(cli_get_val
${1})
233 if ! isinteger PREFERRED_LIFETIME
; then
234 error
"Invalid value for --preferred-lifetime."
239 VALID_LIFETIME
=$
(cli_get_val
${1})
241 if ! isinteger VALID_LIFETIME
; then
242 error
"Invalid value for --valid-lifetime."
247 error
"Unrecognized argument: ${1}"
255 function dhcpd_settings_file
() {
261 print
"${DHCPV6D_SETTINGS_FILE}"
264 print
"${DHCPV4D_SETTINGS_FILE}"
271 function dhcpd_settings
() {
277 print
"${DHCPV6D_SETTINGS}"
280 print
"${DHCPV4D_SETTINGS}"
287 function dhcpd_options_file
() {
293 print
"${DHCPV6D_OPTIONS_FILE}"
296 print
"${DHCPV4D_OPTIONS_FILE}"
303 function dhcpd_options_list
() {
309 print
"DHCPV6D_OPTIONS"
312 print
"DHCPV4D_OPTIONS"
319 function dhcpd_options
() {
325 print
"${DHCPV6D_OPTIONS}"
328 print
"${DHCPV4D_OPTIONS}"
335 function dhcpd_global_settings_defaults
() {
339 local settings
=$
(dhcpd_settings
${proto})
340 assert isset settings
342 local prefix
="DHCPV${proto/ipv/}D_"
344 local setting setting_default
345 for setting
in ${settings}; do
346 setting_default
="${prefix}${setting}"
347 printf -v ${setting} "%s" "${!setting_default}"
351 function dhcpd_global_settings_read
() {
355 local file=$
(dhcpd_settings_file
${proto})
358 local settings
=$
(dhcpd_settings
${proto})
359 assert isset settings
361 dhcpd_global_settings_defaults
${proto}
362 config_read
${file} ${settings}
365 function dhcpd_global_settings_write
() {
369 local file=$
(dhcpd_settings_file
${proto})
372 local settings
=$
(dhcpd_settings
${proto})
373 assert isset settings
375 config_write
${file} ${settings}
378 function dhcpd_global_options_read
() {
382 local options_file
=$
(dhcpd_options_file
${proto})
383 local options_list
=$
(dhcpd_options_list
${proto})
385 config_read_array
${options_file} options
${!options_list}
387 # Check if domain-name is set.
388 if [ -z "${options["domain-name"]}" ]; then
389 options
["domain-name"]=$
(config_domainname
)
393 function dhcpd_subnet_path
() {
398 assert isset subnet_id
403 path
=${DHCPV6D_CONFIG_DIR}
406 path
=${DHCPV4D_CONFIG_DIR}
411 print
"${path}/${DHCPD_SUBNET_PREFIX}${subnet_id}"
415 function dhcpd_subnet_exists
() {
420 assert isset subnet_id
422 local path
=$
(dhcpd_subnet_path
${proto} ${subnet_id})
425 [ -d "${path}" ] && return ${EXIT_TRUE} || return ${EXIT_FALSE}
428 function dhcpd_subnet_match() {
435 local settings=$(dhcpd_subnet_settings ${proto})
436 assert isset settings
438 local subnet_id ${settings}
439 for subnet_id in $(dhcpd_subnet_list ${proto}); do
440 dhcpd_subnet_read ${proto} ${subnet_id}
442 ${proto}_addr_eq "${ADDRESS}/${PREFIX}" "${subnet}" \
443 && return ${EXIT_TRUE}
449 function dhcpd_new_subnet_id
() {
455 if ! dhcpd_subnet_exists
${proto} ${id}; then
466 function dhcpd_subnet_new
() {
471 # Allocate a new subnet id.
472 local subnet_id
=$
(dhcpd_new_subnet_id
${proto})
473 assert isinteger subnet_id
475 # Create directory structure.
476 local path
=$
(dhcpd_subnet_path
${proto} ${subnet_id})
480 touch ${path}/settings
482 dhcpd_subnet_edit
${proto} ${subnet_id} $@
485 # Remove the new subnet, when the edit method returned
487 if [ ${ret} -ne ${EXIT_OK} ]; then
488 dhcpd_subnet_remove
${proto} ${subnet_id}
492 function dhcpd_subnet_edit
() {
504 settings
=${DHCPV6D_SUBNET_SETTINGS}
507 settings
=${DHCPV4D_SUBNET_SETTINGS}
510 assert isset settings
513 # Read current settings.
514 dhcpd_subnet_read
${proto} ${id} ||
:
516 while [ $# -gt 0 ]; do
519 ADDRESS
=$
(cli_get_val
${1})
521 local prefix
=$
(ip_get_prefix
${ADDRESS})
522 if isset prefix
; then
524 ADDRESS
=$
(ip_split_prefix
${ADDRESS})
528 PREFIX
=$
(cli_get_val
${1})
531 ROUTERS
=$
(cli_get_val
${1})
534 error
"Unknown argument: ${1}"
543 if ! ipv6_is_valid
${ADDRESS}; then
544 error
"'${ADDRESS}' is not a valid IPv6 address."
548 if ! ipv6_prefix_is_valid
${PREFIX}; then
549 error
"'${PREFIX}' is not a valid IPv6 prefix."
554 if ! ipv4_is_valid
${ADDRESS}; then
555 error
"'${ADDRESS}' is not a valid IPv4 address."
559 if ! ipv4_prefix_is_valid
${PREFIX}; then
560 error
"'${PREFIX}' is not a valid IPv4 prefix."
566 # XXX Check for subnet collisions!
568 local file="$(dhcpd_subnet_path ${proto} ${id})/settings"
569 config_write
${file} ${settings}
572 function dhcpd_subnet_remove
() {
579 local path
=$
(dhcpd_subnet_path
${proto} ${id})
582 # Remove everything of this subnet.
586 function dhcpd_subnet_list
() {
590 local path
=$
(dhcpd_subnet_path
${proto} 0)
591 path
=$
(dirname ${path})
593 # Return an error of the directory does not exist.
594 [ -d "${path}" ] ||
return ${EXIT_ERROR}
597 for p
in ${path}/${DHCPD_SUBNET_PREFIX}*; do
598 [ -d "${p}" ] ||
continue
601 print
"${p:${#DHCPD_SUBNET_PREFIX}}"
605 function dhcpd_subnet_read
() {
612 local file="$(dhcpd_subnet_path ${proto} ${id})/settings"
616 function dhcpd_subnet_range_path
() {
621 assert isinteger subnet_id
624 assert isinteger range_id
626 print
"$(dhcpd_subnet_path ${proto} ${subnet_id})/${DHCPD_SUBNET_RANGE_PREFIX}${range_id}"
630 function dhcpd_subnet_range_settings
() {
635 print
"${DHCPV6D_SUBNET_RANGE_SETTINGS}"
638 print
"${DHCPV4D_SUBNET_RANGE_SETTINGS}"
645 function dhcpd_subnet_new_range_id
() {
650 assert isset subnet_id
654 path
=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} ${id})
655 if [ ! -f "${path}" ]; then
666 function dhcpd_subnet_range_new
() {
672 assert isset subnet_id
675 # Allocate a new range id.
676 local range_id
=$
(dhcpd_subnet_new_range_id
${proto} ${subnet_id})
677 assert isinteger range_id
679 local path
=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} ${range_id})
682 # Create file (as a placeholder).
685 dhcpd_subnet_range_edit
${proto} ${subnet_id} ${range_id} $@
688 if [ ${ret} -ne ${EXIT_OK} ]; then
689 dhcpd_subnet_range_remove
${proto} ${subnet_id} ${range_id}
696 function dhcpd_subnet_range_edit
() {
702 assert isset subnet_id
706 assert isset range_id
709 local ip_encode ip_is_valid
713 ip_encode
="ipv6_encode"
714 ip_is_valid
="ipv6_is_valid"
715 settings
=${DHCPV6D_SUBNET_RANGE_SETTINGS}
718 ip_encode
="ipv4_encode"
719 ip_is_valid
="ipv4_is_valid"
720 settings
=${DHCPV4D_SUBNET_RANGE_SETTINGS}
723 assert isset settings
726 while [ $# -gt 0 ]; do
729 START
=$
(cli_get_val
${1})
732 END
=$
(cli_get_val
${1})
735 error
"Unknown argument: ${1}"
742 if ! isset START
; then
743 error
"You need to set the start of the IP range with --start=..."
748 error
"You need to set the end of the IP range with --end=..."
753 for var
in START END
; do
754 if ! ${ip_is_valid} ${!var}; then
755 error
"'${!var}' is not a valid IP address."
760 # XXX currently, this check can only be performed for IPv4
761 if [ "${proto}" = "ipv4" ]; then
762 # Check if the end address is greater than the start address.
763 local start_encoded
=$
(${ip_encode} ${START})
764 local end_encoded
=$
(${ip_encode} ${END})
766 if [ ${start_encoded} -ge ${end_encoded} ]; then
767 error
"The start address of the range must be greater than the end address."
772 # Write the configuration to file.
773 local file=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} ${range_id})
776 config_write
${file} ${settings}
779 function dhcpd_subnet_range_remove
() {
780 local path
=$
(dhcpd_subnet_range_path $@
)
786 function dhcpd_subnet_range_list
() {
791 assert isset subnet_id
793 local path
=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} 0)
794 path
=$
(dirname ${path})
797 for p
in ${path}/${DHCPD_SUBNET_RANGE_PREFIX}*; do
798 [ -r "${p}" ] ||
continue
801 print
"${p:${#DHCPD_SUBNET_RANGE_PREFIX}}"
807 function dhcpd_subnet_range_read
() {
812 assert isset subnet_id
815 assert isset range_id
817 local file=$
(dhcpd_subnet_range_path
${proto} ${subnet_id} ${range_id})
821 function dhcpd_subnet_settings
() {
826 print
"${DHCPV6D_SUBNET_SETTINGS}"
829 print
"${DHCPV4D_SUBNET_SETTINGS}"
836 function dhcpd_subnet_options_file
() {
837 local path
=$
(dhcpd_subnet_path $@
)
840 print
"${path}/options"
843 function dhcpd_subnet_options_list
() {
848 print
"DHCPV6D_SUBNET_OPTIONS"
851 print
"DHCPV4D_SUBNET_OPTIONS"
858 function dhcpd_subnet_options
() {
863 print
"${DHCPV6D_SUBNET_OPTIONS}"
866 print
"${DHCPV4D_SUBNET_OPTIONS}"
873 function dhcpd_subnet_options_read
() {
878 assert isset subnet_id
880 local options_file
=$
(dhcpd_subnet_options_file
${proto} ${subnet_id})
881 local options_list
=$
(dhcpd_subnet_options_list
${proto})
883 _dhcpd_read_options
${options_file} ${options_list}
886 # Helper functions to create a DHCP configuration file.
887 function _dhcpd_write_options
() {
894 local options_list
=${3}
895 assert isset options_list
899 # Dump options array.
901 for key
in ${!options_list}; do
902 val
=${options[${key}]}
904 # Prepend dhcp6 on IPv6 options.
905 if [ "${proto}" = "ipv6" ]; then
910 if isinteger val
; then
912 elif isipaddress val
; then
915 fmt="option %s \"%s\";"
917 print
"${ident}${fmt}" "${key}" "${val}"
921 # Append an empty line when options have been written.
922 if [ -n "${!options[@]}" ]; then
927 function _dhcpd_read_options
() {
931 local options_list
=${2}
932 assert isset options_list
934 config_read_array
${file} options
${!options_list}
937 function _dhcpd_write_subnet
() {
942 assert isset subnet_id
947 # Check which settings we do expect.
951 settings
=${DHCPV6D_SUBNET_SETTINGS}
954 settings
=${DHCPV4D_SUBNET_SETTINGS}
957 assert isset settings
960 # Read configuration settings.
961 dhcpd_subnet_read
${proto} ${subnet_id}
963 print
"# Subnet declaration for subnet id ${subnet_id}." >> ${file}
966 print
"subnet6 ${ADDRESS}/${PREFIX} {" >> ${file}
969 local netmask=$(ipv4_get_netmask ${ADDRESS}/${PREFIX})
970 print "subnet
${ADDRESS} netmask ${netmask} {" >> ${file}
975 _dhcpd_write_subnet_options
${proto} ${subnet_id} ${file}
979 for range_id
in $
(dhcpd_subnet_range_list
${proto} ${subnet_id} ${range_id}); do
980 _dhcpd_write_subnet_range
${proto} ${subnet_id} ${range_id} ${file}
983 # End this subnet block.
984 print
"}\n" >> ${file}
989 function _dhcpd_write_subnet_options
() {
994 assert isset subnet_id
1000 local options_file
="$(dhcpd_subnet_path ${proto} ${subnet_id})/options"
1004 settings
=${DHCPV6D_SUBNET_SETTINGS}
1005 options_list
="DHCPV6D_OPTIONS"
1008 settings
=${DHCPV4D_SUBNET_SETTINGS}
1009 options_list
="DHCPV4D_OPTIONS"
1012 assert isset settings
1013 assert isset options_list
1016 dhcpd_subnet_read
${proto} ${subnet_id}
1019 _dhcpd_read_options
${options_file} ${options_list}
1021 # Fill in router, if not already set.
1022 if [ -z "${options["routers"]}" ]; then
1023 options
["routers"]=$
(_dhcpd_search_routers
${proto} "${ADDRESS}/${PREFIX}")
1026 _dhcpd_write_options ${proto} ${file} ${options_list} "\t"
1029 function _dhcpd_search_routers() {
1033 # Do nothing for IPv6 (yet?).
1034 [ "${proto}" = "ipv6
" ] && return ${EXIT_OK}
1042 for zone in $(zones_get_all); do
1043 addr=$(routing_db_get ${zone} ${proto} local-ip-address)
1044 isset addr || continue
1046 if ipv4_in_subnet ${addr} ${subnet}; then
1047 list_append routers $(ip_split_prefix ${addr})
1051 list_join routers ", "
1054 function _dhcpd_write_subnet_range() {
1058 local subnet_id=${2}
1059 assert isset subnet_id
1062 assert isset range_id
1067 local settings=$(dhcpd_subnet_range_settings ${proto})
1068 assert isset settings
1070 # Read the configuration settings.
1072 dhcpd_subnet_range_read ${proto} ${subnet_id} ${range_id}
1074 # Print the range line.
1075 print " # Range id ${range_id}." >> ${file}
1079 print
" range6 ${START} ${END};" >> ${file}
1082 print " range
${START} ${END};" >> ${file}
1090 function dhcpd_write_config
() {
1094 local file options_list
1097 file=${DHCPV6D_CONFIG_FILE}
1098 options_list
="DHCPV6D_OPTIONS"
1101 file=${DHCPV4D_CONFIG_FILE}
1102 options_list
="DHCPV4D_OPTIONS"
1106 assert isset options_list
1109 config_header
"DHCP ${proto} daemon configuration file" > ${file}
1112 if enabled AUTHORITATIVE
; then
1114 print
"# This is an authoritative DHCP server for this network."
1115 print
"authoritative;\n"
1119 print
"# This is NOT an authoritative DHCP server for this network."
1120 print
"not authoritative;\n"
1127 if ininteger VALID_LIFETIME
; then
1128 print
"default-lease-time %d;" "${VALID_LIFETIME}" >> ${file}
1131 if isinteger PREFERRED_LIFETIME
; then
1132 print
"preferred-lifetime %d;" "${PREFERRED_LIFETIME}" >> ${file}
1137 if isinteger DEFAULT_LEASE_TIME
; then
1138 print
"default-lease-time %d;" "${DEFAULT_LEASE_TIME}" >> ${file}
1141 if isinteger MAX_LEASE_TIME
; then
1142 print
"max-lease-time %d;" "${MAX_LEASE_TIME}" >> ${file}
1145 if isinteger MIN_LEASE_TIME
; then
1146 print
"min-lease-time %d;" "${MIN_LEASE_TIME}" >> ${file}
1151 # Write the options to file.
1153 dhcpd_global_options_read
${proto}
1154 _dhcpd_write_options
${proto} ${file} ${options_list}
1156 # Add all subnet declarations.
1158 for subnet_id
in $
(dhcpd_subnet_list
${proto}); do
1159 _dhcpd_write_subnet
${proto} ${subnet_id} ${file}