blob: 06415729e751f2435ad754a7472e3c4b2adaef55 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001#!/bin/sh /etc/rc.common
2
3START=25
4USE_PROCD=1
5PROG=/usr/sbin/dhcpd
6
7TTL=3600
8PREFIX="update add"
9
10lease_file=/tmp/dhcpd.leases
11config_file=/tmp/run/dhcpd.conf
12
13dyndir=/tmp/bind
14conf_local_file=$dyndir/named.conf.local
15
16session_key_name=local-ddns
17session_key_file=/var/run/named/session.key
18
19dyn_file=$(mktemp -u /tmp/dhcpd.XXXXXX)
20
21time2seconds() {
22 local timestring=$1
23 local multiplier number suffix
24
25 suffix="${timestring//[0-9 ]}"
26 number="${timestring%%$suffix}"
27 [ "$number$suffix" != "$timestring" ] && return 1
28 case "$suffix" in
29 "" | s)
30 multiplier=1
31 ;;
32 m)
33 multiplier=60
34 ;;
35 h)
36 multiplier=3600
37 ;;
38 d)
39 multiplier=86400
40 ;;
41 w)
42 multiplier=604800
43 ;;
44 *)
45 return 1
46 ;;
47 esac
48 echo $(( number * multiplier ))
49}
50
51# duplicated from dnsmasq init script
52hex_to_hostid() {
53 local var="$1"
54 local hex="${2#0x}" # strip optional "0x" prefix
55
56 if [ -n "${hex//[0-9a-fA-F]/}" ]; then
57 # is invalid hex literal
58 return 1
59 fi
60
61 # convert into host id
62 export "$var=$(
63 printf "%0x:%0x" \
64 $(((0x$hex >> 16) % 65536)) \
65 $(( 0x$hex % 65536))
66 )"
67
68 return 0
69}
70
71typeof() {
72 echo "$1" | awk '
73/^\d+\.\d+\.\d+\.\d+$/ { print "ip\n"; next; }
74/^(true|false)$/ { print "bool\n"; next; }
75/^\d+$/ { print "integer\n"; next; }
76/^"[^"]*"$/ { print "string\n"; next; }
77/^[0-9a-fA-F]{2,2}(:[0-9a-fA-F]{2,2})*$/ { print "string\n"; next; }
78 { print "other\n"; next; }
79'
80}
81
82update() {
83 local lhs="$1" family="$2" type="$3"
84 shift 3
85
86 [ $dynamicdns -eq 1 ] && \
87 echo -e "$PREFIX" "$lhs $family $type $@\nsend" >> $dyn_file
88}
89
90explode() {
91 local arg="$1"
92
93 echo "$arg" | sed -e 's/\./, /g'
94}
95
96rev_str() {
97 local str="$1" delim="$2"
98 local frag result="" IFS="$delim"
99
100 for frag in $str; do
101 result="$frag${result:+$delim}$result"
102 done
103
104 echo "$result"
105}
106
107create_empty_zone() {
108 local zone="$1"
109
110 if [ ! -f $dyndir/db."$zone" ]; then
111 cp -p /etc/bind/db.empty $dyndir/db."$zone"
112 chmod g+w $dyndir/db."$zone"
113 chgrp bind $dyndir/db."$zone"
114 fi
115}
116
117append_routes() {
118 local tuple tuples="$1"
119 local string=
120
121 local IFS=','
122 for tuple in $tuples; do
123 local network prefix router save octets compacted
124
125 save="${tuple% *}"
126 router="${tuple#${save} }"
127
128 network="${save%/[0-9]*}"
129 prefix="${save##${network}}"
130 prefix="${prefix:1}"
131
132 octets=$((($prefix + 7) / 8))
133 compacted="$(echo "$network" | cut -d. -f1-$octets)"
134
135 string="${string:+, }$(explode "$prefix.$compacted.$router")"
136 done
137
138 echo " option classless-ipv4-route $string;"
139}
140
141append_dhcp_options() {
142 local tuple="$1"
143
144 # strip redundant "option:" prefix
145 tuple="${tuple#option:}"
146
147 local tag="${tuple%%,*}"
148 local values="${tuple#$tag,}"
149
150 local formatted value
151 local IFS=$', \n'
152 for value in $values; do
153 # detect type of $value and quote if necessary
154 case $(typeof "$value") in
155 ip|bool|integer|string)
156 ;;
157 other)
158 value="\"$value\""
159 ;;
160 esac
161 formatted="$formatted${formatted:+, }$value"
162 done
163 echo " option $tag $formatted;"
164}
165
166static_cname_add() {
167 local cfg="$1"
168 local cname target
169
170 config_get cname "$cfg" "cname"
171 [ -n "$cname" ] || return 0
172 config_get target "$cfg" "target"
173 [ -n "$target" ] || return 0
174
175 update "$cname.$domain." IN CNAME "$target.$domain."
176}
177
178static_cnames() {
179 config_foreach static_cname_add cname "$@"
180}
181
182static_domain_add() {
183 local cfg="$1"
184 local name ip ips revip
185
186 config_get name "$cfg" "name"
187 [ -n "$name" ] || return 0
188 config_get ip "$cfg" "ip"
189 [ -n "$ip" ] || return 0
190
191 ips="$ip"
192 for ip in $ips; do
193 revip="$(rev_str "$ip" ".")"
194
195 update "$name.$domain." IN A "$ip"
196 update "$revip.in-addr.arpa." IN PTR "$name.$domain."
197 done
198}
199
200static_domains() {
201 config_foreach static_domain_add domain "$@"
202}
203
204static_mxhost_add() {
205 local cfg="$1"
206 local domain2 relay pref
207
208 config_get domain2 "$cfg" "domain"
209 [ -n "$domain2" ] || return 0
210 config_get relay "$cfg" "relay"
211 [ -n "$relay" ] || return 0
212 config_get pref "$cfg" "pref"
213 [ -n "$pref" ] || return 0
214
215 if [ "$domain2" = "@" ]; then
216 update "$domain." IN MX "$pref" "$relay.$domain."
217 else
218 update "$domain2.$domain." IN MX "$pref" "$relay.$domain."
219 fi
220}
221
222static_mxhosts() {
223 config_foreach static_mxhost_add mxhost "$@"
224}
225
226static_srvhost_add() {
227 local cfg="$1"
228 local srv target port priority weight
229
230 config_get srv "$cfg" "srv"
231 [ -n "$srv" ] || return 0
232 config_get target "$cfg" "target"
233 [ -n "$target" ] || return 0
234 config_get port "$cfg" "port"
235 [ -n "$port" ] || return 0
236 config_get priority "$cfg" "priority"
237 [ -n "$priority" ] || return 0
238 config_get weight "$cfg" "weight"
239 [ -n "$weight" ] || return 0
240
241 update "$srv.$domain." IN SRV "$priority" "$weight" "$port" "$target"
242}
243
244static_srvhosts() {
245 config_foreach static_srvhost_add srvhost "$@"
246}
247
248static_host_add() {
249 local cfg="$1"
250 local broadcast hostid macn macs mac name ip ips revip leasetime
251
252 config_get macs "$cfg" "mac"
253 [ -n "$macs" ] || return 0
254 config_get name "$cfg" "name"
255 [ -n "$name" ] || return 0
256 config_get ip "$cfg" "ip"
257 [ -n "$ip" ] || return 0
258
259 config_get_bool broadcast "$cfg" "broadcast" 0
260 config_get dns "$cfg" "dns"
261 config_get gateway "$cfg" "gateway"
262 config_get leasetime "$cfg" "leasetime"
263 if [ -n "$leasetime" ] ; then
264 leasetime="$(time2seconds "$leasetime")"
265 [ "$?" -ne 0 ] && return 1
266 fi
267
268 config_get hostid "$cfg" "hostid"
269 if [ -n "$hostid" ] ; then
270 hex_to_hostid hostid "$hostid" || return 1
271 fi
272
273 macn=0
274 for mac in $macs; do
275 macn=$(( macn + 1 ))
276 done
277
278 for mac in $macs; do
279 local secname="$name"
280 if [ $macn -gt 1 ] ; then
281 secname="${name}-${mac//:}"
282 fi
283 echo "host $secname {"
284 echo " hardware ethernet $mac;"
285 echo " fixed-address $ip;"
286 echo " option host-name \"$name\";"
287 if [ "$broadcast" -eq 1 ] ; then
288 echo " always-broadcast true;"
289 fi
290 if [ -n "$leasetime" ] ; then
291 echo " default-lease-time $leasetime;"
292 echo " max-lease-time $leasetime;"
293 fi
294 if [ -n "$hostid" ] ; then
295 echo " option dhcp-client-identifier $hostid;"
296 fi
297 if [ -n "$dns" ] ; then
298 echo " option domain-name-servers $dns;"
299 fi
300 if [ -n "$gateway" ] ; then
301 echo " option routers $gateway;"
302 fi
303 config_list_foreach "$cfg" "routes" append_routes
304 config_list_foreach "$cfg" "dhcp_option" append_dhcp_options
305 echo "}"
306 done
307
308 ips="$ip"
309 for ip in $ips; do
310 revip="$(rev_str "$ip" ".")"
311
312 update "$name.$domain." IN A "$ip"
313 update "$revip.in-addr.arpa." IN PTR "$name.$domain."
314 done
315}
316
317static_hosts() {
318 config_foreach static_host_add host "$@"
319}
320
321gen_dhcp_subnet() {
322 local cfg="$1"
323
324 echo "subnet $NETWORK netmask $NETMASK {"
325 echo " range $START $END;"
326 echo " option subnet-mask $netmask;"
327 if [ "$BROADCAST" != "0.0.0.0" ] ; then
328 echo " option broadcast-address $BROADCAST;"
329 fi
330 if [ "$dynamicdhcp" -eq 0 ] ; then
331 if [ "$authoritative" -eq 1 ] ; then
332 echo " deny unknown-clients;"
333 else
334 echo " ignore unknown-clients;"
335 fi
336 fi
337 if [ -n "$leasetime" ] ; then
338 echo " default-lease-time $leasetime;"
339 echo " max-lease-time $leasetime;"
340 fi
341 echo " option routers $gateway;"
342 echo " option domain-name-servers $DNS;"
343 config_list_foreach "$cfg" "routes" append_routes
344 config_list_foreach "$cfg" "dhcp_option" append_dhcp_options
345 echo "}"
346}
347
348dhcpd_add() {
349 local cfg="$1" synthesize="$2"
350 local dhcp6range="::"
351 local dynamicdhcp end gateway ifname ignore leasetime limit net netmask
352 local proto networkid start subnet
353 local IP NETMASK BROADCAST NETWORK PREFIX DNS START END
354
355 config_get_bool ignore "$cfg" "ignore" 0
356 [ "$ignore" = "0" ] || return 0
357
358 config_get net "$cfg" "interface"
359 [ -n "$net" ] || return 0
360
361 config_get start "$cfg" "start"
362 [ -n "$start" ] || return 0
363
364 config_get limit "$cfg" "limit"
365 [ -n "$limit" ] || return 0
366
367 network_get_subnet subnet "$net" || return 0
368 network_get_device ifname "$net" || return 0
369 network_get_protocol proto "$net" || return 0
370
371 [ static = "$proto" ] || return 0
372
373 local pair="$(echo "${subnet%%/*}" | cut -d. -f1-2)"
374 case "$pair" in
375 10.*)
376 rfc1918_nets="$rfc1918_nets${rfc1918_nets:+ }10"
377 ;;
378 172.1[6789]|172.2[0-9]|172.3[01]|192.168)
379 rfc1918_nets="$rfc1918_nets${rfc1918_nets:+ }$pair"
380 ;;
381 esac
382 [ $synthesize -eq 0 ] && return
383
384 config_get_bool dynamicdhcp "$cfg" "dynamicdhcp" 1
385
386 dhcp_ifs="$dhcp_ifs $ifname"
387
388 eval "$(ipcalc.sh $subnet $start $limit)"
389
390 config_get netmask "$cfg" "netmask" "$NETMASK"
391 config_get leasetime "$cfg" "leasetime"
392 if [ -n "$leasetime" ] ; then
393 leasetime="$(time2seconds "$leasetime")"
394 [ "$?" -ne 0 ] && return 1
395 fi
396
397 if network_get_dnsserver dnsserver "$net" ; then
398 for dnsserv in $dnsserver ; do
399 DNS="$DNS${DNS:+, }$dnsserv"
400 done
401 else
402 DNS="$IP"
403 fi
404
405 if ! network_get_gateway gateway "$net" ; then
406 gateway="$IP"
407 fi
408
409 gen_dhcp_subnet "$cfg"
410}
411
412general_config() {
413 local always_broadcast boot_unknown_clients log_facility
414 local default_lease_time max_lease_time
415
416 config_get_bool always_broadcast "isc_dhcpd" "always_broadcast" 0
417 config_get_bool authoritative "isc_dhcpd" "authoritative" 1
418 config_get_bool boot_unknown_clients "isc_dhcpd" "boot_unknown_clients" 1
419 config_get default_lease_time "isc_dhcpd" "default_lease_time" 3600
420 config_get max_lease_time "isc_dhcpd" "max_lease_time" 86400
421 config_get log_facility "isc_dhcpd" "log_facility"
422
423 config_get domain "isc_dhcpd" "domain"
424 config_get_bool dynamicdns "isc_dhcpd" dynamicdns 0
425
426 [ $always_broadcast -eq 1 ] && echo "always-broadcast true;"
427 [ $authoritative -eq 1 ] && echo "authoritative;"
428 [ $boot_unknown_clients -eq 0 ] && echo "boot-unknown-clients false;"
429
430 default_lease_time="$(time2seconds "$default_lease_time")"
431 [ "$?" -ne 0 ] && return 1
432 max_lease_time="$(time2seconds "$max_lease_time")"
433 [ "$?" -ne 0 ] && return 1
434
435 if [ $dynamicdns -eq 1 ]; then
436 create_empty_zone "$domain"
437
438 local mynet
439
440 for mynet in $rfc1918_nets; do
441 mynet="$(rev_str "$mynet" ".")"
442 create_empty_zone "$mynet.in-addr.arpa"
443 done
444
445 cat <<EOF > $conf_local_file
446zone "$domain" {
447 type master;
448 file "$dyndir/db.$domain";
449 allow-update { key $session_key_name; };
450 allow-transfer { key $session_key_name; };
451};
452
453EOF
454
455 for mynet in $rfc1918_nets; do
456 mynet="$(rev_str "$mynet" ".")"
457 cat <<EOF >> $conf_local_file
458zone "$mynet.in-addr.arpa" {
459 type master;
460 file "$dyndir/db.$mynet.in-addr.arpa";
461 allow-update { key $session_key_name; };
462 allow-transfer { key $session_key_name; };
463};
464
465EOF
466 done
467
468 /etc/init.d/named reload
469 sleep 1
470
471 cat <<EOF
472ddns-domainname "$domain.";
473ddns-update-style standard;
474ddns-updates on;
475ignore client-updates;
476
477update-static-leases on;
478use-host-decl-names on;
479update-conflict-detection off;
480update-optimization off;
481
482include "$session_key_file";
483
484zone $domain. {
485 primary 127.0.0.1;
486 key local-ddns;
487}
488
489EOF
490
491 for mynet in $rfc1918_nets; do
492 mynet="$(rev_str "$mynet" ".")"
493 cat <<EOF
494zone $mynet.in-addr.arpa. {
495 primary 127.0.0.1;
496 key local-ddns;
497}
498
499EOF
500 done
501 fi
502
503 if [ -n "$log_facility" ] ; then
504 echo "log-facility $log_facility;"
505 fi
506 echo "default-lease-time $default_lease_time;"
507 echo "max-lease-time $max_lease_time;"
508
509 [ -n "$domain" ] && echo "option domain-name \"$domain\";"
510
511 echo -e "\n# additional codes\noption classless-ipv4-route code 121 = array of { unsigned integer 8 };\n"
512
513 rm -f /tmp/resolv.conf
514 echo "# This file is generated by the DHCPD service" > /tmp/resolv.conf
515 [ -n "$domain" ] && echo "domain $domain" >> /tmp/resolv.conf
516 echo "nameserver 127.0.0.1" >> /tmp/resolv.conf
517}
518
519# base procd hooks
520
521boot() {
522 DHCPD_BOOT=1
523 start "$@"
524}
525
526start_service() {
527 local domain dhcp_ifs authoritative dynamicdns
528
529 if [ -n "$DHCPD_BOOT" ] ; then
530 return 0
531 fi
532
533 if [ ! -e $lease_file ] ; then
534 touch $lease_file
535 fi
536
537 if [ -e "/etc/dhcpd.conf" ] ; then
538 config_file="/etc/dhcpd.conf"
539 else
540 . /lib/functions/network.sh
541
542 config_load dhcp
543
544 local rfc1918_nets=""
545
546 # alas we have to make 2 passes...
547 config_foreach dhcpd_add dhcp 0
548
549 rfc1918_nets="$(echo "$rfc1918_nets" | tr ' ' $'\n' | sort | uniq | tr $'\n' ' ')"
550
551 general_config > $config_file
552
553 if [ $dynamicdns -eq 1 ]; then
554 cat <<EOF > $dyn_file
555; Generated by /etc/init.d/dhcpd at $(date)
556
557ttl $TTL
558
559EOF
560 fi
561
562 rfc1918_nets=
563
564 config_foreach dhcpd_add dhcp 1 >> $config_file
565
566 static_hosts >> $config_file
567
568 static_cnames >> $config_file
569
570 static_domains >> $config_file
571
572 static_mxhosts >> $config_file
573
574 static_srvhosts >> $config_file
575
576 if [ $dynamicdns -eq 1 ]; then
577 nsupdate -l -v $dyn_file
578
579 rm -f $dyn_file
580 fi
581
582 [ -z "$dhcp_ifs" ] && return 0
583 fi
584
585 procd_open_instance
586 procd_set_param command $PROG -q -f -cf $config_file -lf $lease_file $dhcp_ifs
587 procd_close_instance
588}
589
590reload_service() {
591 rc_procd start_service "$@"
592 procd_send_signal dhcpd "$@"
593}
594
595add_interface_trigger() {
596 local cfg=$1
597 local trigger ignore
598
599 config_get trigger "$cfg" interface
600 config_get_bool ignore "$cfg" ignore 0
601
602 if [ -n "$trigger" -a $ignore -eq 0 ] ; then
603 procd_add_reload_interface_trigger "$trigger"
604 fi
605}
606
607service_triggers() {
608 if [ -n "$DHCPD_BOOT" ] ; then
609 # Make the first start robust to slow interfaces; wait a while
610 procd_add_raw_trigger "interface.*.up" 5000 /etc/init.d/dhcpd restart
611
612 else
613 # reload with normal parameters
614 procd_add_reload_trigger "network" "dhcp"
615 config_load dhcp
616 config_foreach add_interface_trigger dhcp
617 fi
618}
619