1
0
mirror of https://git.openwrt.org/feed/packages.git synced 2024-06-18 05:13:56 +02:00
openwrt-packages/net/isc-dhcp/files/dhcpd.init
Leon M. Busch-George 3ad482078b isc-dhcp: adapt to new ipcalc paradigm
With #12925, 'BROADCAST' will no longer be set if there is no local
broadcast address (rather than holding the global broadcast address).
Prepare for the merge but stay compatible with the old version of ipcalc.

Signed-off-by: Leon M. Busch-George <leon@georgemail.eu>
2023-10-15 21:33:15 -06:00

698 lines
15 KiB
Bash
Executable File

#!/bin/sh /etc/rc.common
START=25
USE_PROCD=1
PROG=/usr/sbin/dhcpd
TTL=3600
PREFIX="update add"
lease_file=/tmp/dhcpd.leases
config_file=/tmp/run/dhcpd.conf
dyndir=/tmp/bind
conf_local_file=$dyndir/named.conf.local
session_key_name=local-ddns
session_key_file=/var/run/named/session.key
time2seconds() {
local timestring=$1
local multiplier number suffix
suffix="${timestring//[0-9 ]}"
number="${timestring%%$suffix}"
[ "$number$suffix" != "$timestring" ] && return 1
case "$suffix" in
"" | s)
multiplier=1
;;
m)
multiplier=60
;;
h)
multiplier=3600
;;
d)
multiplier=86400
;;
w)
multiplier=604800
;;
*)
return 1
;;
esac
echo $(( number * multiplier ))
}
trim() {
local arg="$1"
echo "$arg" | sed -e 's/^ *//' -e 's/ *$//'
}
rfc1918_prefix() {
local octets="$(echo "${1%%/*}" | cut -d. -f1)"
[ "$octets" = "10" ] && { echo "$octets"; return; }
octets="$(echo "${1%%/*}" | cut -d. -f1-2)"
case "$octets" in
172.1[6789]|172.2[0-9]|172.3[01]|192.168)
echo "$octets"
;;
esac
}
no_ipv6() {
[ -n "$(named-checkconf -px \
| sed -r -ne '1N; N; /^\tlisten-on-v6 ?\{\n\t\t"none";\n\t\};$/{ p; q; }; D')" ]
}
# duplicated from dnsmasq init script
hex_to_hostid() {
local var="$1"
local hex="${2#0x}" # strip optional "0x" prefix
if [ -n "${hex//[0-9a-fA-F]/}" ]; then
# is invalid hex literal
return 1
fi
# convert into host id
export "$var=$(
printf "%0x:%0x" \
$(((0x$hex >> 16) % 65536)) \
$(( 0x$hex % 65536))
)"
return 0
}
typeof() {
echo "$1" | awk '
/^\d+\.\d+\.\d+\.\d+$/ { print "ip\n"; next; }
/^(true|false)$/ { print "bool\n"; next; }
/^\d+$/ { print "integer\n"; next; }
/^"[^"]*"$/ { print "string\n"; next; }
/^[0-9a-fA-F]{2,2}(:[0-9a-fA-F]{2,2})*$/ { print "string\n"; next; }
{ print "other\n"; next; }
'
}
update() {
local lhs="$1" family="$2" type="$3"
shift 3
[ $dynamicdns -eq 1 ] && \
echo -e "$PREFIX" "$lhs $family $type $@\nsend" >> $dyn_file
}
explode() {
local arg="$1"
echo "$arg" | sed -e 's/\./, /g'
}
rev_str() {
local str="$1" delim="$2"
local frag result="" IFS="$delim"
for frag in $str; do
result="$frag${result:+$delim}$result"
done
echo "$result"
}
create_empty_zone() {
local zone="$1"
if [ ! -f $dyndir/db."$zone" ]; then
cp -p /etc/bind/db.empty $dyndir/db."$zone"
chmod g+w $dyndir/db."$zone"
chgrp bind $dyndir/db."$zone"
fi
}
append_routes() {
local tuple tuple="$(trim "$1")"
local network prefix router save octets compacted
save="${tuple% *}"
router="$(trim "${tuple#${save} }")"
network="$(trim "${save%/[0-9]*}")"
prefix="$(trim "${save##${network}/}")"
octets=$((($prefix + 7) / 8))
compacted="$(echo "$network" | cut -d. -f1-$octets)"
routes="$routes${routes:+, }$(explode "$prefix${compacted:+.$compacted}.$router")"
}
append_dhcp_options() {
local tuple="$1"
# strip redundant "option:" prefix
tuple="${tuple#option:}"
local tag="${tuple%%,*}"
local values="${tuple#$tag,}"
local formatted value
local IFS=$', \n'
for value in $values; do
# detect type of $value and quote if necessary
case $(typeof "$value") in
ip|bool|integer|string)
;;
other)
value="\"$value\""
;;
esac
formatted="$formatted${formatted:+, }$value"
done
echo " option $tag $formatted;"
}
static_cname_add() {
local cfg="$1"
local cname target
config_get cname "$cfg" "cname"
[ -n "$cname" ] || return 0
config_get target "$cfg" "target"
[ -n "$target" ] || return 0
update "$cname.$domain." IN CNAME "$target.$domain."
}
static_cnames() {
config_foreach static_cname_add cname "$@"
}
static_domain_add() {
local cfg="$1"
local name ip ips revip
config_get name "$cfg" "name"
[ -n "$name" ] || return 0
config_get ip "$cfg" "ip"
[ -n "$ip" ] || return 0
ips="$ip"
for ip in $ips; do
revip="$(rev_str "$ip" ".")"
update "$name.$domain." IN A "$ip"
[ -n "$(rfc1918_prefix "$ip")" ] && \
update "$revip.in-addr.arpa." IN PTR "$name.$domain."
done
}
static_domains() {
config_foreach static_domain_add domain "$@"
}
static_mxhost_add() {
local cfg="$1"
local domain2 relay pref
config_get domain2 "$cfg" "domain"
[ -n "$domain2" ] || return 0
config_get relay "$cfg" "relay"
[ -n "$relay" ] || return 0
config_get pref "$cfg" "pref"
[ -n "$pref" ] || return 0
if [ "$domain2" = "@" ]; then
update "$domain." IN MX "$pref" "$relay.$domain."
else
update "$domain2.$domain." IN MX "$pref" "$relay.$domain."
fi
}
static_mxhosts() {
config_foreach static_mxhost_add mxhost "$@"
}
static_srvhost_add() {
local cfg="$1"
local srv target port priority weight
config_get srv "$cfg" "srv"
[ -n "$srv" ] || return 0
config_get target "$cfg" "target"
[ -n "$target" ] || return 0
config_get port "$cfg" "port"
[ -n "$port" ] || return 0
config_get priority "$cfg" "priority"
[ -n "$priority" ] || return 0
config_get weight "$cfg" "weight"
[ -n "$weight" ] || return 0
update "$srv.$domain." IN SRV "$priority" "$weight" "$port" "$target.$domain"
}
static_srvhosts() {
config_foreach static_srvhost_add srvhost "$@"
}
static_host_add() {
local cfg="$1"
local broadcast hostid macn macs mac name ip ips revip leasetime
local force_send extra_options option
config_get macs "$cfg" "mac"
[ -n "$macs" ] || return 0
config_get name "$cfg" "name"
[ -n "$name" ] || return 0
config_get ip "$cfg" "ip"
[ -n "$ip" ] || return 0
config_get_bool broadcast "$cfg" "broadcast" 0
config_get dns "$cfg" "dns"
config_get gateway "$cfg" "gateway"
config_get leasetime "$cfg" "leasetime"
if [ -n "$leasetime" ] ; then
leasetime="$(time2seconds "$leasetime")"
[ "$?" -ne 0 ] && return 1
fi
config_get hostid "$cfg" "hostid"
if [ -n "$hostid" ] ; then
hex_to_hostid hostid "$hostid" || return 1
fi
config_get force_send "$cfg" "force_send"
extra_options=
local _IFS="$IFS" IFS=','
for option in $force_send; do
case "$option" in
hostname)
extra_options="$extra_options${extra_options:+,}0c" ;;
domain-name)
extra_options="$extra_options${extra_options:+,}0f" ;;
renewal-time)
extra_options="$extra_options${extra_options:+,}3a" ;;
rebinding-time)
extra_options="$extra_options${extra_options:+,}3b" ;;
fqdn)
extra_options="$extra_options${extra_options:+,}51" ;;
routes)
extra_options="$extra_options${extra_options:+,}79" ;;
*)
echo "unknown option: $option" >&2 ;;
esac
done
IFS="$_IFS"
macn=0
for mac in $macs; do
macn=$(( macn + 1 ))
done
for mac in $macs; do
local secname="$name"
if [ $macn -gt 1 ] ; then
secname="${name}-${mac//:}"
fi
echo "host $secname {"
echo " hardware ethernet $mac;"
echo " fixed-address $ip;"
echo " option host-name \"$name\";"
if [ "$broadcast" -eq 1 ] ; then
echo " always-broadcast true;"
fi
if [ -n "$leasetime" ] ; then
echo " default-lease-time $leasetime;"
echo " max-lease-time $leasetime;"
fi
if [ -n "$hostid" ] ; then
echo " option dhcp-client-identifier $hostid;"
fi
if [ -n "$dns" ] ; then
echo " option domain-name-servers $dns;"
fi
if [ -n "$gateway" ] ; then
echo " option routers $gateway;"
fi
local routes=
config_list_foreach "$cfg" "routes" append_routes
[ -n "$routes" ] && echo " option classless-ipv4-route $routes;"
config_list_foreach "$cfg" "dhcp_option" append_dhcp_options
if [ -n "$extra_options" ]; then
echo -e " if exists dhcp-parameter-request-list {\n option dhcp-parameter-request-list = concat(option dhcp-parameter-request-list, $extra_options);\n }"
fi
echo "}"
done
ips="$ip"
for ip in $ips; do
revip="$(rev_str "$ip" ".")"
update "$name.$domain." IN A "$ip"
update "$revip.in-addr.arpa." IN PTR "$name.$domain."
done
}
static_hosts() {
config_foreach static_host_add host "$@"
}
gen_dhcp_subnet() {
local cfg="$1"
echo "subnet $NETWORK netmask $NETMASK {"
if [ -n "$START" ] && [ -n "$END" ]; then
echo " range $START $END;"
fi
echo " option subnet-mask $netmask;"
# check for 0.0.0.0 until all active releases of ipcalc.sh omit it
# for small networks:
if [ -n "$BROADCAST" ] && [ "$BROADCAST" != "0.0.0.0" ] ; then
echo " option broadcast-address $BROADCAST;"
fi
if [ "$dynamicdhcp" -eq 0 ] ; then
if [ "$authoritative" -eq 1 ] ; then
echo " deny unknown-clients;"
else
echo " ignore unknown-clients;"
fi
fi
if [ -n "$leasetime" ] ; then
echo " default-lease-time $leasetime;"
echo " max-lease-time $leasetime;"
fi
if [ "$defaultroute" -eq 1 ] ; then
echo " option routers $gateway;"
fi
echo " option domain-name-servers $DNS;"
[ -n "$domain" ] && echo " option domain-name \"$domain\";"
local routes=
config_list_foreach "$cfg" "routes" append_routes
[ -n "$routes" ] && echo " option classless-ipv4-route $routes;"
config_list_foreach "$cfg" "dhcp_option" append_dhcp_options
echo "}"
}
dhcpd_add() {
local cfg="$1" synthesize="$2"
local dhcp6range="::"
local dynamicdhcp defaultroute end gateway ifname ignore leasetime limit net netmask
local proto networkid start subnet domain
local IP NETMASK BROADCAST NETWORK PREFIX DNS START END
config_get_bool ignore "$cfg" "ignore" 0
[ "$ignore" = "0" ] || return 0
config_get net "$cfg" "interface"
[ -n "$net" ] || return 0
config_get start "$cfg" "start"
config_get limit "$cfg" "limit"
case "$start:$limit" in
":*"|"*:")
echo "dhcpd: start/limit must be used together in $cfg" >&2
return 0
esac
network_get_subnet subnet "$net" || return 0
network_get_device ifname "$net" || return 0
network_get_protocol proto "$net" || return 0
[ static = "$proto" ] || return 0
local octets="$(rfc1918_prefix "$subnet")"
[ -n "$octets" ] && rfc1918_nets="$rfc1918_nets${rfc1918_nets:+ }$octets"
[ $synthesize -eq 0 ] && return
config_get_bool dynamicdhcp "$cfg" "dynamicdhcp" 1
config_get_bool defaultroute "$cfg" "default_route" 1
dhcp_ifs="$dhcp_ifs $ifname"
ipcalc $subnet $start $limit
config_get netmask "$cfg" "netmask" "$NETMASK"
config_get leasetime "$cfg" "leasetime"
if [ -n "$leasetime" ] ; then
leasetime="$(time2seconds "$leasetime")"
[ "$?" -ne 0 ] && return 1
fi
if network_get_dnsserver dnsserver "$net" ; then
for dnsserv in $dnsserver ; do
DNS="$DNS${DNS:+, }$dnsserv"
done
else
DNS="$IP"
fi
if ! network_get_gateway gateway "$net" ; then
gateway="$IP"
fi
config_get domain "$cfg" "domain"
gen_dhcp_subnet "$cfg"
}
general_config() {
local always_broadcast boot_unknown_clients log_facility
local default_lease_time max_lease_time
config_get_bool always_broadcast "isc_dhcpd" "always_broadcast" 0
config_get_bool authoritative "isc_dhcpd" "authoritative" 1
config_get_bool boot_unknown_clients "isc_dhcpd" "boot_unknown_clients" 1
config_get default_lease_time "isc_dhcpd" "default_lease_time" 3600
config_get max_lease_time "isc_dhcpd" "max_lease_time" 86400
config_get log_facility "isc_dhcpd" "log_facility"
config_get domain "isc_dhcpd" "domain"
config_get_bool dynamicdns "isc_dhcpd" dynamicdns 0
[ $always_broadcast -eq 1 ] && echo "always-broadcast true;"
[ $authoritative -eq 1 ] && echo "authoritative;"
[ $boot_unknown_clients -eq 0 ] && echo "boot-unknown-clients false;"
default_lease_time="$(time2seconds "$default_lease_time")"
[ "$?" -ne 0 ] && return 1
max_lease_time="$(time2seconds "$max_lease_time")"
[ "$?" -ne 0 ] && return 1
if [ $dynamicdns -eq 1 ]; then
create_empty_zone "$domain"
local mynet
for mynet in $rfc1918_nets; do
mynet="$(rev_str "$mynet" ".")"
create_empty_zone "$mynet.in-addr.arpa"
done
local need_reload=
cp -p $conf_local_file ${conf_local_file}_
cat <<EOF > $conf_local_file
zone "$domain" {
type master;
file "$dyndir/db.$domain";
update-policy {
grant $session_key_name zonesub any;
};
};
EOF
for mynet in $rfc1918_nets; do
mynet="$(rev_str "$mynet" ".")"
cat <<EOF >> $conf_local_file
zone "$mynet.in-addr.arpa" {
type master;
file "$dyndir/db.$mynet.in-addr.arpa";
update-policy {
grant $session_key_name zonesub any;
};
};
EOF
done
cmp -s $conf_local_file ${conf_local_file}_ || need_reload=1
rm -f ${conf_local_file}_
[ -n "$need_reload" ] && /etc/init.d/named reload
sleep 1
cat <<EOF
ddns-domainname "$domain.";
ddns-update-style standard;
ddns-updates on;
ignore client-updates;
update-static-leases on;
use-host-decl-names on;
update-conflict-detection off;
update-optimization off;
include "$session_key_file";
zone $domain. {
primary 127.0.0.1;
key $session_key_name;
}
EOF
for mynet in $rfc1918_nets; do
mynet="$(rev_str "$mynet" ".")"
cat <<EOF
zone $mynet.in-addr.arpa. {
primary 127.0.0.1;
key $session_key_name;
}
EOF
done
fi
if [ -n "$log_facility" ] ; then
echo "log-facility $log_facility;"
fi
echo "default-lease-time $default_lease_time;"
echo "max-lease-time $max_lease_time;"
[ -n "$domain" ] && echo "option domain-name \"$domain\";"
echo -e "\n# additional codes\noption classless-ipv4-route code 121 = array of { unsigned integer 8 };\n"
rm -f /tmp/resolv.conf
echo "# This file is generated by the DHCPD service" > /tmp/resolv.conf
[ -n "$domain" ] && echo "domain $domain" >> /tmp/resolv.conf
echo "nameserver 127.0.0.1" >> /tmp/resolv.conf
}
# base procd hooks
boot() {
DHCPD_BOOT=1
start "$@"
}
start_service() {
local domain dhcp_ifs authoritative dynamicdns
if [ -n "$DHCPD_BOOT" ] ; then
return 0
fi
if [ ! -e $lease_file ] ; then
touch $lease_file
fi
if [ -e "/etc/dhcpd.conf" ] ; then
config_file="/etc/dhcpd.conf"
else
. /lib/functions/network.sh
local dyn_file=$(mktemp -u /tmp/dhcpd.XXXXXX)
config_load dhcp
local rfc1918_nets=""
# alas we have to make 2 passes...
config_foreach dhcpd_add dhcp 0
rfc1918_nets="$(echo "$rfc1918_nets" | tr ' ' $'\n' | sort | uniq | tr $'\n' ' ')"
general_config > $config_file
if [ $dynamicdns -eq 1 ]; then
cat <<EOF > $dyn_file
; Generated by /etc/init.d/dhcpd at $(date)
ttl $TTL
EOF
fi
rfc1918_nets=
config_foreach dhcpd_add dhcp 1 >> $config_file
static_hosts >> $config_file
static_cnames >> $config_file
static_domains >> $config_file
static_mxhosts >> $config_file
static_srvhosts >> $config_file
if [ $dynamicdns -eq 1 ]; then
local args=
no_ipv6 && args="-4"
nsupdate -l -v $args $dyn_file
fi
rm -f $dyn_file
[ -z "$dhcp_ifs" ] && return 0
fi
procd_open_instance
procd_set_param command $PROG -q -f -cf $config_file -lf $lease_file $dhcp_ifs
procd_close_instance
}
reload_service() {
rc_procd start_service "$@"
procd_send_signal dhcpd "$@"
}
add_interface_trigger() {
local cfg=$1
local trigger ignore
config_get trigger "$cfg" interface
config_get_bool ignore "$cfg" ignore 0
if [ -n "$trigger" -a $ignore -eq 0 ] ; then
procd_add_reload_interface_trigger "$trigger"
fi
}
service_triggers() {
if [ -n "$DHCPD_BOOT" ] ; then
# Make the first start robust to slow interfaces; wait a while
procd_add_raw_trigger "interface.*.up" 5000 /etc/init.d/dhcpd restart
else
# reload with normal parameters
procd_add_reload_trigger "network" "dhcp"
config_load dhcp
config_foreach add_interface_trigger dhcp
fi
}