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