1

Background I want to dynamically update my DNS A records from my DHCP server, both running on the same synology NAS. Unfortunately Synology has (still) not provided a working solution for this.

I have been using a shell script to accomplish this, but it seems to work imperfectly in that after a while (days, weeks) my DNS server does not resolve my local addresses anymore, most likely because the shell scripts puts the dns records in an incompatible order.

So..

H.Hasenack
  • 1,094
  • 8
  • 27
  • From your title: You're asking for an example, rather than showing what you've done and where it errors. Within the body of your post, you don't include any code, only a link to someone else's question where code was provided. Doing a quick search of how to update DNS with C#-- I found [this](https://blog.mikejmcguire.com/2014/06/15/creating-and-updating-dns-records-in-microsoft-dns-servers-with-c-net-and-wmi/) , along with [this](https://forums.asp.net/t/1934169.aspx?C+Check+and+create+DNS+Record+on+MS+DNS+Server) – Jaskier Nov 05 '18 at 22:42
  • Thanks, but this was wat I was not looking for; The first examples uses WMI, and both examples target a windows based DNS server. – H.Hasenack Nov 06 '18 at 18:56

3 Answers3

3

Never mind, I found a different way than c# and mono to update my local dns dynamically.

The script I tried in the first place was the one I found here, but after a couple of days my local DNS queries did not work anymore for some unknown reason.

As it turns out the dhcp server on my Synology NAS (DS214Play) calls a bash script after leases have changed, in the location /usr/share/dhcpd/dhcpd-script.sh

After I found out you can actually change your (local) dns records easily using the nsupdate command, I updated the existing script to the one below. The lines I added are marked with my initials HH.

As for security on the dns update: I did not use a key, as my DNS server only accepts updates from the internal network and local host anyway.

If you found my answer useful, please also upvote the question if it's still negative. (I answered it myself) NOTE1: I found out that DSM updates may revert the file to its original NOTE2: Some of my markings were lost, so I updated the code below

#!/bin/sh
# Copyright (c) 2000-2013 Synology Inc. All rights reserved.
#HH20191123 Updated for dynamic DNS updates

DHCPD_DIR="/etc/dhcpd/"
LEASE_FILE="${DHCPD_DIR}/dhcpd.conf.leases"
LOG_FILE="${DHCPD_DIR}/dhcpd-leases.log"
TMP_FILE="${DHCPD_DIR}/tmp-dhcpd-leases.log"


# HH20191123: Define the zone (local domain name) here, but without a trailing point
ZONE_NAME="hhbhasenack.local"

# HH20191123: Prepare a file for updating the DNS through the nsupdate command
TMP_NSUPDATE="${DHCPD_DIR}/tmp-nsupdate.log"
NSUPDATE_LOG_FILE="${DHCPD_DIR}/nsupdate.log"
echo "server 127.0.0.1" > ${TMP_NSUPDATE}
echo "zone ${ZONE_NAME}." >> ${TMP_NSUPDATE}



del_leases() { # $2: mac
    local mac=$2
    grep -v "${mac}" ${LOG_FILE} > ${TMP_FILE}
    cp ${TMP_FILE} ${LOG_FILE}
}

renew_record() { # $1: expired $2 mac $3 ip $4 hostname $5 iface
    local record=$@
    local mac=$2
    local iface=$5

    grep -v "${mac}" ${LOG_FILE} > ${TMP_FILE}
    echo "${record}" >> ${TMP_FILE}
    cp ${TMP_FILE} ${LOG_FILE}

#HH20191123: Prepare dns update command
    local ip="$3"
    local hostname="$4"
    echo "update delete ${hostname}.${ZONE_NAME} A"  >> ${TMP_NSUPDATE}
    echo "update add ${hostname}.${ZONE_NAME} 3600 A ${ip}" >> ${TMP_NSUPDATE}

}

add_new_record() {
    local record="$@"
    local mac=$2

    # when disable dhcp-server and any lease is expired, then next time the dhcp client
    # renew the lease the action will be add, so remove the old record has same MAC address
    grep -v "${mac}" ${LOG_FILE} > ${TMP_FILE}
    cp ${TMP_FILE} ${LOG_FILE}

    if [ -s ${LOG_FILE} ]; then
        sed -i "1 i${record}" ${LOG_FILE}
    else
        echo ${record} >> ${LOG_FILE}
    fi

#HH20191123: Prepare dns update command
    local ip="$3"
    local hostname="$4"
    echo "update add ${hostname}.${ZONE_NAME} 3600 A ${ip}" >> ${TMP_NSUPDATE}

}

get_hostname_from_logfile() {
    local mac="$1"
    local filename="";
    local line="`grep \"${mac}\" ${LOG_FILE}`"
    local tokens=( $line )

    if [ 5 -eq ${#tokens[@]} ]; then
        filename=${tokens[3]}
    fi

    echo $filename
}

get_new_record() {
    local mac="$2"
    local ip="$3"
    local hostname="$4"
    local fileHostname=$(get_hostname_from_logfile $mac)

    if [ "x" = "x${hostname}" ] && [ "xold" = "x${ACTION}" ];then
        if [ "x" != "x${DNSMASQ_SUPPLIED_HOSTNAME}" ]; then
            hostname=${DNSMASQ_SUPPLIED_HOSTNAME}
        elif [ "x" != "x${fileHostname}" ]; then
            hostname=${fileHostname}
        fi
    fi

    NEW_RECORD="${DNSMASQ_LEASE_EXPIRES} ${mac} ${ip} ${hostname} ${DNSMASQ_INTERFACE}"
}

# record format: action mac ip hostname
NEW_RECORD=$@
ACTION=`echo ${NEW_RECORD} | awk '{print $1}'`

if [ "${DNSMASQ_INTERFACE}" = "" ]; then
    exit 0
fi
get_new_record ${NEW_RECORD}

case "${ACTION}" in
    old)
        renew_record ${NEW_RECORD}
        ;;
    add)
        add_new_record ${NEW_RECORD}
        ;;
    del)
        del_leases ${NEW_RECORD}
        ;;
    *)
        ;;
esac


#HH20191123: complete command file for nsupdate with a send command
echo "send" >> ${TMP_NSUPDATE}

#HH20191123: actually execute the nsupdate command
nsupdate ${TMP_NSUPDATE} >>${NSUPDATE_LOG_FILE}



exit 0
H.Hasenack
  • 1,094
  • 8
  • 27
1

This didn't work for me. My zones were created as part of the installation of the Synology Directory Server. As the zone's are managed via samba no amount of finagling and mucking around with keys would work with nsupdate.

Smb-tool is already installed and handles my use case quite nicely. Other than the script the only other task is to create an unprivileged service account and add it to the "DNSAdmins" group.

Here's my version:

#!/bin/sh
# Copyright (c) 2000-2013 Synology Inc. All rights reserved.
#GB: define our variables
ZONE="example.com"
REVERSE="1.168.192.in-addr.arpa"
SERVER="127.0.0.1"
USERNAME="dhcp-to-dns"
PASSWORD="insertreallyinsecurepasswordhere"

DHCPD_DIR="/etc/dhcpd/"
LEASE_FILE="${DHCPD_DIR}/dhcpd.conf.leases"
LOG_FILE="${DHCPD_DIR}/dhcpd-leases.log"
TMP_FILE="${DHCPD_DIR}/tmp-dhcpd-leases.log"

del_leases() { # $2: mac
    local mac=$2
    local IP=$3
#GB: Define Reverse IP address and Hostname. Could have defined this global but stuck with the conventions of the original script
    local REVIP=$(echo $IP | cut -d '.' -f 4)
    local HOSTNAME=$4
    grep -v "${mac}" ${LOG_FILE} > ${TMP_FILE}
    cp ${TMP_FILE} ${LOG_FILE}
#GB: use samba-tool to delete forward/reverse DNS entries of expired leases.
    samba-tool dns delete $SERVER $ZONE $HOSTNAME A $IP --username=$USERNAME --password=$PASSWORD 2> /dev/null
    samba-tool dns delete $SERVER $REVERSE $REVIP PTR ${HOSTNAME}.${ZONE} --username=$USERNAME --password=$PASSWORD 2> /dev/null
}

renew_record() { # $1: expired $2 mac $3 ip $4 hostname $5 iface
    local record=$@
    local mac=$2
    local iface=$5

    grep -v "${mac}" ${LOG_FILE} > ${TMP_FILE}
    echo "${record}" >> ${TMP_FILE}
    cp ${TMP_FILE} ${LOG_FILE}
}

add_new_record() {
    local record="$@"
    local mac=$2
#GB: Variables again
    local IP=$3
    local REVIP=$(echo $IP | cut -d '.' -f 4)
    local HOSTNAME=$4

    # when disable dhcp-server and any lease is expired, then next time the dhcp client
    # renew the lease the action will be add, so remove the old record has same MAC address
    grep -v "${mac}" ${LOG_FILE} > ${TMP_FILE}
    cp ${TMP_FILE} ${LOG_FILE}

    if [ -s ${LOG_FILE} ]; then
        sed -i "1 i${record}" ${LOG_FILE}
    else
        echo ${record} >> ${LOG_FILE}
    fi
#GB: Use samba-tool to add forward/reverse DNS Entries.
    samba-tool dns add $SERVER $ZONE $HOSTNAME A $IP --username=$USERNAME --password=$PASSWORD 2> /dev/null
    samba-tool dns add $SERVER $REVERSE $REVIP PTR ${HOSTNAME}.${ZONE} --username=$USERNAME --password=$PASSWORD 2> /dev/null
}

get_hostname_from_logfile() {
    local mac="$1"
    local filename="";
    local line="`grep \"${mac}\" ${LOG_FILE}`"
    local tokens=( $line )

    if [ 5 -eq ${#tokens[@]} ]; then
        filename=${tokens[3]}
    fi

    echo $filename
}

get_new_record() {
    local mac="$2"
    local ip="$3"
    local hostname="$4"
    local fileHostname=$(get_hostname_from_logfile $mac)

    if [ "x" = "x${hostname}" ] && [ "xold" = "x${ACTION}" ];then
        if [ "x" != "x${DNSMASQ_SUPPLIED_HOSTNAME}" ]; then
            hostname=${DNSMASQ_SUPPLIED_HOSTNAME}
        elif [ "x" != "x${fileHostname}" ]; then
            hostname=${fileHostname}
        fi
    fi

    NEW_RECORD="${DNSMASQ_LEASE_EXPIRES} ${mac} ${ip} ${hostname} ${DNSMASQ_INTERFACE}"
}

# record format: action mac ip hostname
NEW_RECORD=$@
ACTION=`echo ${NEW_RECORD} | awk '{print $1}'`

if [ "${DNSMASQ_INTERFACE}" = "" ]; then
    exit 0
fi
get_new_record ${NEW_RECORD}

case "${ACTION}" in
    old)
        renew_record ${NEW_RECORD}
        ;;
    add)
        add_new_record ${NEW_RECORD}
        ;;
    del)
        del_leases ${NEW_RECORD}
        ;;
    *)
        ;;
esac

exit 0
  • I do not use the directory server on my NAS (yet). So could you mark the modifications you made to the file using comment lines? - That would certainly help understanding the changes you made. BTW I have lately upgraded my NAS from DS214Play to DS718Plus and there my solution also worked perfectly. – H.Hasenack Feb 06 '20 at 16:00
0

Another example using the diagram like in the NAS diagram1

The roles used for completing the diagrm from above are: Domain server, DNS server, DHCPD server. The user that is managing the DNS changes is dhcptodns (part of DNSAdmin group in Synology Directory Server). In this case the script /usr/share/dhcpd/dhcpd-script.sh is:

#!/bin/sh
# Copyright (c) 2000-2013 Synology Inc. All rights reserved.
ZONE="yourdomain.local"
REVERSE="1.168.192.in-addr.arpa"
SERVER="127.0.0.1"
USERNAME="dhcptodns"
PASSWORD="change_me_with_a_real_password"

DHCPD_DIR="/etc/dhcpd/"
LEASE_FILE="${DHCPD_DIR}/dhcpd.conf.leases"
LOG_FILE="${DHCPD_DIR}/dhcpd-leases.log"
TMP_FILE="${DHCPD_DIR}/tmp-dhcpd-leases.log"

internal_logger() {
     local _tmp_msg1="$1"
         /usr/syno/bin/synologset1 sys info 0x11100000 "DHCP:$$:${_tmp_msg1} "
     return 0
}

del_leases() {
# $1: expired
# $2: mac
# $3: ip
# $4: hostname
# $5: iface
   local del_MAC=$2
   local del_IP=$3
   local del_REVIP=$(echo ${del_IP} | cut -d '.' -f 4)
   local del_HOSTNAME=$4
   internal_logger  "del $2 $3 $4"
   grep -vi "$del_MAC" ${LOG_FILE} > ${TMP_FILE}
   cp ${TMP_FILE} ${LOG_FILE}
   cat  ${LOG_FILE}
   if [ `samba-tool dns query $SERVER $ZONE ${del_HOSTNAME} --username=${USERNAME} A --password=${PASSWORD} 2>/dev/null|grep 'A:'|sed 's/(.*//;s/.*A: //'|wc -l|cut -d ' ' -f 1` -gt 0 ] ; then
   {
     for del_i in `samba-tool dns query $SERVER $ZONE ${del_HOSTNAME} A --username=${USERNAME} --password=${PASSWORD} 2>/dev/null|grep 'A:'|sed 's/(.*//;s/.*A: //'` ; do
        internal_logger  "Record to be cleaned.... ${del_i}"
        samba-tool dns delete $SERVER $ZONE ${del_HOSTNAME} A ${del_i} --username=$USERNAME --password=${PASSWORD} 2>/dev/null
        internal_logger  "samba-tool dns delete $SERVER $ZONE ${del_HOSTNAME} A ${del_i} --username=${USERNAME} --password=<masked>"
     done
   }
   fi
   # Reverse is already cleaned up
   samba-tool dns delete $SERVER $REVERSE ${del_REVIP} PTR ${del_HOSTNAME}.${ZONE} --username=${USERNAME} --password=${PASSWORD} 2>/dev/null
   internal_logger  "samba-tool dns delete $SERVER $REVERSE ${del_REVIP} PTR ${del_HOSTNAME}.${ZONE} --username=${USERNAME} --password=<masked>"
   return 0
}

renew_record() {
# $1: expired
# $2: mac
# $3: ip
# $4: hostname
# $5: iface
   local record=$@
   local mac=$2
   local iface=$5

   grep -vi "${mac}" ${LOG_FILE} > ${TMP_FILE}
   echo "${NEW_RECORD}" >> ${TMP_FILE}
   cp ${TMP_FILE} ${LOG_FILE}
   return 0
}

add_new_record() {
# $1: expired
# $2: mac
# $3: ip
# $4: hostname
# $5: iface
   local record="$@"
   local mac=$2
   local IP=$3
   local REVIP=$(echo $IP | cut -d '.' -f 4)
   local HOSTNAME=$4

   # when disable dhcp-server and any lease is expired, then next time the dhcp client
   # renew the lease the action will be add, so remove the old record has same MAC address
   grep -v "${mac}" ${LOG_FILE} > ${TMP_FILE}
   cp ${TMP_FILE} ${LOG_FILE}
   if [ -s ${LOG_FILE} ]; then
       sed -i "1 i${record}" ${LOG_FILE}
   else
       echo ${record} >> ${LOG_FILE}
   fi
   # DEBUG
   #internal_logger `samba-tool dns query $SERVER $ZONE $HOSTNAME --username=$USERNAME A --password=$PASSWORD 2>/dev/null|grep 'A:'|sed 's/(.*//;s/.*A: //'|wc -l|cut -d ' ' -f 1`
   if [ `samba-tool dns query $SERVER $ZONE $HOSTNAME --username=$USERNAME A --password=$PASSWORD 2>/dev/null|grep 'A:'|sed 's/(.*//;s/.*A: //'|wc -l|cut -d ' ' -f 1` -gt 0 ] ; then
   {
      del_leases  ${NEW_RECORD}
   }
   fi
   samba-tool dns add $SERVER $ZONE $HOSTNAME A $IP --username=$USERNAME --password=$PASSWORD 2> /dev/null
   internal_logger  "samba-tool dns add $SERVER $ZONE $HOSTNAME A $IP --username=$USERNAME --password=<masked>"
   samba-tool dns add $SERVER $REVERSE $REVIP PTR ${HOSTNAME}.${ZONE} --username=$USERNAME --password=$PASSWORD 2> /dev/null
   internal_logger  "samba-tool dns add $SERVER $REVERSE $REVIP PTR ${HOSTNAME}.${ZONE} --username=$USERNAME --password=<masked>"
   return 0
}

get_hostname_from_logfile() {
   local mac="$1"
   local filename="";
   local line="`grep \"${mac}\" ${LOG_FILE}`"
   local tokens=( $line )

   if [ 5 -eq ${#tokens[@]} ]; then
       filename=${tokens[3]}
   fi
   echo $filename
}

get_new_record() {
# $1: expired
# $2: mac
# $3: ip
# $4: hostname
# $5: iface
   local VALIDHOSTMAC="$2"
   local VALIDHOSTIP="$3"
   local VALIDHOST="$4"
   local VALIDHOSTIF="$5"
   local FILEVALIDHOST=$(get_hostname_from_logfile $mac)
   if [[ "X${VALIDHOST}" == "X" ]] && [[ "Xold" == "X${ACTION}" ]]; then
   {
    [[ "X${DNSMASQ_SUPPLIED_HOSTNAME}" == "X" ]] || VALIDHOST=${DNSMASQ_SUPPLIED_HOSTNAME}
    [[ "X${VALIDHOST}" == "X" ]] &&  export VALIDHOST=${FILEVALIDHOST}
    [[ "X${VALIDHOST}" == "X" ]] && VALIDHOST="unknown_`echo $VALIDHOSTMAC|tr -d ':'`"
        # DEBUG
        #internal_logger  "Exception found: $1 $2 $3 $4"
   }
   fi
   [[ "X$VALIDHOST" == "X" ]] && VALIDHOST="unknown_`echo $VALIDHOSTMAC|tr -d ':'`"
   NEW_RECORD="${DNSMASQ_LEASE_EXPIRES} ${VALIDHOSTMAC} ${VALIDHOSTIP} ${VALIDHOST} ${DNSMASQ_INTERFACE}"
   return 0
}

# record format: action mac ip hostname
NEW_RECORD=$@
ACTION=`echo ${NEW_RECORD} | awk '{print $1}'`

[[ "X${DNSMASQ_INTERFACE}" == "X" ]] && exit 0

get_new_record ${NEW_RECORD}

case "${ACTION}" in
    old)
        renew_record ${NEW_RECORD}
        ;;
    add)
        add_new_record ${NEW_RECORD}
        ;;
    del)
        del_leases ${NEW_RECORD}
        ;;
    *)
        ;;
esac

exit 0

I have added the logging function to synology system log:

internal_logger() {
     local _tmp_msg1="$1"
     #[ -t ] || echo -e "${_tmp_msg1}" && echo -e "${_tmp_msg1}" |tee -a /etc/dhcpd/debug.log
     /usr/syno/bin/synologset1 sys info 0x11100000 "DHCP:$$:${_tmp_msg1} "
     return 0
}

And fixed adding hosts that are not providing the DNSMASQ_SUPPLIED_HOSTNAME field. The block for adding hostname like unknown_mac:

         [[ "X${DNSMASQ_SUPPLIED_HOSTNAME}" == "X" ]] || VALIDHOST=${DNSMASQ_SUPPLIED_HOSTNAME}
         [[ "X${VALIDHOST}" == "X" ]] &&  export VALIDHOST=${FILEVALIDHOST}
         [[ "X${VALIDHOST}" == "X" ]] && VALIDHOST="unknown_`echo $VALIDHOSTMAC|tr -d ':'`"
  • Looks great, except that I don't use the AD server in my NAS (yet?). If I find the time I'll definitly look into this as I have upgraded my DS214Play to a DS718Plus a couple of months ago. – H.Hasenack May 25 '20 at 10:27