dnsmasq: make DHCPv6 viable for standalone dnsmasq install
authorEric Luehrsen <ericluehrsen@hotmail.com>
Thu, 22 Dec 2016 18:20:25 +0000 (13:20 -0500)
committerJo-Philipp Wich <jo@mein.io>
Wed, 1 Feb 2017 23:13:49 +0000 (00:13 +0100)
dnsmasq has sufficient services to meet the needs of DHCP
and RA with IP6 for single router router users. This is
the most common use for consumer routers. Its reenforced
as most ISP tend to only DHCP-PD /64. dnsmasq has year
over year demonstrated great flexibility in its option
set, and support for off-standard DHCP clients.

odhcpd has enhanced capabilities focused on IP6 such
as DHCP/RA relay and NDP proxy. However, it is not as
flexible in its option set. odhcpd is not as forgiving
with off-standard DHCP clients. Some points may represent
a long term TODO list, but it is the state currently.

These changes make any such combination possible. Already
odhcpd can be set as the main dhcp server. Now odhcpd
can be removed or disabled and dnsmasq will take over
if DHCPv6 compiled in. The existing DHCPv6 and RA UCI
are translated into dnsmasq.conf. The changes focus on
'--dhcp-range', '--dhcp-host', and '--dhcp-options'.

DHCP host ID is least 16 bits [::1000-::FFFF], but
leaves low range for typical infrastructure assignments.
dnsmasq accepts DHCPv6 options in the tranditional
'--dhcp-option' put they must be prefixed 'option6:'.
dnsmasq will also discover SLAAC DNS entries from DHCPv4
clients MAC, and confirm with a ping at least renew.

Long term TODO include improving use of dnsmasq relay
options for DHCPv4 and DHCPv6 in parallel. It would also
be possible to preconfigure DHCP-PD in host-with-options
records for fixed infrastructure.

Signed-off-by: Eric Luehrsen <ericluehrsen@hotmail.com>
[Jo-Philipp Wich: emit proper IPv6 hostid format in dhcp-host directive]
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
package/network/services/dnsmasq/Makefile
package/network/services/dnsmasq/files/dnsmasq.init

index 9833385..a6689d1 100644 (file)
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
 
 PKG_NAME:=dnsmasq
 PKG_VERSION:=2.76
-PKG_RELEASE:=7
+PKG_RELEASE:=8
 
 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
 PKG_SOURCE_URL:=http://thekelleys.org.uk/dnsmasq
index fe66592..40c756b 100644 (file)
@@ -18,6 +18,8 @@ TRUSTANCHORSFILE="/usr/share/dnsmasq/trust-anchors.conf"
 TIMEVALIDFILE="/var/state/dnsmasqsec"
 BASEDHCPSTAMPFILE="/var/run/dnsmasq"
 
+DNSMASQ_DHCP_VER=4
+
 xappend() {
        local value="$1"
 
@@ -182,6 +184,7 @@ dhcp_remoteid_add() {
 }
 
 dhcp_circuitid_add() {
+       # TODO: DHCPV6 does not have circuitid; catch "option6:"
        local cfg="$1"
 
        config_get networkid "$cfg" networkid
@@ -214,6 +217,7 @@ dhcp_userclass_add() {
 }
 
 dhcp_vendorclass_add() {
+       # TODO: DHCPV6 vendor class has stricter definitions; catch? fixup?
        local cfg="$1"
 
        config_get networkid "$cfg" networkid
@@ -247,6 +251,7 @@ dhcp_match_add() {
 
 dhcp_host_add() {
        local cfg="$1"
+       local hosttag nametime addrs
 
        config_get_bool force "$cfg" force 0
 
@@ -258,7 +263,9 @@ dhcp_host_add() {
 
        config_get name "$cfg" name
        config_get ip "$cfg" ip
-       [ -n "$ip" -o -n "$name" ] || return 0
+       config_get hostid "$cfg" hostid
+
+       [ -n "$ip" -o -n "$name" -o -n "$hostid" ] || return 0
 
        config_get_bool dns "$cfg" dns 0
        [ "$dns" = "1" -a -n "$ip" -a -n "$name" ] && {
@@ -266,33 +273,47 @@ dhcp_host_add() {
        }
 
        config_get mac "$cfg" mac
+       config_get duid "$cfg" duid
+       config_get tag "$cfg" 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
                macs=""
                for m in $mac; do append macs "$m" ","; done
-       else
-               # --dhcp-host=lap,192.168.0.199
+       fi
+
+       if [ $DNSMASQ_DHCP_VER -eq 6 -a -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" -a -z "$duids" ]; then
+               # --dhcp-host=lap,192.168.0.199,[::beef]
                [ -n "$name" ] || return 0
                macs="$name"
                name=""
        fi
 
-       config_get tag "$cfg" tag
-
-       if [ "$DHCPv6CAPABLE" -eq 1 ]; then
-               config_get duid "$cfg" duid
-               config_get hostid "$cfg" hostid
-               if [ -n "$hostid" ]; then
+       if [ -n "$hostid" ]; then
                        hex_to_hostid hostid "$hostid"
-               fi
        fi
 
        config_get_bool broadcast "$cfg" broadcast 0
-       [ "$broadcast" = "0" ] && broadcast=
-
        config_get leasetime "$cfg" leasetime
 
-       xappend "--dhcp-host=$macs${duid:+,id:$duid}${networkid:+,net:$networkid}${broadcast:+,set:needs-broadcast}${tag:+,set:$tag}${ip:+,$ip${hostid:+,[::$hostid]}}${name:+,$name}${leasetime:+,$leasetime}"
+       [ "$broadcast" = "0" ] && broadcast= || broadcast=",set:needs-broadcast"
+
+       hosttag="${networkid:+,set:${networkid}}${tag:+,set:${tag}}$broadcast"
+       nametime="${name:+,$name}${leasetime:+,$leasetime}"
+
+       if [ $DNSMASQ_DHCP_VER -eq 6 ]; then
+               addrs="${ip:+,$ip}${hostid:+,[::$hostid]}"
+               xappend "--dhcp-host=$macs${duids:+,$duids}$hosttag$addrs$nametime"
+       else
+               xappend "--dhcp-host=$macs$hosttag${ip:+,$ip}$nametime"
+       fi
 }
 
 dhcp_this_host_add() {
@@ -345,6 +366,7 @@ dhcp_this_host_add() {
 }
 
 dhcp_tag_add() {
+       # NOTE: dnsmasq has explicit "option6:" prefix for DHCPv6 so no collisions
        local cfg="$1"
 
        tag="$cfg"
@@ -375,6 +397,7 @@ dhcp_mac_add() {
 }
 
 dhcp_boot_add() {
+       # TODO: BOOTURL is different between DHCPv4 and DHCPv6
        local cfg="$1"
 
        config_get networkid "$cfg" networkid
@@ -397,12 +420,12 @@ dhcp_boot_add() {
 
 dhcp_add() {
        local cfg="$1"
+       local dhcp6range="::"
+       local nettag
+
        config_get net "$cfg" interface
        [ -n "$net" ] || return 0
 
-       config_get dhcpv4 "$cfg" dhcpv4
-       [ "$dhcpv4" != "disabled" ] || return 0
-
        config_get networkid "$cfg" networkid
        [ -n "$networkid" ] || networkid="$net"
 
@@ -430,27 +453,92 @@ dhcp_add() {
        config_get_bool force "$cfg" force 0
        [ $force -gt 0 ] || dhcp_check "$ifname" || return 0
 
-       config_get start "$cfg" start
-       config_get limit "$cfg" limit
-       config_get leasetime "$cfg" leasetime
+       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 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
+
        # Put the router host name on this DHCP served interface address(es)
        dhcp_this_host_add "$net" "$ifname" "$ADD_LOCAL_FQDN"
 
-       leasetime="${leasetime:-12h}"
-       start="$(dhcp_calc "${start:-100}")"
-       limit="${limit:-150}"
-       [ "$limit" -gt 0 ] && limit=$((limit-1))
+       start="$( dhcp_calc "$start" )"
+       nettag="${networkid:+set:${networkid},}"
+
+       if [ "$limit" -gt 0 ] ; then
+               limit=$((limit-1))
+       fi
+
        eval "$(ipcalc.sh "${subnet%%/*}" $netmask $start $limit)"
-       if [ "$dynamicdhcp" = "0" ]; then END="static"; fi
-       xappend "--dhcp-range=$networkid,$START,$END,$NETMASK,$leasetime${options:+ $options}"
+
+       if [ "$dynamicdhcp" = "0" ] ; then
+               END="static"
+               dhcp6range="::,static"
+       else
+               dhcp6range="::1000,::ffff"
+       fi
+
+
+       if [ "$dhcpv4" != "disabled" ] ; then
+               xappend "--dhcp-range=$nettag$START,$END,$NETMASK,$leasetime${options:+ $options}"
+       fi
+
+
+       if [ $DNSMASQ_DHCP_VER -eq 6 -a "$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
+       fi
 
        dhcp_option_add "$cfg" "$networkid"
 }
 
 dhcp_option_add() {
+       # NOTE: dnsmasq has explicit "option6:" prefix for DHCPv6 so no collisions
        local cfg="$1"
        local networkid="$2"
        local force="$3"
@@ -605,6 +693,32 @@ dnsmasq_start()
 
        $PROG --version | grep -osqE "^Compile time options:.* DHCPv6( |$)" && DHCPv6CAPABLE=1 || DHCPv6CAPABLE=0
 
+
+       if [ -x /usr/sbin/odhcpd -a -x /etc/init.d/odhcpd ] ; then
+               local odhcpd_is_main odhcpd_is_enabled
+               config_get odhcpd_is_main odhcpd maindhcp
+               /etc/init.d/odhcpd enabled && odhcpd_is_enabled=1 || odhcpd_is_enabled=0
+
+
+               if [ "$odhcpd_is_enabled" -eq 0 -a "$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
+
+
        append_bool "$cfg" authoritative "--dhcp-authoritative"
        append_bool "$cfg" nodaemon "--no-daemon"
        append_bool "$cfg" domainneeded "--domain-needed"
@@ -772,11 +886,20 @@ dnsmasq_start()
        config_foreach filter_dnsmasq mxhost dhcp_mx_add "$cfg"
        echo >> $CONFIGFILE_TMP
 
-       config_get odhcpd_is_active odhcpd maindhcp
-       if [ "$odhcpd_is_active" != "1" ]; then
-               config_foreach filter_dnsmasq dhcp dhcp_add "$cfg"
+
+       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
+               config_foreach dhcp_add dhcp
+               xappend "--enable-ra"
+               xappend "--quiet-ra"
+
+       elif [ "$DNSMASQ_DHCP_VER" -gt 0 ] ; then
+               config_foreach dhcp_add dhcp
        fi
 
+
        echo >> $CONFIGFILE_TMP
        config_foreach filter_dnsmasq cname dhcp_cname_add "$cfg"
        echo >> $CONFIGFILE_TMP