]> git.ipfire.org Git - people/ms/network.git/blame - src/functions/functions.dhcpd
DHCP: Allow configuring lease times in human-readable format
[people/ms/network.git] / src / functions / functions.dhcpd
CommitLineData
6c07160e
MT
1#!/bin/bash
2###############################################################################
3# #
4# IPFire.org - A linux based firewall #
5# Copyright (C) 2012 IPFire Network Development Team #
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
22DHCPV6D_CONFIG_FILE="/etc/dhcp/dhcpd6.conf"
23DHCPV4D_CONFIG_FILE="/etc/dhcp/dhcpd.conf"
24
25DHCPV6D_CONFIG_DIR="${NETWORK_CONFIG_DIR}/dhcpd/ipv6"
26DHCPV4D_CONFIG_DIR="${NETWORK_CONFIG_DIR}/dhcpd/ipv4"
27
28DHCPV6D_OPTIONS_FILE="${DHCPV6D_CONFIG_DIR}/options"
29DHCPV4D_OPTIONS_FILE="${DHCPV4D_CONFIG_DIR}/options"
30
31DHCPV6D_SETTINGS_FILE="${DHCPV6D_CONFIG_DIR}/settings"
32DHCPV4D_SETTINGS_FILE="${DHCPV4D_CONFIG_DIR}/settings"
33
34DHCPD_SETTINGS="\
35 AUTHORITATIVE
36"
37DHCPV6D_SETTINGS="\
38 ${DHCPD_SETTINGS} \
ea878018 39 PREFERRED_LIFETIME \
6c07160e
MT
40 VALID_LIFETIME
41"
42DHCPV4D_SETTINGS="\
43 ${DHCPD_SETTINGS} \
44 DEFAULT_LEASE_TIME \
45 MAX_LEASE_TIME \
46 MIN_LEASE_TIME \
47"
48
49DHCPD_SUBNET_PREFIX="subnet-"
50#DHCPD_SUBNET_POOL_PREFIX="pool-"
51DHCPD_SUBNET_RANGE_PREFIX="range-"
52
ea878018
MT
53DHCPD_SUBNET_SETTINGS="ADDRESS PREFIX"
54DHCPV6D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS} PREFIX_DELEGATION \
55 DELEGATED_PREFIX_FIRST DELEGATED_PREFIX_LAST DELEGATED_PREFIX_SIZE"
56DHCPV4D_SUBNET_SETTINGS="${DHCPD_SUBNET_SETTINGS} ROUTERS"
6c07160e
MT
57
58DHCPD_SUBNET_RANGE_SETTINGS="START END"
59DHCPV6D_SUBNET_RANGE_SETTINGS="${DHCPD_SUBNET_RANGE_SETTINGS}"
60DHCPV4D_SUBNET_RANGE_SETTINGS="${DHCPD_SUBNET_RANGE_SETTINGS}"
61
62DHCPV6D_OPTIONS="\
63 domain-search \
64 name-servers \
65"
66DHCPV4D_OPTIONS="\
67 all-subnets-local \
68 arp-cache-timeout \
69 bootfile-name \
70 broadcast-address \
71 default-ip-ttl \
72 default-tcp-ttl \
73 dhcp-client-identifier \
74 dhcp-lease-time \
75 dhcp-max-message-size \
76 dhcp-rebinding-time \
77 dhcp-renewal-time \
78 domain-name \
79 domain-name-servers \
80 domain-search \
81 interface-mtu \
82 ntp-servers \
83 root-path \
84 routers \
85 tftp-server-name \
86"
87
cf0d2484
MT
88DHCPV6D_SUBNET_OPTIONS="${DHCPV6D_OPTIONS}"
89DHCPV4D_SUBNET_OPTIONS="${DHCPV4D_OPTIONS}"
6c07160e 90
ea878018
MT
91# Global defaults
92DHCP_DEFAULT_DELEGATED_PREFIX_SIZE="64"
93
6c07160e
MT
94# Defaults for DHCPv6.
95DHCPV6D_PREFERRED_LIFETIME=""
96DHCPV6D_VALID_LIFETIME="43200" # 12h
97
98# Defaults for DHCPv4.
99DHCPV4D_AUTHORITATIVE="true"
100DHCPV4D_DEFAULT_LEASE_TIME="43200" # 12h
101DHCPV4D_MAX_LEASE_TIME="86400" # 24h
102DHCPV4D_MIN_LEASE_TIME=""
103
1c6a4e30 104dhcpd_service() {
6c07160e
MT
105 case "${1}" in
106 ipv6)
107 print "dhcpd6.service"
108 ;;
109 ipv4)
110 print "dhcpd.service"
111 ;;
112 "")
113 print "dhcpd6.service dhcp.service"
114 ;;
115 esac
116
117 return ${EXIT_OK}
118}
119
1c6a4e30 120dhcpd_start() {
6c07160e
MT
121 local services=$(dhcpd_service $@)
122
123 local service
124 for service in ${services}; do
125 service_start ${service}
126 done
127}
128
1c6a4e30 129dhcpd_stop() {
6c07160e
MT
130 local services=$(dhcpd_service $@)
131
132 local service
133 for service in ${services}; do
134 service_stop ${service}
135 done
136}
137
1c6a4e30 138dhcpd_restart() {
6c07160e
MT
139 # DHCP does not support a reload, so
140 # we retsart it.
141 local services=$(dhcpd_service $@)
142
143 local service
144 for service in ${services}; do
145 service_restart ${service}
146 done
147}
148
1c6a4e30 149dhcpd_reload() {
6c07160e
MT
150 dhcpd_restart $@
151}
152
1c6a4e30 153dhcpd_edit() {
6c07160e
MT
154 local proto=${1}
155 assert isset proto
156 shift
157
158 local settings=$(dhcpd_settings ${proto})
159 assert isset settings
160
161 local ${settings}
162 dhcpd_global_settings_read ${proto}
163
164 case "${proto}" in
165 ipv6)
166 _dhcpd_edit_ipv6 $@ || return $?
167 ;;
168 ipv4)
169 _dhcpd_edit_ipv4 $@ || return $?
170 ;;
171 esac
172
173 dhcpd_global_settings_write ${proto}
174}
175
1c6a4e30 176_dhcpd_edit_ipv4() {
6c07160e
MT
177 local val
178
179 while [ $# -gt 0 ]; do
180 case "${1}" in
181 --authoritative=*)
182 val=$(cli_get_val ${1})
183
184 if enabled val; then
185 AUTHORITATIVE="true"
186 else
187 AUTHORITATIVE="false"
188 fi
189 ;;
190 --default-lease-time=*)
b383499d
MT
191 local val=$(cli_get_val ${1})
192 DEFAULT_LEASE_TIME=$(parse_time ${val})
6c07160e
MT
193
194 if ! isinteger DEFAULT_LEASE_TIME; then
b383499d 195 error "Invalid value for --default-lease-time: ${val}"
6c07160e
MT
196 return ${EXIT_ERROR}
197 fi
198 ;;
199 --max-lease-time=*)
b383499d
MT
200 local val=$(cli_get_val ${1})
201 MAX_LEASE_TIME=$(parse_time ${val})
6c07160e
MT
202
203 if ! isinteger MAX_LEASE_TIME; then
b383499d 204 error "Invalid value for --max-lease-time: ${val}"
6c07160e
MT
205 return ${EXIT_ERROR}
206 fi
207 ;;
208 --min-lease-time=*)
b383499d
MT
209 local val=$(cli_get_val ${1})
210 MIN_LEASE_TIME=$(parse_time ${val})
6c07160e
MT
211
212 if isset MIN_LEASE_TIME; then
213 if ! isinteger MIN_LEASE_TIME; then
b383499d 214 error "Invalid value for --min-lease-time: ${val}"
6c07160e
MT
215 return ${EXIT_ERROR}
216 fi
217 fi
218 ;;
219 *)
220 error "Unrecognized argument: ${1}"
221 return ${EXIT_ERROR}
222 ;;
223 esac
224 shift
225 done
226
227 if [ ${MAX_LEASE_TIME} -le ${DEFAULT_LEASE_TIME} ]; then
228 error "The max. lease time must be higher than the default lease time."
229 return ${EXIT_ERROR}
230 fi
231}
232
1c6a4e30 233_dhcpd_edit_ipv6() {
6c07160e
MT
234 while [ $# -gt 0 ]; do
235 case "${1}" in
236 --preferred-lifetime=*)
b383499d
MT
237 local val=$(cli_get_val ${1})
238 PREFERRED_LIFETIME=$(parse_time ${val})
6c07160e
MT
239
240 if ! isinteger PREFERRED_LIFETIME; then
b383499d 241 error "Invalid value for --preferred-lifetime: ${val}"
6c07160e
MT
242 return ${EXIT_ERROR}
243 fi
244 ;;
245 --valid-lifetime=*)
b383499d
MT
246 local val=$(cli_get_val ${1})
247 VALID_LIFETIME=$(parse_time ${val})
6c07160e
MT
248
249 if ! isinteger VALID_LIFETIME; then
b383499d 250 error "Invalid value for --valid-lifetime: ${val}"
6c07160e
MT
251 return ${EXIT_ERROR}
252 fi
253 ;;
254 *)
255 error "Unrecognized argument: ${1}"
256 return ${EXIT_ERROR}
257 ;;
258 esac
259 shift
260 done
261}
262
1c6a4e30 263dhcpd_settings_file() {
6c07160e
MT
264 local proto=${1}
265 assert isset proto
266
267 case "${proto}" in
268 ipv6)
269 print "${DHCPV6D_SETTINGS_FILE}"
270 ;;
271 ipv4)
272 print "${DHCPV4D_SETTINGS_FILE}"
273 ;;
274 esac
275
276 return ${EXIT_OK}
277}
278
1c6a4e30 279dhcpd_settings() {
6c07160e
MT
280 local proto=${1}
281 assert isset proto
282
283 case "${proto}" in
284 ipv6)
285 print "${DHCPV6D_SETTINGS}"
286 ;;
287 ipv4)
288 print "${DHCPV4D_SETTINGS}"
289 ;;
290 esac
291
292 return ${EXIT_OK}
293}
294
1c6a4e30 295dhcpd_options_file() {
6c07160e
MT
296 local proto=${1}
297 assert isset proto
298
299 case "${proto}" in
300 ipv6)
301 print "${DHCPV6D_OPTIONS_FILE}"
302 ;;
303 ipv4)
304 print "${DHCPV4D_OPTIONS_FILE}"
305 ;;
306 esac
307
308 return ${EXIT_OK}
309}
310
1c6a4e30 311dhcpd_options_list() {
6c07160e
MT
312 local proto=${1}
313 assert isset proto
314
315 case "${proto}" in
316 ipv6)
317 print "DHCPV6D_OPTIONS"
318 ;;
319 ipv4)
320 print "DHCPV4D_OPTIONS"
321 ;;
322 esac
323
324 return ${EXIT_OK}
325}
326
1c6a4e30 327dhcpd_options() {
6c07160e
MT
328 local proto=${1}
329 assert isset proto
330
331 case "${proto}" in
332 ipv6)
333 print "${DHCPV6D_OPTIONS}"
334 ;;
335 ipv4)
336 print "${DHCPV4D_OPTIONS}"
337 ;;
338 esac
339
340 return ${EXIT_OK}
341}
342
1c6a4e30 343dhcpd_global_settings_list() {
cc02f6be
MT
344 local proto="${1}"
345 assert isset proto
346
347 dhcpd_settings "${proto}"
348}
349
1c6a4e30 350dhcpd_global_settings_defaults() {
6c07160e
MT
351 local proto=${1}
352 assert isset proto
353
354 local settings=$(dhcpd_settings ${proto})
355 assert isset settings
356
357 local prefix="DHCPV${proto/ipv/}D_"
358
359 local setting setting_default
360 for setting in ${settings}; do
361 setting_default="${prefix}${setting}"
362 printf -v ${setting} "%s" "${!setting_default}"
363 done
364}
365
1c6a4e30 366dhcpd_global_settings_read() {
6c07160e
MT
367 local proto=${1}
368 assert isset proto
369
370 local file=$(dhcpd_settings_file ${proto})
371 assert isset file
372
373 local settings=$(dhcpd_settings ${proto})
374 assert isset settings
375
376 dhcpd_global_settings_defaults ${proto}
e9df08ad 377 settings_read ${file} ${settings}
6c07160e
MT
378}
379
1c6a4e30 380dhcpd_global_settings_write() {
6c07160e
MT
381 local proto=${1}
382 assert isset proto
383
384 local file=$(dhcpd_settings_file ${proto})
385 assert isset file
386
387 local settings=$(dhcpd_settings ${proto})
388 assert isset settings
389
e9df08ad 390 settings_write ${file} ${settings}
6c07160e
MT
391}
392
1c6a4e30 393dhcpd_global_options_read() {
6c07160e
MT
394 local proto=${1}
395 assert isset proto
396
397 local options_file=$(dhcpd_options_file ${proto})
398 local options_list=$(dhcpd_options_list ${proto})
399
e9df08ad 400 settings_read_array ${options_file} options ${!options_list}
6c07160e
MT
401
402 # Check if domain-name is set.
403 if [ -z "${options["domain-name"]}" ]; then
404 options["domain-name"]=$(config_domainname)
405 fi
406}
407
94784eb9
MT
408dhcpd_subnet_escape() {
409 assert [ $# -eq 1 ]
410
411 local subnet="${1}"
412
413 # Escape any slashes
414 subnet="${subnet//\//-}"
415
416 print "${subnet}"
417}
418
419dhcpd_subnet_unescape() {
420 assert [ $# -eq 1 ]
421
422 local subnet="${1}"
423
424 # Unescape any slashes
425 subnet="${subnet//-/\/}"
426
427 print "${subnet}"
428}
429
1c6a4e30 430dhcpd_subnet_path() {
94784eb9 431 assert [ $# -ge 1 -a $# -le 2 ]
6c07160e 432
94784eb9
MT
433 local proto=${1}
434 local subnet=${2}
6c07160e
MT
435
436 local path
437 case "${proto}" in
438 ipv6)
439 path=${DHCPV6D_CONFIG_DIR}
440 ;;
441 ipv4)
442 path=${DHCPV4D_CONFIG_DIR}
443 ;;
444 esac
445 assert isset path
446
94784eb9
MT
447 if ! isset subnet; then
448 print "${path}"
449 return ${EXIT_OK}
450 fi
451
452 # Escape subnet
453 subnet="$(dhcpd_subnet_escape ${subnet})"
454
455 # Add path prefix
456 subnet="${DHCPD_SUBNET_PREFIX}${subnet}"
457
458 print "${path}/${subnet}"
6c07160e
MT
459 return ${EXIT_OK}
460}
461
1c6a4e30 462dhcpd_subnet_exists() {
6c07160e
MT
463 local proto=${1}
464 assert isset proto
465
94784eb9
MT
466 local subnet=${2}
467 assert isset subnet
6c07160e 468
94784eb9 469 local path=$(dhcpd_subnet_path ${proto} ${subnet})
6c07160e
MT
470 assert isset path
471
472 [ -d "${path}" ] && return ${EXIT_TRUE} || return ${EXIT_FALSE}
473}
474
1c6a4e30 475dhcpd_subnet_match() {
6c07160e
MT
476 local proto=${1}
477 assert isset proto
478
479 local subnet=${2}
480 assert isset subnet
481
482 local settings=$(dhcpd_subnet_settings ${proto})
483 assert isset settings
484
94784eb9
MT
485 local _subnet ${settings}
486 for _subnet in $(dhcpd_subnet_list ${proto}); do
487 dhcpd_subnet_read ${proto} ${_subnet}
6c07160e
MT
488
489 ${proto}_addr_eq "${ADDRESS}/${PREFIX}" "${subnet}" \
490 && return ${EXIT_TRUE}
491 done
492
493 return ${EXIT_FALSE}
494}
495
94784eb9
MT
496dhcpd_subnet_exists() {
497 dhcpd_subnet_match $@
6c07160e
MT
498}
499
1c6a4e30 500dhcpd_subnet_new() {
6c07160e
MT
501 local proto=${1}
502 assert isset proto
503 shift
504
94784eb9 505 dhcpd_subnet_edit ${proto} "new" $@
6c07160e
MT
506}
507
1c6a4e30 508dhcpd_subnet_edit() {
94784eb9
MT
509 assert [ $# -ge 2 ]
510
6c07160e 511 local proto=${1}
94784eb9
MT
512 local subnet=${2}
513 shift 2
6c07160e 514
94784eb9
MT
515 local mode="edit"
516 if [ "${subnet}" = "new" ]; then
517 mode="new"
518 subnet=""
519 fi
6c07160e
MT
520
521 local settings
522 case "${proto}" in
523 ipv6)
524 settings=${DHCPV6D_SUBNET_SETTINGS}
525 ;;
526 ipv4)
527 settings=${DHCPV4D_SUBNET_SETTINGS}
528 ;;
529 esac
530 assert isset settings
531 local ${settings}
532
94784eb9
MT
533 # Read current settings
534 if [ "${mode}" = "edit" ]; then
535 dhcpd_subnet_read ${proto} ${subnet}
536 fi
6c07160e
MT
537
538 while [ $# -gt 0 ]; do
94784eb9 539 case "${proto},${mode},${1}" in
ea878018 540 # Common options
94784eb9
MT
541 ipv6,new,*:*/*|ipv4,new,*.*.*.*/*)
542 local subnet="$(cli_get_val ${1})"
6c07160e 543
94784eb9
MT
544 ADDRESS="$(ip_split_prefix ${subnet})"
545 PREFIX="$(ip_get_prefix ${subnet})"
6c07160e 546 ;;
ea878018
MT
547
548 # IPv6 options
549
94784eb9 550 ipv6,*,--delegated-prefix=*)
ea878018
MT
551 local subnet="$(cli_get_val "${1}")"
552 if [[ -n "${subnet}" ]]; then
553 local delegated_prefix_first="${subnet%-*}"
554 local delegated_prefix_last="${subnet#*-}"
555
556 # Check for correct syntax
557 if ! isset delegated_prefix_first || ! isset delegated_prefix_last; then
558 error "--delegated-prefix= must be formatted like 2001:db8:aaaa::-2001:db8:bbbb::"
559 return ${EXIT_ERROR}
560 fi
561
562 # Check if the addresses are correct
563 local addr
564 for addr in ${delegated_prefix_first} ${delegated_prefix_last}; do
565 if ! ipv6_is_valid "${addr}"; then
566 error "Invalid IP address: ${addr}"
567 return ${EXIT_ERROR}
568 fi
569 done
570
571 # Make sure this is a range
572 if ! ipv6_addr_gt "${delegated_prefix_last}" "${delegated_prefix_first}"; then
573 error "--delegated-prefix: The second address must be larger than the first one"
574 return ${EXIT_ERROR}
575 fi
576
577 PREFIX_DELEGATION="on"
578 DELEGATED_PREFIX_FIRST="${delegated_prefix_first}"
579 DELEGATED_PREFIX_LAST="${delegated_prefix_last}"
580
581 # Prefix delegation has been disabled
582 else
583 PREFIX_DELEGATION="off"
584 fi
585 ;;
586
94784eb9 587 ipv6,*,--delegated-prefix-size=*)
ea878018
MT
588 local prefix_size="$(cli_get_val "${1}")"
589
590 if ipv6_prefix_size_is_valid_for_delegation "${prefix_size}"; then
591 DELEGATED_PREFIX_SIZE="${prefix_size}"
592 else
593 error "Invalid prefix size for prefix delegation: ${prefix_size}"
594 return ${EXIT_ERROR}
595 fi
596 ;;
597
94784eb9 598
ea878018
MT
599 # IPv4 options
600
94784eb9 601 ipv4,*,--routers=*)
6c07160e
MT
602 ROUTERS=$(cli_get_val ${1})
603 ;;
94784eb9 604
6c07160e
MT
605 *)
606 error "Unknown argument: ${1}"
607 return ${EXIT_ERROR}
608 ;;
609 esac
610 shift
611 done
612
94784eb9
MT
613 if ! ${proto}_is_valid ${ADDRESS} || ! ${proto}_prefix_is_valid ${PREFIX}; then
614 error "Invalid subnet: ${ADDRESS}/${PREFIX}"
615 return ${EXIT_ERROR}
616 fi
6c07160e 617
94784eb9 618 # XXX Check for subnet collisions!
6c07160e 619
94784eb9
MT
620 case "${mode}" in
621 new)
622 if dhcpd_subnet_exists ${proto} "${ADDRESS}/${PREFIX}"; then
623 error "DHCP subnet configuration already exists for subnet ${ADDRESS}/${PREFIX}"
6c07160e
MT
624 return ${EXIT_ERROR}
625 fi
626 ;;
627 esac
628
94784eb9 629 local file="$(dhcpd_subnet_path ${proto} "${ADDRESS}/${PREFIX}")/settings"
e9df08ad 630 settings_write ${file} ${settings}
6c07160e
MT
631}
632
1c6a4e30 633dhcpd_subnet_remove() {
94784eb9 634 assert [ $# -eq 2 ]
6c07160e 635
94784eb9
MT
636 local proto=${1}
637 local subnet=${2}
6c07160e 638
94784eb9 639 local path=$(dhcpd_subnet_path ${proto} ${subnet})
6c07160e
MT
640 assert isset path
641
642 # Remove everything of this subnet.
643 rm -rf ${path}
644}
645
1c6a4e30 646dhcpd_subnet_list() {
6c07160e
MT
647 local proto=${1}
648 assert isset proto
649
94784eb9 650 local path=$(dhcpd_subnet_path ${proto})
6c07160e
MT
651
652 # Return an error of the directory does not exist.
653 [ -d "${path}" ] || return ${EXIT_ERROR}
654
655 local p
656 for p in ${path}/${DHCPD_SUBNET_PREFIX}*; do
657 [ -d "${p}" ] || continue
658
659 p=$(basename ${p})
94784eb9
MT
660 p="${p:${#DHCPD_SUBNET_PREFIX}}"
661
662 # Return unescaped subnet
663 dhcpd_subnet_unescape "${p}"
6c07160e
MT
664 done
665}
666
1c6a4e30 667dhcpd_subnet_read() {
6c07160e
MT
668 local proto=${1}
669 assert isset proto
670
94784eb9
MT
671 local subnet=${2}
672 assert isset subnet
6c07160e 673
94784eb9 674 local file="$(dhcpd_subnet_path ${proto} ${subnet})/settings"
e9df08ad 675 settings_read ${file}
6c07160e
MT
676}
677
1c6a4e30 678dhcpd_subnet_range_path() {
f3ac1159 679 assert [ $# -ge 2 -a $# -le 3 ]
6c07160e 680
94784eb9
MT
681 local proto=${1}
682 local subnet=${2}
6c07160e 683
f3ac1159
MT
684 local range=${3}
685 if ! isset range; then
686 dhcpd_subnet_path ${proto} ${subnet}
687 return $?
688 fi
689
690 # Add prefix
691 range="${DHCPD_SUBNET_RANGE_PREFIX}${range}"
692
693 print "$(dhcpd_subnet_path ${proto} ${subnet})/${range}"
6c07160e
MT
694 return ${EXIT_OK}
695}
696
1c6a4e30 697dhcpd_subnet_range_settings() {
6c07160e
MT
698 local proto=${1}
699
700 case "${proto}" in
701 ipv6)
702 print "${DHCPV6D_SUBNET_RANGE_SETTINGS}"
703 ;;
704 ipv4)
705 print "${DHCPV4D_SUBNET_RANGE_SETTINGS}"
706 ;;
707 esac
708
709 return ${EXIT_OK}
710}
711
1c6a4e30 712dhcpd_subnet_range_new() {
6c07160e
MT
713 local proto=${1}
714 assert isset proto
715 shift
716
94784eb9
MT
717 local subnet=${1}
718 assert isset subnet
6c07160e
MT
719 shift
720
6c07160e
MT
721 local settings
722 case "${proto}" in
723 ipv6)
6c07160e
MT
724 settings=${DHCPV6D_SUBNET_RANGE_SETTINGS}
725 ;;
726 ipv4)
6c07160e
MT
727 settings=${DHCPV4D_SUBNET_RANGE_SETTINGS}
728 ;;
729 esac
730 assert isset settings
52f69c52 731 local range ${settings}
6c07160e
MT
732
733 while [ $# -gt 0 ]; do
734 case "${1}" in
f3ac1159 735 *-*)
52f69c52 736 range=${1}
f3ac1159
MT
737
738 START="${range%-*}"
739 END="${range#*-}"
6c07160e
MT
740 ;;
741 *)
742 error "Unknown argument: ${1}"
743 return ${EXIT_ERROR}
744 ;;
745 esac
746 shift
747 done
748
6c07160e
MT
749 local var
750 for var in START END; do
f3ac1159
MT
751 if ! ${proto}_is_valid ${!var}; then
752 error "'${!var}' is not a valid IP address"
6c07160e
MT
753 return ${EXIT_ERROR}
754 fi
755 done
756
7da6a387
MT
757 # Check if the end address is larger than the start address
758 if ! ${proto}_addr_gt "${END}" "${START}"; then
759 error "The end address of the range must be greater than the start address"
760 return ${EXIT_ERROR}
6c07160e
MT
761 fi
762
089e7410
MT
763 # Check if range already exists
764 if dhcpd_subnet_range_exists ${proto} ${subnet} ${range}; then
765 error "DHCP subnet range ${range} already exists"
766 return ${EXIT_ERROR}
767 fi
768
ae02c40e
MT
769 # Search for any overlaps
770 local overlaps=$(dhcpd_subnet_range_overlaps ${proto} ${subnet} ${range})
771 if isset overlaps; then
772 error "DHCP subnet range ${range} overlaps with ${overlaps}"
773 return ${EXIT_ERROR}
774 fi
775
6c07160e 776 # Write the configuration to file.
52f69c52 777 local file=$(dhcpd_subnet_range_path ${proto} ${subnet} ${range})
6c07160e
MT
778 assert isset file
779
e9df08ad 780 settings_write ${file} ${settings}
f3ac1159 781
52f69c52 782 log INFO "DHCP subnet range ${range} created"
f2c9ad8c 783
f3ac1159 784 return ${EXIT_OK}
6c07160e
MT
785}
786
1c6a4e30 787dhcpd_subnet_range_remove() {
1b9e7008
MT
788 assert [ $# -eq 3 ]
789
790 local proto=${1}
791 local subnet=${2}
792 local range=${3}
793
794 if ! dhcpd_subnet_range_exists ${proto} ${subnet} ${range}; then
795 error "DHCP subnet range ${range} does not exist"
796 return ${EXIT_ERROR}
797 fi
798
799 local path=$(dhcpd_subnet_range_path ${proto} ${subnet} ${range})
6c07160e
MT
800 assert isset path
801
802 rm -f ${path}
1b9e7008
MT
803
804 log INFO "DHCP subnet range ${range} removed"
805 return ${EXIT_OK}
6c07160e
MT
806}
807
1c6a4e30 808dhcpd_subnet_range_list() {
1b9e7008 809 assert [ $# -eq 2 ]
6c07160e 810
1b9e7008 811 local proto=${1}
94784eb9 812 local subnet=${2}
6c07160e 813
f3ac1159 814 local path=$(dhcpd_subnet_range_path ${proto} ${subnet})
6c07160e
MT
815
816 local p
817 for p in ${path}/${DHCPD_SUBNET_RANGE_PREFIX}*; do
818 [ -r "${p}" ] || continue
819
820 p=$(basename ${p})
821 print "${p:${#DHCPD_SUBNET_RANGE_PREFIX}}"
822 done
823
824 return ${EXIT_OK}
825}
826
1c6a4e30 827dhcpd_subnet_range_read() {
f3ac1159 828 assert [ $# -eq 3 ]
6c07160e 829
f3ac1159 830 local proto=${1}
94784eb9 831 local subnet=${2}
f3ac1159 832 local range=${3}
6c07160e 833
f3ac1159 834 local file=$(dhcpd_subnet_range_path ${proto} ${subnet} ${range})
e9df08ad 835 settings_read ${file}
6c07160e
MT
836}
837
1b9e7008
MT
838dhcpd_subnet_range_exists() {
839 assert [ $# -eq 3 ]
840
841 local proto=${1}
842 local subnet=${2}
843 local range=${3}
844
845 local start=${range%-*}
846 local end=${range#*-}
847
848 assert isset start
849 assert isset end
850
851 local settings=$(dhcpd_subnet_range_settings ${proto})
852
853 local r ${settings}
854 for r in $(dhcpd_subnet_range_list ${proto} ${subnet}); do
855 dhcpd_subnet_range_read ${proto} ${subnet} ${r}
856
857 # If start and end addresses equal we got a match
858 if ${proto}_addr_eq "${START}" "${start}" && ${proto}_addr_eq "${END}" "${end}"; then
859 return ${EXIT_TRUE}
860 fi
861 done
862
863 return ${EXIT_FALSE}
864}
865
ae02c40e
MT
866dhcpd_subnet_range_overlaps() {
867 assert [ $# -eq 3 ]
868
869 local proto=${1}
870 local subnet=${2}
871 local range=${3}
872
873 local start=${range%-*}
874 local end=${range#*-}
875
876 assert isset start
877 assert isset end
878
879 local settings=$(dhcpd_subnet_range_settings ${proto})
880
881 local r ${settings}
882 for r in $(dhcpd_subnet_range_list ${proto} ${subnet}); do
883 dhcpd_subnet_range_read ${proto} ${subnet} ${r}
884
885 # Check if the new range is a sub-range of any existing range
886
887 # Check if the start address is somewhere in this range
888 if ${proto}_addr_ge ${START} ${start} && ${proto}_addr_le ${START} ${end}; then
889 print "${r}"
890 return ${EXIT_TRUE}
891 fi
892
893 # Check if the end address is somewhere in this range
894 if ${proto}_addr_ge ${END} ${start} && ${proto}_addr_le ${END} ${end}; then
895 print "${r}"
896 return ${EXIT_TRUE}
897 fi
898
899 # Check if any existing range is a sub-range of the new range
900
901 # Check if the start address is somewhere in this range
902 if ${proto}_addr_ge ${start} ${START} && ${proto}_addr_le ${start} ${END}; then
903 print "${r}"
904 return ${EXIT_TRUE}
905 fi
906
907 # Check if the end address is somewhere in this range
908 if ${proto}_addr_ge ${end} ${START} && ${proto}_addr_le ${end} ${END}; then
909 print "${r}"
910 return ${EXIT_TRUE}
911 fi
912 done
913
914 return ${EXIT_FALSE}
915}
916
1c6a4e30 917dhcpd_subnet_settings() {
6c07160e
MT
918 local proto=${1}
919
920 case "${proto}" in
921 ipv6)
922 print "${DHCPV6D_SUBNET_SETTINGS}"
923 ;;
924 ipv4)
925 print "${DHCPV4D_SUBNET_SETTINGS}"
926 ;;
927 esac
928
929 return ${EXIT_OK}
930}
931
1c6a4e30 932dhcpd_subnet_options_file() {
6c07160e
MT
933 local path=$(dhcpd_subnet_path $@)
934 assert isset path
935
936 print "${path}/options"
937}
938
1c6a4e30 939dhcpd_subnet_options_list() {
6c07160e 940 local proto=${1}
cc02f6be 941 assert isset proto
6c07160e
MT
942
943 case "${proto}" in
944 ipv6)
945 print "${DHCPV6D_SUBNET_OPTIONS}"
946 ;;
947 ipv4)
948 print "${DHCPV4D_SUBNET_OPTIONS}"
949 ;;
950 esac
951
952 return ${EXIT_OK}
953}
954
1c6a4e30 955dhcpd_subnet_options_read() {
6c07160e
MT
956 local proto=${1}
957 assert isset proto
958
94784eb9
MT
959 local subnet=${2}
960 assert isset subnet
6c07160e 961
94784eb9 962 local options_file=$(dhcpd_subnet_options_file ${proto} ${subnet})
6c07160e
MT
963 local options_list=$(dhcpd_subnet_options_list ${proto})
964
965 _dhcpd_read_options ${options_file} ${options_list}
966}
967
968# Helper functions to create a DHCP configuration file.
1c6a4e30 969_dhcpd_write_options() {
6c07160e
MT
970 local proto=${1}
971 assert isset proto
972
973 local file=${2}
974 assert isset file
975
976 local options_list=${3}
977 assert isset options_list
978
979 local ident=${4}
980
ea878018
MT
981 print "${ident}# Options" >> ${file}
982
6c07160e
MT
983 # Dump options array.
984 local key val fmt
985 for key in ${!options_list}; do
986 val=${options[${key}]}
987
ea878018
MT
988 # Skip the rest if val is empty
989 isset val || continue
990
991 # Enable raw formatting (i.e. quote the value?)
992 local raw="false"
993
994 # Update the formatting of some options
995 case "${key}" in
996 name-servers)
997 val="$(list_join val ", ")"
998 raw="true"
999 ;;
1000 esac
1001
6c07160e
MT
1002 # Prepend dhcp6 on IPv6 options.
1003 if [ "${proto}" = "ipv6" ]; then
1004 key="dhcp6.${key}"
1005 fi
1006
ea878018
MT
1007 if isinteger val; then
1008 fmt="option %s %d;"
1009 elif enabled raw || isipaddress val; then
1010 fmt="option %s %s;"
1011 else
1012 fmt="option %s \"%s\";"
6c07160e 1013 fi
ea878018
MT
1014
1015 print "${ident}${fmt}" "${key}" "${val}"
6c07160e
MT
1016 done >> ${file}
1017
ea878018
MT
1018 # Empty line
1019 print >> ${file}
6c07160e
MT
1020}
1021
1c6a4e30 1022_dhcpd_read_options() {
6c07160e
MT
1023 local file=${1}
1024 assert isset file
1025
1026 local options_list=${2}
1027 assert isset options_list
1028
4f366759 1029 settings_read_array ${file} options ${!options_list}
6c07160e
MT
1030}
1031
1c6a4e30 1032_dhcpd_write_subnet() {
94784eb9 1033 assert [ $# -eq 3 ]
6c07160e 1034
94784eb9
MT
1035 local proto=${1}
1036 local subnet=${2}
6c07160e 1037 local file=${3}
6c07160e
MT
1038
1039 # Check which settings we do expect.
1040 local settings
1041 case "${proto}" in
1042 ipv6)
1043 settings=${DHCPV6D_SUBNET_SETTINGS}
1044 ;;
1045 ipv4)
1046 settings=${DHCPV4D_SUBNET_SETTINGS}
1047 ;;
1048 esac
1049 assert isset settings
1050 local ${settings}
1051
1052 # Read configuration settings.
94784eb9 1053 dhcpd_subnet_read ${proto} ${subnet}
6c07160e 1054
94784eb9 1055 print "# Subnet declaration" >> ${file}
6c07160e
MT
1056 case "${proto}" in
1057 ipv6)
1058 print "subnet6 ${ADDRESS}/${PREFIX} {" >> ${file}
1059 ;;
1060 ipv4)
13a6e69f 1061 local netmask="$(ipv4_prefix2netmask "${PREFIX}")"
6c07160e
MT
1062 print "subnet ${ADDRESS} netmask ${netmask} {" >> ${file}
1063 ;;
1064 esac
1065
1066 # Add options.
94784eb9 1067 _dhcpd_write_subnet_options ${proto} ${subnet} ${file}
6c07160e 1068
ea878018
MT
1069 # Prefix Delegation for IPv6
1070 if [[ "${proto}" = "ipv6" ]]; then
94784eb9 1071 _dhcpd_write_subnet_pd "${subnet}" "${file}"
ea878018
MT
1072 fi
1073
6c07160e 1074 # Add the ranges.
f3ac1159
MT
1075 local range
1076 for range in $(dhcpd_subnet_range_list ${proto} ${subnet} ${range}); do
1077 _dhcpd_write_subnet_range ${proto} ${subnet} ${range} ${file}
6c07160e
MT
1078 done
1079
1080 # End this subnet block.
1081 print "}\n" >> ${file}
1082
1083 return ${EXIT_OK}
1084}
1085
1c6a4e30 1086_dhcpd_write_subnet_options() {
94784eb9 1087 assert [ $# -eq 3 ]
6c07160e 1088
94784eb9
MT
1089 local proto=${1}
1090 local subnet=${2}
6c07160e 1091 local file=${3}
6c07160e
MT
1092
1093 local settings
94784eb9 1094 local options_file="$(dhcpd_subnet_path ${proto} ${subnet})/options"
6c07160e
MT
1095 local options_list
1096 case "${proto}" in
1097 ipv6)
1098 settings=${DHCPV6D_SUBNET_SETTINGS}
1099 options_list="DHCPV6D_OPTIONS"
1100 ;;
1101 ipv4)
1102 settings=${DHCPV4D_SUBNET_SETTINGS}
1103 options_list="DHCPV4D_OPTIONS"
1104 ;;
1105 esac
1106 assert isset settings
1107 assert isset options_list
1108
1109 local ${settings}
94784eb9 1110 dhcpd_subnet_read ${proto} ${subnet}
6c07160e
MT
1111
1112 local -A options
1113 _dhcpd_read_options ${options_file} ${options_list}
1114
1115 # Fill in router, if not already set.
1116 if [ -z "${options["routers"]}" ]; then
1117 options["routers"]=$(_dhcpd_search_routers ${proto} "${ADDRESS}/${PREFIX}")
1118 fi
1119
1120 _dhcpd_write_options ${proto} ${file} ${options_list} "\t"
1121}
1122
ea878018
MT
1123_dhcpd_write_subnet_pd() {
1124 # Nothing to do if prefix delegation is not enabled
1125 enabled PREFIX_DELEGATION || return ${EXIT_OK}
1126
94784eb9 1127 assert [ $# -eq 2 ]
ea878018 1128
94784eb9 1129 local subnet="${1}"
ea878018 1130 local file="${2}"
ea878018
MT
1131
1132 local prefix_size="${DELEGATED_PREFIX_SIZE}"
1133 isset prefix_size || prefix_size="${DHCP_DEFAULT_DELEGATED_PREFIX_SIZE}"
1134
1135 assert ipv6_is_valid "${DELEGATED_PREFIX_FIRST}"
1136 assert ipv6_is_valid "${DELEGATED_PREFIX_LAST}"
1137 assert ipv6_prefix_size_is_valid_for_delegation "${prefix_size}"
1138
1139 (
1140 print " # Prefix Delegation"
1141 print " prefix6 ${DELEGATED_PREFIX_FIRST} ${DELEGATED_PREFIX_LAST} /${prefix_size};"
1142 print ""
1143 ) >> "${file}"
1144}
1145
1c6a4e30 1146_dhcpd_search_routers() {
94784eb9
MT
1147 assert [ $# -eq 2 ]
1148
6c07160e 1149 local proto=${1}
94784eb9 1150 local subnet=${2}
6c07160e
MT
1151
1152 # Do nothing for IPv6 (yet?).
1153 [ "${proto}" = "ipv6" ] && return ${EXIT_OK}
1154
94784eb9 1155 local routers zone addr
6c07160e 1156 for zone in $(zones_get_all); do
c041b631 1157 addr="$(db_get "${zone}/${proto}/local-ip-address")"
6c07160e
MT
1158 isset addr || continue
1159
1160 if ipv4_in_subnet ${addr} ${subnet}; then
1161 list_append routers $(ip_split_prefix ${addr})
1162 fi
1163 done
1164
1165 list_join routers ", "
1166}
1167
1c6a4e30 1168_dhcpd_write_subnet_range() {
94784eb9 1169 assert [ $# -eq 4 ]
6c07160e 1170
94784eb9
MT
1171 local proto=${1}
1172 local subnet=${2}
f3ac1159 1173 local range=${3}
6c07160e 1174 local file=${4}
6c07160e
MT
1175
1176 local settings=$(dhcpd_subnet_range_settings ${proto})
1177 assert isset settings
1178
1179 # Read the configuration settings.
1180 local ${settings}
f3ac1159 1181 dhcpd_subnet_range_read ${proto} ${subnet} ${range}
6c07160e
MT
1182
1183 case "${proto}" in
1184 ipv6)
1185 print " range6 ${START} ${END};" >> ${file}
1186 ;;
1187 ipv4)
1188 print " range ${START} ${END};" >> ${file}
1189 ;;
1190 esac
1191 print >> ${file}
1192
1193 return ${EXIT_OK}
1194}
1195
1c6a4e30 1196dhcpd_write_config() {
94784eb9
MT
1197 assert [ $# -eq 1 ]
1198
6c07160e 1199 local proto=${1}
6c07160e
MT
1200
1201 local file options_list
1202 case "${proto}" in
1203 ipv6)
1204 file=${DHCPV6D_CONFIG_FILE}
1205 options_list="DHCPV6D_OPTIONS"
1206 ;;
1207 ipv4)
1208 file=${DHCPV4D_CONFIG_FILE}
1209 options_list="DHCPV4D_OPTIONS"
1210 ;;
1211 esac
1212 assert isset file
1213 assert isset options_list
1214
1215 # Writing header.
1216 config_header "DHCP ${proto} daemon configuration file" > ${file}
1217
ea878018
MT
1218 # Read global DHCP configuration
1219 dhcpd_global_settings_read "${proto}"
1220
6c07160e
MT
1221 # Authoritative.
1222 if enabled AUTHORITATIVE; then
1223 (
1224 print "# This is an authoritative DHCP server for this network."
1225 print "authoritative;\n"
1226 ) >> ${file}
1227 else
1228 (
1229 print "# This is NOT an authoritative DHCP server for this network."
1230 print "not authoritative;\n"
1231 ) >> ${file}
1232 fi
1233
1234 case "${proto}" in
1235 ipv6)
1236 # Lease times.
ea878018 1237 if isinteger VALID_LIFETIME; then
6c07160e
MT
1238 print "default-lease-time %d;" "${VALID_LIFETIME}" >> ${file}
1239 fi
1240
1241 if isinteger PREFERRED_LIFETIME; then
1242 print "preferred-lifetime %d;" "${PREFERRED_LIFETIME}" >> ${file}
1243 fi
1244 ;;
1245 ipv4)
1246 # Lease times.
1247 if isinteger DEFAULT_LEASE_TIME; then
1248 print "default-lease-time %d;" "${DEFAULT_LEASE_TIME}" >> ${file}
1249 fi
1250
1251 if isinteger MAX_LEASE_TIME; then
1252 print "max-lease-time %d;" "${MAX_LEASE_TIME}" >> ${file}
1253 fi
1254
1255 if isinteger MIN_LEASE_TIME; then
1256 print "min-lease-time %d;" "${MIN_LEASE_TIME}" >> ${file}
1257 fi
1258 ;;
1259 esac
1260
1261 # Write the options to file.
1262 local -A options
1263 dhcpd_global_options_read ${proto}
1264 _dhcpd_write_options ${proto} ${file} ${options_list}
1265
1266 # Add all subnet declarations.
94784eb9
MT
1267 local subnet
1268 for subnet in $(dhcpd_subnet_list ${proto}); do
1269 _dhcpd_write_subnet ${proto} ${subnet} ${file}
6c07160e
MT
1270 done
1271
1272 return ${EXIT_OK}
1273}