#!/bin/sh /etc/rc.common # Copyright (C) 2007-2012 OpenWrt.org START=19 USE_PROCD=1 PROG=/usr/sbin/dnsmasq ADD_LOCAL_DOMAIN=1 ADD_LOCAL_HOSTNAME=1 ADD_WAN_FQDN=0 ADD_LOCAL_FQDN="" BASECONFIGFILE="/var/etc/dnsmasq.conf" EXTRACONFFILE="extraconfig.conf" BASEHOSTFILE="/tmp/hosts/dhcp" TRUSTANCHORSFILE="/usr/share/dnsmasq/trust-anchors.conf" TIMEVALIDFILE="/var/state/dnsmasqsec" BASEDHCPSTAMPFILE="/var/run/dnsmasq" DHCPBOGUSHOSTNAMEFILE="/usr/share/dnsmasq/dhcpbogushostname.conf" RFC6761FILE="/usr/share/dnsmasq/rfc6761.conf" DHCPSCRIPT="/usr/lib/dnsmasq/dhcp-script.sh" DHCPSCRIPT_DEPENDS="/usr/share/libubox/jshn.sh /usr/bin/jshn /bin/ubus" DNSMASQ_DHCP_VER=4 dnsmasq_ignore_opt() { local opt="$1" if [ -z "$dnsmasq_features" ]; then dnsmasq_features="$(dnsmasq --version | grep -m1 'Compile time options:' | cut -d: -f2) " [ "${dnsmasq_features#* DHCP }" = "$dnsmasq_features" ] || dnsmasq_has_dhcp=1 [ "${dnsmasq_features#* DHCPv6 }" = "$dnsmasq_features" ] || dnsmasq_has_dhcp6=1 [ "${dnsmasq_features#* DNSSEC }" = "$dnsmasq_features" ] || dnsmasq_has_dnssec=1 [ "${dnsmasq_features#* TFTP }" = "$dnsmasq_features" ] || dnsmasq_has_tftp=1 [ "${dnsmasq_features#* ipset }" = "$dnsmasq_features" ] || dnsmasq_has_ipset=1 [ "${dnsmasq_features#* nftset }" = "$dnsmasq_features" ] || dnsmasq_has_nftset=1 fi case "$opt" in dhcp-duid|\ ra-param) [ -z "$dnsmasq_has_dhcp6" ] ;; dhcp-*|\ bootp-*|\ pxe-*) [ -z "$dnsmasq_has_dhcp" ] ;; dnssec*|\ trust-anchor) if [ -z "$dnsmasq_has_dnssec" ]; then echo "dnsmasq: \"$opt\" requested, but dnssec support is not available" >&2 exit 1 fi return 1 ;; tftp-*) [ -z "$dnsmasq_has_tftp" ] ;; ipset) [ -z "$dnsmasq_has_ipset" ] ;; nftset) [ -z "$dnsmasq_has_nftset" ] ;; *) return 1 esac } xappend() { local value="${1#--}" local opt="${value%%=*}" if ! dnsmasq_ignore_opt "$opt"; then echo "$value" >>"$CONFIGFILE_TMP" fi } 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 } dhcp_calc() { local ip="$1" local res=0 while [ -n "$ip" ]; do part="${ip%%.*}" res="$(($res * 256))" res="$(($res + $part))" [ "${ip%.*}" != "$ip" ] && ip="${ip#*.}" || ip= done echo "$res" } dhcp_check() { local ifname="$1" local stamp="${BASEDHCPSTAMPFILE_CFG}.${ifname}.dhcp" local rv=0 [ -s "$stamp" ] && return $(cat "$stamp") # If interface is down, skip it. # The init script will be called again once the link is up case "$(devstatus "$ifname" | jsonfilter -e @.up)" in false) return 1;; esac udhcpc -n -q -s /bin/true -t 1 -i "$ifname" >&- && rv=1 || rv=0 echo $rv > "$stamp" return $rv } log_once() { pidof dnsmasq >/dev/null || \ logger -t dnsmasq "$@" } has_handler() { local file for file in /etc/hotplug.d/dhcp/* /etc/hotplug.d/tftp/* /etc/hotplug.d/neigh/*; do [ -f "$file" ] && return 0 done return 1 } append_bool() { local section="$1" local option="$2" local value="$3" local default="$4" local _loctmp [ -z "$default" ] && default="0" config_get_bool _loctmp "$section" "$option" "$default" [ $_loctmp -gt 0 ] && xappend "$value" } append_parm() { local section="$1" local option="$2" local switch="$3" local default="$4" local _loctmp config_get _loctmp "$section" "$option" "$default" [ -z "$_loctmp" ] && return 0 xappend "$switch=$_loctmp" } append_server() { xappend "--server=$1" } append_rev_server() { xappend "--rev-server=$1" } append_address() { xappend "--address=$1" } append_connmark_allowlist() { xappend "--connmark-allowlist=$1" } append_interface() { network_get_device ifname "$1" || ifname="$1" xappend "--interface=$ifname" } append_listenaddress() { xappend "--listen-address=$1" } append_notinterface() { network_get_device ifname "$1" || ifname="$1" xappend "--except-interface=$ifname" } ismounted() { local filename="$1" local dirname for dirname in $EXTRA_MOUNT ; do case "$filename" in "${dirname}/"* | "${dirname}" ) return 0 ;; esac done return 1 } append_extramount() { ismounted "$1" || append EXTRA_MOUNT "$1" } append_addnhosts() { append_extramount "$1" xappend "--addn-hosts=$1" } append_bogusnxdomain() { xappend "--bogus-nxdomain=$1" } append_pxe_service() { xappend "--pxe-service=$1" } append_interface_name() { xappend "--interface-name=$1,$2" } filter_dnsmasq() { local cfg="$1" func="$2" match_cfg="$3" found_cfg # use entry when no instance entry set, or if it matches config_get found_cfg "$cfg" "instance" if [ -z "$found_cfg" ] || [ "$found_cfg" = "$match_cfg" ]; then $func $cfg fi } dhcp_subscrid_add() { local cfg="$1" config_get networkid "$cfg" networkid [ -n "$networkid" ] || return 0 config_get subscriberid "$cfg" subscriberid [ -n "$subscriberid" ] || return 0 xappend "--dhcp-subscrid=set:$networkid,$subscriberid" config_get_bool force "$cfg" force 0 dhcp_option_add "$cfg" "$networkid" "$force" } dhcp_remoteid_add() { local cfg="$1" config_get networkid "$cfg" networkid [ -n "$networkid" ] || return 0 config_get remoteid "$cfg" remoteid [ -n "$remoteid" ] || return 0 xappend "--dhcp-remoteid=set:$networkid,$remoteid" config_get_bool force "$cfg" force 0 dhcp_option_add "$cfg" "$networkid" "$force" } dhcp_circuitid_add() { # TODO: DHCPV6 does not have circuitid; catch "option6:" local cfg="$1" config_get networkid "$cfg" networkid [ -n "$networkid" ] || return 0 config_get circuitid "$cfg" circuitid [ -n "$circuitid" ] || return 0 xappend "--dhcp-circuitid=set:$networkid,$circuitid" config_get_bool force "$cfg" force 0 dhcp_option_add "$cfg" "$networkid" "$force" } dhcp_userclass_add() { local cfg="$1" config_get networkid "$cfg" networkid [ -n "$networkid" ] || return 0 config_get userclass "$cfg" userclass [ -n "$userclass" ] || return 0 xappend "--dhcp-userclass=set:$networkid,$userclass" config_get_bool force "$cfg" force 0 dhcp_option_add "$cfg" "$networkid" "$force" } dhcp_vendorclass_add() { # TODO: DHCPV6 vendor class has stricter definitions; catch? fixup? local cfg="$1" config_get networkid "$cfg" networkid [ -n "$networkid" ] || return 0 config_get vendorclass "$cfg" vendorclass [ -n "$vendorclass" ] || return 0 xappend "--dhcp-vendorclass=set:$networkid,$vendorclass" config_get_bool force "$cfg" force 0 dhcp_option_add "$cfg" "$networkid" "$force" } dhcp_match_add() { local cfg="$1" config_get networkid "$cfg" networkid [ -n "$networkid" ] || return 0 config_get match "$cfg" match [ -n "$match" ] || return 0 xappend "--dhcp-match=set:$networkid,$match" config_get_bool force "$cfg" force 0 dhcp_option_add "$cfg" "$networkid" "$force" } dhcp_host_add() { local cfg="$1" local hosttag nametime addrs duids macs tags mtags config_get_bool force "$cfg" force 0 config_get networkid "$cfg" networkid [ -n "$networkid" ] && dhcp_option_add "$cfg" "$networkid" "$force" config_get_bool enable "$cfg" enable 1 [ "$enable" = "0" ] && return 0 config_get name "$cfg" name config_get ip "$cfg" ip config_get hostid "$cfg" hostid [ -z "$ip" ] && [ -z "$name" ] && [ -z "$hostid" ] && return 0 config_get_bool dns "$cfg" dns 0 [ "$dns" = "1" ] && [ -n "$ip" ] && [ -n "$name" ] && { echo "$ip $name${DOMAIN:+.$DOMAIN}" >> "$HOSTFILE_TMP" } config_get mac "$cfg" mac config_get duid "$cfg" duid config_get tag "$cfg" tag add_tag() { mtags="${mtags}tag:$1," } config_list_foreach "$cfg" match_tag add_tag if [ -n "$mac" ]; then # --dhcp-host=00:20:e0:3b:13:af,192.168.0.199,lap # many MAC are possible to track a laptop ON/OFF dock for m in $mac; do append macs "$m" ","; done fi if [ $DNSMASQ_DHCP_VER -eq 6 ] && [ -n "$duid" ]; then # --dhcp-host=id:00:03:00:01:12:00:00:01:02:03,[::beef],lap # one (virtual) machine gets one DUID per RFC3315 duids="id:${duid// */}" fi if [ -z "$macs" ] && [ -z "$duids" ]; then # --dhcp-host=lap,192.168.0.199,[::beef] [ -n "$name" ] || return 0 macs="$name" name="" fi if [ -n "$hostid" ]; then hex_to_hostid hostid "$hostid" fi if [ -n "$tag" ]; then for t in $tag; do append tags "$t" ",set:"; done fi config_get_bool broadcast "$cfg" broadcast 0 config_get leasetime "$cfg" leasetime [ "$broadcast" = "0" ] && broadcast= || broadcast=",set:needs-broadcast" hosttag="${networkid:+,set:${networkid}}${tags:+,set:${tags}}$broadcast" nametime="${name:+,$name}${leasetime:+,$leasetime}" if [ $DNSMASQ_DHCP_VER -eq 6 ]; then addrs="${ip:+,$ip}${hostid:+,[::$hostid]}" xappend "--dhcp-host=$mtags$macs${duids:+,$duids}$hosttag$addrs$nametime" else xappend "--dhcp-host=$mtags$macs$hosttag${ip:+,$ip}$nametime" fi } dhcp_this_host_add() { local net="$1" local ifname="$2" local mode="$3" local routerstub routername ifdashname local lanaddr lanaddr6 lanaddrs6 ulaprefix if [ "$mode" -gt 0 ] ; then ifdashname="${ifname//./-}" routerstub="$( md5sum /etc/os-release )" routerstub="router-${routerstub// */}" routername="$( uci_get system @system[0] hostname $routerstub )" if [ "$mode" -gt 1 ] ; then if [ "$mode" -gt 2 ] ; then if [ "$mode" -gt 3 ] ; then append_interface_name "$ifdashname.$routername.$DOMAIN" "$ifname" fi append_interface_name "$routername.$DOMAIN" "$ifname" fi # All IP addresses discovered by dnsmasq will be labeled (except fe80::) append_interface_name "$routername" "$ifname" else # This uses a static host file entry for only limited addresses. # Use dnsmasq option "--expandhosts" to enable FQDN on host files. ulaprefix="$(uci_get network @globals[0] ula_prefix)" network_get_ipaddr lanaddr "$net" network_get_ipaddrs6 lanaddrs6 "$net" if [ -n "$lanaddr" ] ; then dhcp_domain_add "" "$routername" "$lanaddr" fi if [ -n "$ulaprefix" ] && [ -n "$lanaddrs6" ] ; then for lanaddr6 in $lanaddrs6 ; do case "$lanaddr6" in "${ulaprefix%%:/*}"*) dhcp_domain_add "" "$routername" "$lanaddr6" ;; esac done fi fi fi } dhcp_tag_add() { # NOTE: dnsmasq has explicit "option6:" prefix for DHCPv6 so no collisions local cfg="$1" tag="$cfg" [ -n "$tag" ] || return 0 config_get_bool force "$cfg" force 0 [ "$force" = "0" ] && force= config_get option "$cfg" dhcp_option for o in $option; do xappend "--dhcp-option${force:+-force}=tag:$tag,$o" done } dhcp_mac_add() { local cfg="$1" config_get networkid "$cfg" networkid [ -n "$networkid" ] || return 0 config_get mac "$cfg" mac [ -n "$mac" ] || return 0 xappend "--dhcp-mac=$networkid,$mac" dhcp_option_add "$cfg" "$networkid" } dhcp_boot_add() { # TODO: BOOTURL is different between DHCPv4 and DHCPv6 local cfg="$1" config_get networkid "$cfg" networkid config_get filename "$cfg" filename [ -n "$filename" ] || return 0 config_get servername "$cfg" servername config_get serveraddress "$cfg" serveraddress [ -n "$serveraddress" ] && [ ! -n "$servername" ] && return 0 xappend "--dhcp-boot=${networkid:+tag:$networkid,}${filename}${servername:+,$servername}${serveraddress:+,$serveraddress}" config_get_bool force "$cfg" force 0 dhcp_option_add "$cfg" "$networkid" "$force" } dhcp_add() { local cfg="$1" local dhcp6range="::" local nettag local tags config_get net "$cfg" interface [ -n "$net" ] || return 0 config_get networkid "$cfg" networkid [ -n "$networkid" ] || networkid="$net" network_get_device ifname "$net" || return 0 [ "$cachelocal" = "0" ] && network_get_dnsserver dnsserver "$net" && { DNS_SERVERS="$DNS_SERVERS $dnsserver" } append_bool "$cfg" ignore "--no-dhcp-interface=$ifname" && { # Many ISP do not have useful names for DHCP customers (your WAN). dhcp_this_host_add "$net" "$ifname" "$ADD_WAN_FQDN" return 0 } network_get_subnet subnet "$net" || return 0 network_get_protocol proto "$net" || return 0 # Do not support non-static interfaces for now [ static = "$proto" ] || return 0 ipaddr="${subnet%%/*}" prefix_or_netmask="${subnet##*/}" # Override interface netmask with dhcp config if applicable config_get netmask "$cfg" netmask [ -n "$netmask" ] && prefix_or_netmask="$netmask" #check for an already active dhcp server on the interface, unless 'force' is set config_get_bool force "$cfg" force 0 [ $force -gt 0 ] || dhcp_check "$ifname" || { logger -t dnsmasq \ "found already running DHCP-server on interface '$ifname'" \ "refusing to start, use 'option force 1' to override" return 0 } config_get start "$cfg" start 100 config_get limit "$cfg" limit 150 config_get leasetime "$cfg" leasetime 12h config_get options "$cfg" options config_get_bool dynamicdhcp "$cfg" dynamicdhcp 1 config_get_bool dynamicdhcpv4 "$cfg" dynamicdhcpv4 $dynamicdhcp config_get_bool dynamicdhcpv6 "$cfg" dynamicdhcpv6 $dynamicdhcp config_get dhcpv4 "$cfg" dhcpv4 config_get dhcpv6 "$cfg" dhcpv6 config_get ra "$cfg" ra config_get ra_management "$cfg" ra_management config_get ra_preference "$cfg" ra_preference config_get dns "$cfg" dns config_get dns_sl "$cfg" domain config_list_foreach "$cfg" "interface_name" append_interface_name "$ifname" # Put the router host name on this DHCP served interface address(es) dhcp_this_host_add "$net" "$ifname" "$ADD_LOCAL_FQDN" start="$( dhcp_calc "$start" )" add_tag() { tags="${tags}tag:$1," } config_list_foreach "$cfg" tag add_tag nettag="${networkid:+set:${networkid},}" # make sure the DHCP range is not empty if [ "$dhcpv4" != "disabled" ] && ipcalc "$ipaddr/$prefix_or_netmask" "$start" "$limit" ; then [ "$dynamicdhcpv4" = "0" ] && END="static" xappend "--dhcp-range=$tags$nettag$START,$END,$NETMASK,$leasetime${options:+ $options}" fi if [ "$dynamicdhcpv6" = "0" ] ; then dhcp6range="::,static" else dhcp6range="::1000,::ffff" fi if [ $DNSMASQ_DHCP_VER -eq 6 ] && [ "$ra" = "server" ] ; then # Note: dnsmasq cannot just be a DHCPv6 server (all-in-1) # and let some other machine(s) send RA pointing to it. case $ra_preference in *high*) xappend "--ra-param=$ifname,high,0,7200" ;; *low*) xappend "--ra-param=$ifname,low,0,7200" ;; *) # Send UNSOLICITED RA at default interval and live for 2 hours. # TODO: convert flexible lease time into route life time (only seconds). xappend "--ra-param=$ifname,0,7200" ;; esac if [ "$dhcpv6" = "disabled" ] ; then ra_management="3" fi case $ra_management in 0) # SLACC with DCHP for extended options xappend "--dhcp-range=$nettag::,constructor:$ifname,ra-stateless,ra-names" ;; 2) # DHCP address and RA only for management redirection xappend "--dhcp-range=$nettag$dhcp6range,constructor:$ifname,$leasetime" ;; 3) # SLAAC only but dnsmasq attempts to link HOSTNAME, DHCPv4 MAC, and SLAAC xappend "--dhcp-range=$nettag::,constructor:$ifname,ra-only,ra-names" ;; *) # SLAAC and full DHCP xappend "--dhcp-range=$nettag$dhcp6range,constructor:$ifname,slaac,ra-names,$leasetime" ;; esac if [ -n "$dns" ]; then dnss="" for d in $dns; do append dnss "[$d]" ","; done else dnss="[::]" fi dhcp_option_append "option6:dns-server,$dnss" "$networkid" if [ -n "$dns_sl" ]; then ddssl="" for dd in $dns_sl; do append ddssl "$dd" ","; done fi dhcp_option_append "option6:domain-search,$ddssl" "$networkid" fi dhcp_option_add "$cfg" "$networkid" 0 dhcp_option_add "$cfg" "$networkid" 2 } dhcp_option_append() { local option="$1" local networkid="$2" local force="$3" xappend "--dhcp-option${force:+-force}=${networkid:+$networkid,}$option" } dhcp_option_add() { # NOTE: dnsmasq has explicit "option6:" prefix for DHCPv6 so no collisions local cfg="$1" local networkid="$2" local force="$3" local opt="dhcp_option" [ "$force" = "0" ] && force= [ "$force" = "2" ] && opt="dhcp_option_force" local list_len config_get list_len "$cfg" "${opt}_LENGTH" if [ -n "$list_len" ]; then config_list_foreach "$cfg" "$opt" dhcp_option_append "$networkid" "$force" else config_get dhcp_option "$cfg" "$opt" [ -n "$dhcp_option" ] && echo "Warning: the 'option $opt' syntax is deprecated, use 'list $opt'" >&2 local option for option in $dhcp_option; do dhcp_option_append "$option" "$networkid" "$force" done fi } dhcp_domain_add() { local cfg="$1" local ip name names record config_get names "$cfg" name "$2" [ -n "$names" ] || return 0 config_get ip "$cfg" ip "$3" [ -n "$ip" ] || return 0 for name in $names; do record="${record:+$record }$name" done echo "$ip $record" >> "$HOSTFILE_TMP" } dhcp_srv_add() { local cfg="$1" 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 class "$cfg" class config_get weight "$cfg" weight local service="$srv,$target,$port${class:+,$class${weight:+,$weight}}" xappend "--srv-host=$service" } dhcp_mx_add() { local cfg="$1" local domain relay pref config_get domain "$cfg" domain [ -n "$domain" ] || return 0 config_get relay "$cfg" relay [ -n "$relay" ] || return 0 config_get pref "$cfg" pref 0 local service="$domain,$relay,$pref" xappend "--mx-host=$service" } dhcp_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 xappend "--cname=${cname},${target}" } dhcp_hostrecord_add() { local cfg="$1" local names addresses record val config_get names "$cfg" name "$2" if [ -z "$names" ]; then return 0 fi config_get addresses "$cfg" ip "$3" if [ -z "$addresses" ]; then return 0 fi for val in $names $addresses; do record="${record:+$record,}$val" done xappend "--host-record=$record" } dhcp_dnsrr_add() { #This adds arbitrary resource record types (of IN class) whose optional data must be hex local cfg="$1" local rrname rrnumber hexdata config_get rrname "$cfg" rrname [ -n "$rrname" ] || return 0 config_get rrnumber "$cfg" rrnumber [ -n "$rrnumber" ] && [ "$rrnumber" -gt 0 ] || return 0 config_get hexdata "$cfg" hexdata # dnsmasq accepts colon XX:XX:.., space XX XX .., or contiguous XXXX.. hex forms or mixtures thereof if [ -n "${hexdata//[0-9a-fA-F\:\ ]/}" ]; then # is invalid hex literal echo "dnsmasq: \"$hexdata\" is malformed hexadecimal (separate hex with colon, space or not at all)." >&2 return 1 fi xappend "--dns-rr=${rrname},${rrnumber}${hexdata:+,$hexdata}" } dhcp_relay_add() { local cfg="$1" local local_addr server_addr interface config_get local_addr "$cfg" local_addr [ -n "$local_addr" ] || return 0 config_get server_addr "$cfg" server_addr [ -n "$server_addr" ] || return 0 config_get interface "$cfg" interface if [ -z "$interface" ]; then xappend "--dhcp-relay=$local_addr,$server_addr" else network_get_device ifname "$interface" || return xappend "--dhcp-relay=$local_addr,$server_addr,$ifname" fi } dnsmasq_ipset_add() { local cfg="$1" local ipsets nftsets domains add_ipset() { ipsets="${ipsets:+$ipsets,}$1" } add_nftset() { local IFS=, for set in $1; do local fam="$family" [ -n "$fam" ] || fam=$(echo "$set" | sed -nre \ 's#^.*[^0-9]([46])$|^.*[-_]([46])[-_].*$|^([46])[^0-9].*$#\1\2\3#p') [ -n "$fam" ] || \ fam=$(nft -t list set "$table_family" "$table" "$set" 2>&1 | sed -nre \ 's#^\t\ttype .*\bipv([46])_addr\b.*$#\1#p') [ -n "$fam" ] || \ logger -t dnsmasq "Cannot infer address family from non-existent nftables set '$set'" nftsets="${nftsets:+$nftsets,}${fam:+$fam#}$table_family#$table#$set" done } add_domain() { # leading '/' is expected domains="$domains/$1" } config_get table "$cfg" table 'fw4' config_get table_family "$cfg" table_family 'inet' if [ "$table_family" = "ip" ] ; then family="4" elif [ "$table_family" = "ip6" ] ; then family="6" else config_get family "$cfg" family fi config_list_foreach "$cfg" "name" add_ipset config_list_foreach "$cfg" "name" add_nftset config_list_foreach "$cfg" "domain" add_domain if [ -z "$ipsets" ] || [ -z "$nftsets" ] || [ -z "$domains" ]; then return 0 fi xappend "--ipset=$domains/$ipsets" xappend "--nftset=$domains/$nftsets" } dnsmasq_start() { local cfg="$1" local disabled user_dhcpscript logfacility local resolvfile resolvdir localuse=1 config_get_bool disabled "$cfg" disabled 0 [ "$disabled" -gt 0 ] && return 0 # reset list of DOMAINS, DNS servers and EXTRA mounts (for each dnsmasq instance) DNS_SERVERS="" DOMAIN="" EXTRA_MOUNT="" CONFIGFILE="${BASECONFIGFILE}.${cfg}" CONFIGFILE_TMP="${CONFIGFILE}.$$" HOSTFILE="${BASEHOSTFILE}.${cfg}" HOSTFILE_TMP="${HOSTFILE}.$$" HOSTFILE_DIR="$(dirname "$HOSTFILE")" BASEDHCPSTAMPFILE_CFG="${BASEDHCPSTAMPFILE}.${cfg}" # before we can call xappend umask u=rwx,g=rx,o=rx mkdir -p /var/run/dnsmasq/ mkdir -p "$(dirname "$CONFIGFILE")" mkdir -p "$HOSTFILE_DIR" mkdir -p /var/lib/misc chown dnsmasq:dnsmasq /var/run/dnsmasq echo "# auto-generated config file from /etc/config/dhcp" > "$CONFIGFILE_TMP" echo "# auto-generated config file from /etc/config/dhcp" > "$HOSTFILE_TMP" local dnsmasqconffile="/etc/dnsmasq.${cfg}.conf" if [ ! -r "$dnsmasqconffile" ]; then dnsmasqconffile=/etc/dnsmasq.conf fi # if we did this last, we could override auto-generated config [ -f "${dnsmasqconffile}" ] && { xappend "--conf-file=${dnsmasqconffile}" } $PROG --version | grep -osqE "^Compile time options:.* DHCPv6( |$)" && DHCPv6CAPABLE=1 || DHCPv6CAPABLE=0 if [ -x /usr/sbin/odhcpd ] && [ -x /etc/init.d/odhcpd ] ; then local odhcpd_is_main odhcpd_is_enabled config_get odhcpd_is_main odhcpd maindhcp 0 /etc/init.d/odhcpd enabled && odhcpd_is_enabled=1 || odhcpd_is_enabled=0 if [ "$odhcpd_is_enabled" -eq 0 ] && [ "$DHCPv6CAPABLE" -eq 1 ] ; then # DHCP V4 and V6 in DNSMASQ DNSMASQ_DHCP_VER=6 elif [ "$odhcpd_is_main" -gt 0 ] ; then # ODHCPD is doing it all DNSMASQ_DHCP_VER=0 else # You have ODHCPD but use DNSMASQ for DHCPV4 DNSMASQ_DHCP_VER=4 fi elif [ "$DHCPv6CAPABLE" -eq 1 ] ; then # DHCP V4 and V6 in DNSMASQ DNSMASQ_DHCP_VER=6 else DNSMASQ_DHCP_VER=4 fi # Allow DHCP/DHCPv6 to be handled by ISC DHCPD if [ -x /usr/sbin/dhcpd ] ; then if [ -x /etc/init.d/dhcpd ] ; then /etc/init.d/dhcpd enabled && DNSMASQ_DHCP_VER=0 fi if [ -x /etc/init.d/dhcpd6 ] && [ "$DNSMASQ_DHCP_VER" -gt 0 ] ; then /etc/init.d/dhcpd6 enabled && DNSMASQ_DHCP_VER=4 fi fi append_bool "$cfg" authoritative "--dhcp-authoritative" append_bool "$cfg" nodaemon "--no-daemon" append_bool "$cfg" domainneeded "--domain-needed" append_bool "$cfg" filterwin2k "--filterwin2k" append_bool "$cfg" nohosts "--no-hosts" append_bool "$cfg" nonegcache "--no-negcache" append_bool "$cfg" strictorder "--strict-order" append_bool "$cfg" logqueries "--log-queries=extra" append_bool "$cfg" noresolv "--no-resolv" append_bool "$cfg" localise_queries "--localise-queries" append_bool "$cfg" readethers "--read-ethers" local instance_name="dnsmasq.$cfg" if [ "$cfg" = "$DEFAULT_INSTANCE" ]; then instance_name="dnsmasq" fi config_get_bool dbus "$cfg" "dbus" 0 [ $dbus -gt 0 ] && xappend "--enable-dbus=uk.org.thekelleys.$instance_name" config_get_bool ubus "$cfg" "ubus" 1 [ $ubus -gt 0 ] && xappend "--enable-ubus=$instance_name" append_bool "$cfg" expandhosts "--expand-hosts" config_get tftp_root "$cfg" "tftp_root" [ -n "$tftp_root" ] && mkdir -p "$tftp_root" && append_bool "$cfg" enable_tftp "--enable-tftp" append_bool "$cfg" tftp_no_fail "--tftp-no-fail" append_bool "$cfg" nonwildcard "--bind-dynamic" 1 append_bool "$cfg" fqdn "--dhcp-fqdn" append_bool "$cfg" proxydnssec "--proxy-dnssec" append_bool "$cfg" localservice "--local-service" append_bool "$cfg" logdhcp "--log-dhcp" append_bool "$cfg" quietdhcp "--quiet-dhcp" append_bool "$cfg" sequential_ip "--dhcp-sequential-ip" append_bool "$cfg" allservers "--all-servers" append_bool "$cfg" noping "--no-ping" append_bool "$cfg" rapidcommit "--dhcp-rapid-commit" append_bool "$cfg" scriptarp "--script-arp" # deprecate or remove filter-X in favor of filter-rr? append_bool "$cfg" filter_aaaa "--filter-AAAA" append_bool "$cfg" filter_a "--filter-A" append_parm "$cfg" filter_rr "--filter-rr" append_parm "$cfg" cache_rr "--cache-rr" append_parm "$cfg" logfacility "--log-facility" config_get logfacility "$cfg" "logfacility" append_parm "$cfg" cachesize "--cache-size" append_parm "$cfg" dnsforwardmax "--dns-forward-max" append_parm "$cfg" port "--port" append_parm "$cfg" ednspacket_max "--edns-packet-max" append_parm "$cfg" dhcpleasemax "--dhcp-lease-max" append_parm "$cfg" "queryport" "--query-port" append_parm "$cfg" "minport" "--min-port" append_parm "$cfg" "maxport" "--max-port" append_parm "$cfg" "domain" "--domain" append_parm "$cfg" "local" "--local" config_list_foreach "$cfg" "listen_address" append_listenaddress config_list_foreach "$cfg" "server" append_server config_list_foreach "$cfg" "rev_server" append_rev_server config_list_foreach "$cfg" "address" append_address local connmark_allowlist_enable config_get connmark_allowlist_enable "$cfg" connmark_allowlist_enable 0 [ "$connmark_allowlist_enable" -gt 0 ] && { append_parm "$cfg" "connmark_allowlist_enable" "--connmark-allowlist-enable" config_list_foreach "$cfg" "connmark_allowlist" append_connmark_allowlist } [ -n "$BOOT" ] || { config_list_foreach "$cfg" "interface" append_interface config_list_foreach "$cfg" "notinterface" append_notinterface } config_get_bool ignore_hosts_dir "$cfg" ignore_hosts_dir 0 if [ "$ignore_hosts_dir" = "1" ]; then xappend "--addn-hosts=$HOSTFILE" append EXTRA_MOUNT "$HOSTFILE" else xappend "--addn-hosts=$HOSTFILE_DIR" append EXTRA_MOUNT "$HOSTFILE_DIR" fi config_list_foreach "$cfg" "addnhosts" append_addnhosts config_list_foreach "$cfg" "bogusnxdomain" append_bogusnxdomain append_parm "$cfg" "leasefile" "--dhcp-leasefile" "/tmp/dhcp.leases" local serversfile config_get serversfile "$cfg" "serversfile" [ -n "$serversfile" ] && { xappend "--servers-file=$serversfile" append EXTRA_MOUNT "$serversfile" } append_parm "$cfg" "tftp_root" "--tftp-root" append_parm "$cfg" "dhcp_boot" "--dhcp-boot" append_parm "$cfg" "local_ttl" "--local-ttl" append_parm "$cfg" "max_ttl" "--max-ttl" append_parm "$cfg" "min_cache_ttl" "--min-cache-ttl" append_parm "$cfg" "max_cache_ttl" "--max-cache-ttl" append_parm "$cfg" "pxe_prompt" "--pxe-prompt" append_parm "$cfg" "tftp_unique_root" "--tftp-unique-root" config_list_foreach "$cfg" "pxe_service" append_pxe_service config_get DOMAIN "$cfg" domain config_get_bool ADD_LOCAL_DOMAIN "$cfg" add_local_domain 1 config_get_bool ADD_LOCAL_HOSTNAME "$cfg" add_local_hostname 1 config_get ADD_LOCAL_FQDN "$cfg" add_local_fqdn "" config_get ADD_WAN_FQDN "$cfg" add_wan_fqdn 0 if [ -z "$ADD_LOCAL_FQDN" ] ; then # maintain support for previous UCI ADD_LOCAL_FQDN="$ADD_LOCAL_HOSTNAME" fi config_get user_dhcpscript $cfg dhcpscript if has_handler || [ -n "$user_dhcpscript" ]; then xappend "--dhcp-script=$DHCPSCRIPT" xappend "--script-arp" fi config_get leasefile $cfg leasefile "/tmp/dhcp.leases" [ -n "$leasefile" ] && [ ! -e "$leasefile" ] && touch "$leasefile" config_get_bool cachelocal "$cfg" cachelocal 1 config_get_bool noresolv "$cfg" noresolv 0 if [ "$noresolv" != "1" ]; then config_get resolvfile "$cfg" resolvfile /tmp/resolv.conf.d/resolv.conf.auto [ -n "$resolvfile" ] && [ ! -e "$resolvfile" ] && touch "$resolvfile" xappend "--resolv-file=$resolvfile" [ "$resolvfile" != "/tmp/resolv.conf.d/resolv.conf.auto" ] && localuse=0 resolvdir="$(dirname "$resolvfile")" fi config_get_bool localuse "$cfg" localuse "$localuse" config_get hostsfile "$cfg" dhcphostsfile [ -e "$hostsfile" ] && xappend "--dhcp-hostsfile=$hostsfile" local rebind config_get_bool rebind "$cfg" rebind_protection 1 [ $rebind -gt 0 ] && { log_once \ "DNS rebinding protection is active," \ "will discard upstream RFC1918 responses!" xappend "--stop-dns-rebind" local rebind_localhost config_get_bool rebind_localhost "$cfg" rebind_localhost 0 [ $rebind_localhost -gt 0 ] && { log_once "Allowing 127.0.0.0/8 responses" xappend "--rebind-localhost-ok" } append_rebind_domain() { log_once "Allowing RFC1918 responses for domain $1" xappend "--rebind-domain-ok=$1" } config_list_foreach "$cfg" rebind_domain append_rebind_domain } config_get_bool dnssec "$cfg" dnssec 0 [ "$dnssec" -gt 0 ] && { xappend "--conf-file=$TRUSTANCHORSFILE" xappend "--dnssec" [ -x /etc/init.d/sysntpd ] && { if /etc/init.d/sysntpd enabled || [ "$(uci_get system.ntp.enabled)" = "1" ] ; then [ -f "$TIMEVALIDFILE" ] || xappend "--dnssec-no-timecheck" fi } config_get_bool dnsseccheckunsigned "$cfg" dnsseccheckunsigned 1 [ "$dnsseccheckunsigned" -eq 0 ] && xappend "--dnssec-check-unsigned=no" } config_get addmac "$cfg" addmac 0 [ "$addmac" != "0" ] && { [ "$addmac" = "1" ] && addmac= xappend "--add-mac${addmac:+="$addmac"}" } append_bool "$cfg" stripmac "--strip-mac" append_parm "$cfg" addsubnet "--add-subnet" append_bool "$cfg" stripsubnet "--strip-subnet" dhcp_option_add "$cfg" "" 0 dhcp_option_add "$cfg" "" 2 xappend "--dhcp-broadcast=tag:needs-broadcast" # Create a dnsmasq.d dir for each instance config_get dnsmasqconfdir "$cfg" confdir "/tmp/dnsmasq${cfg:+.$cfg}.d" # Ensure dnsmasqconfdir is an absolute path [ "${dnsmasqconfdir:0:1}" = '/' ] && { xappend "--conf-dir=$dnsmasqconfdir" dnsmasqconfdir="${dnsmasqconfdir%%,*}" [ ! -d "$dnsmasqconfdir" ] && mkdir -p "$dnsmasqconfdir" xappend "--user=dnsmasq" xappend "--group=dnsmasq" echo >> "$CONFIGFILE_TMP" # EXTRACONFFILE allows new dnsmasq parameters before they are natively handled in this init file config_get extraconftext "$cfg" extraconftext [ -n "$extraconftext" ] && echo -e "$extraconftext" > "$dnsmasqconfdir"/"$EXTRACONFFILE" } config_get_bool enable_tftp "$cfg" enable_tftp 0 [ "$enable_tftp" -gt 0 ] && { config_get tftp_root "$cfg" tftp_root append EXTRA_MOUNT $tftp_root } config_foreach filter_dnsmasq host dhcp_host_add "$cfg" echo >> "$CONFIGFILE_TMP" config_get_bool dhcpbogushostname "$cfg" dhcpbogushostname 1 [ "$dhcpbogushostname" -gt 0 ] && { xappend "--dhcp-ignore-names=tag:dhcp_bogus_hostname" [ -r "$DHCPBOGUSHOSTNAMEFILE" ] && xappend "--conf-file=$DHCPBOGUSHOSTNAMEFILE" } config_foreach filter_dnsmasq boot dhcp_boot_add "$cfg" config_foreach filter_dnsmasq mac dhcp_mac_add "$cfg" config_foreach filter_dnsmasq tag dhcp_tag_add "$cfg" config_foreach filter_dnsmasq vendorclass dhcp_vendorclass_add "$cfg" config_foreach filter_dnsmasq userclass dhcp_userclass_add "$cfg" config_foreach filter_dnsmasq circuitid dhcp_circuitid_add "$cfg" config_foreach filter_dnsmasq remoteid dhcp_remoteid_add "$cfg" config_foreach filter_dnsmasq subscrid dhcp_subscrid_add "$cfg" config_foreach filter_dnsmasq match dhcp_match_add "$cfg" config_foreach filter_dnsmasq domain dhcp_domain_add "$cfg" config_foreach filter_dnsmasq hostrecord dhcp_hostrecord_add "$cfg" config_foreach filter_dnsmasq dnsrr dhcp_dnsrr_add "$cfg" [ -n "$BOOT" ] || config_foreach filter_dnsmasq relay dhcp_relay_add "$cfg" echo >> "$CONFIGFILE_TMP" config_foreach filter_dnsmasq srvhost dhcp_srv_add "$cfg" config_foreach filter_dnsmasq mxhost dhcp_mx_add "$cfg" echo >> "$CONFIGFILE_TMP" config_get_bool boguspriv "$cfg" boguspriv 1 [ "$boguspriv" -gt 0 ] && { xappend "--bogus-priv" [ -r "$RFC6761FILE" ] && xappend "--conf-file=$RFC6761FILE" } if [ "$DNSMASQ_DHCP_VER" -gt 4 ] ; then # Enable RA feature for when/if it is constructed, # and RA is selected per interface pool (RA, DHCP, or both), # but no one (should) want RA broadcast in syslog [ -n "$BOOT" ] || config_foreach filter_dnsmasq dhcp dhcp_add "$cfg" xappend "--enable-ra" xappend "--quiet-ra" append_bool "$cfg" quietdhcp "--quiet-dhcp6" elif [ "$DNSMASQ_DHCP_VER" -gt 0 ] ; then [ -n "$BOOT" ] || config_foreach filter_dnsmasq dhcp dhcp_add "$cfg" fi echo >> "$CONFIGFILE_TMP" config_foreach filter_dnsmasq cname dhcp_cname_add "$cfg" echo >> "$CONFIGFILE_TMP" echo >> "$CONFIGFILE_TMP" config_foreach filter_dnsmasq ipset dnsmasq_ipset_add "$cfg" echo >> "$CONFIGFILE_TMP" mv -f "$CONFIGFILE_TMP" "$CONFIGFILE" mv -f "$HOSTFILE_TMP" "$HOSTFILE" [ "$localuse" -gt 0 ] && { rm -f /tmp/resolv.conf [ $ADD_LOCAL_DOMAIN -eq 1 ] && [ -n "$DOMAIN" ] && { echo "search $DOMAIN" >> /tmp/resolv.conf } DNS_SERVERS="$DNS_SERVERS 127.0.0.1" [ -e /proc/sys/net/ipv6 ] && DNS_SERVERS="$DNS_SERVERS ::1" for DNS_SERVER in $DNS_SERVERS ; do echo "nameserver $DNS_SERVER" >> /tmp/resolv.conf done } config_list_foreach "$cfg" addnmount append_extramount procd_open_instance $cfg procd_set_param command $PROG -C $CONFIGFILE -k -x /var/run/dnsmasq/dnsmasq."${cfg}".pid procd_set_param file $CONFIGFILE [ -n "$user_dhcpscript" ] && procd_set_param env USER_DHCPSCRIPT="$user_dhcpscript" procd_set_param respawn local instance_ifc instance_netdev config_get instance_ifc "$cfg" interface [ -n "$instance_ifc" ] && network_get_device instance_netdev "$instance_ifc" && [ -n "$instance_netdev" ] && procd_set_param netdev $instance_netdev procd_add_jail dnsmasq ubus log procd_add_jail_mount $CONFIGFILE $DHCPBOGUSHOSTNAMEFILE $DHCPSCRIPT $DHCPSCRIPT_DEPENDS procd_add_jail_mount $EXTRA_MOUNT $RFC6761FILE $TRUSTANCHORSFILE procd_add_jail_mount $dnsmasqconffile $dnsmasqconfdir $resolvdir $user_dhcpscript procd_add_jail_mount /etc/passwd /etc/group /etc/TZ /etc/hosts /etc/ethers procd_add_jail_mount_rw /var/run/dnsmasq/ $leasefile case "$logfacility" in */*) [ ! -e "$logfacility" ] && touch "$logfacility" procd_add_jail_mount_rw "$logfacility" esac [ -e "$hostsfile" ] && procd_add_jail_mount $hostsfile procd_close_instance } dnsmasq_stop() { local cfg="$1" local noresolv resolvfile localuse=1 config_get_bool noresolv "$cfg" noresolv 0 config_get resolvfile "$cfg" "resolvfile" [ "$noresolv" = 0 ] && [ "$resolvfile" != "/tmp/resolv.conf.d/resolv.conf.auto" ] && localuse=0 config_get_bool localuse "$cfg" localuse "$localuse" [ "$localuse" -gt 0 ] && ln -sf "/tmp/resolv.conf.d/resolv.conf.auto" /tmp/resolv.conf rm -f ${BASEDHCPSTAMPFILE}.${cfg}.*.dhcp } add_interface_trigger() { local interface ifname ignore config_get interface "$1" interface config_get_bool ignore "$1" ignore 0 network_get_device ifname "$interface" || ignore=0 [ -n "$interface" ] && [ $ignore -eq 0 ] && procd_add_interface_trigger "interface.*" "$interface" /etc/init.d/dnsmasq reload } service_triggers() { procd_add_reload_trigger "dhcp" "system" config_load dhcp config_foreach add_interface_trigger dhcp config_foreach add_interface_trigger relay } boot() { BOOT=1 start "$@" } start_service() { local instance="$1" local instance_found=0 local first_instance="" . /lib/functions/network.sh config_cb() { local type="$1" local name="$2" if [ "$type" = "dnsmasq" ]; then if [ -n "$instance" ] && [ "$instance" = "$name" ]; then instance_found=1 fi if [ -z "$DEFAULT_INSTANCE" ]; then local disabled config_get_bool disabled "$name" disabled 0 if [ "$disabled" -eq 0 ]; then # First enabled section will be assigned default instance name. # Unnamed sections get precedence over named sections. if expr "$cfg" : 'cfg[0-9a-f]*$' >/dev/null = "9"; then # See uci_fixup_section. DEFAULT_INSTANCE="$name" # Unnamed config section. elif [ -z "$first_instance" ]; then first_instance="$name" fi fi fi fi } DEFAULT_INSTANCE="" config_load dhcp if [ -z "$DEFAULT_INSTANCE" ]; then DEFAULT_INSTANCE="$first_instance" # No unnamed config section was found. fi if [ -n "$instance" ]; then [ "$instance_found" -gt 0 ] || return dnsmasq_start "$instance" else config_foreach dnsmasq_start dnsmasq fi } reload_service() { rc_procd start_service "$@" procd_send_signal dnsmasq "$@" } stop_service() { local instance="$1" local instance_found=0 config_cb() { local type="$1" local name="$2" if [ "$type" = "dnsmasq" ]; then if [ -n "$instance" ] && [ "$instance" = "$name" ]; then instance_found=1 fi fi } config_load dhcp if [ -n "$instance" ]; then [ "$instance_found" -gt 0 ] || return dnsmasq_stop "$instance" else config_foreach dnsmasq_stop dnsmasq fi }