Community discussions

MikroTik App
 
mwp
just joined
Topic Author
Posts: 4
Joined: Tue Nov 30, 2021 2:54 am

Yet Another DNS from DHCP Script

Wed May 04, 2022 3:33 am

I wrote yet another DNS from DHCP script. Why? Several reasons:
* I wanted to minimize writes to flash.
* I wanted to use the DHCP network domain.
* I wanted to handle name collisions in a stable way (first lease wins).

It seems to be working for me on 7.2.1. IPv4 only for now. Feedback/patches welcome.

https://github.com/MichaelPaddon/router ... in/syncdns
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Yet Another DNS from DHCP Script

Wed May 04, 2022 3:42 am

DNS RegEx for MikroTik:
viewtopic.php?p=876023&hilit=regex+dns#p876023
# from
:global IsValidName do={
    :local string [:tostr $1]
    :return ($string ~ "^[A-Za-z0-9-][A-Za-z0-9-]*\$" && [:len $string] < 64)
}

#to
:global IsValidName do={
    :local string [:tostr $1]
    :return ($string~"^(([a-zA-Z0-9][a-zA-Z0-9-]{0,61}){0,1}[a-zA-Z]\\.){1,9}[a-zA-Z][a-zA-Z0-9-]{0,28}[a-zA-Z]\$")
}
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Yet Another DNS from DHCP Script

Wed May 04, 2022 3:47 am

Instead to write everytime the path, write once for each sections, this also increase readability
# from
    :foreach network in [/ip/dhcp-server/network/find] do={
        :local netblock [/ip/dhcp-server/network/get value-name=address $network]
        :if ($ipaddr in $netblock) do={
            :return [/ip/dhcp-server/network/get value-name=domain $network]
        }
    }


# to
    /ip/dhcp-server/network/
    :foreach network in [find] do={
        :local netblock [get value-name=address $network]
        :if ($ipaddr in $netblock) do={
            :return [get value-name=domain $network]
        }
    }
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Yet Another DNS from DHCP Script

Wed May 04, 2022 3:49 am

Do not write path with "/" If the script can be run on also on v6
"/ip/dns/static/find" is incopmpatible on v6, but "/ip dns static find" is compatible with both v6 and v7


Do not mix "styles", or use "/" or not use:
    :foreach entry in [/ip/dns/static/find where comment=$Magic] do={
        :local fqdn [/ip dns static get value-name=name $entry]


If possible, make easier the scripting, this is also more readable
:local fqdn ($hostname . "." . $domain) ==>> :local fqdn "$hostname.$domain"

Some comments......
# record already present?
must be like
# exact same record already present?

# record exists but needs updating?
must be like
# if exist update record (regardless of any other conditions)

# no record of that name at all?
must be like
# if not exist add one
 
mwp
just joined
Topic Author
Posts: 4
Joined: Tue Nov 30, 2021 2:54 am

Re: Yet Another DNS from DHCP Script

Thu May 05, 2022 4:30 am

Thanks for all the feedback! I'll update the script accordingly!
 
Valerio5000
Frequent Visitor
Frequent Visitor
Posts: 92
Joined: Fri Dec 06, 2013 2:38 am

Re: Yet Another DNS from DHCP Script

Fri May 19, 2023 8:12 pm

How to include hostnames with _ character? I've always used this font with no problems and this script is great if it would include these names
# syncdns version: 1.0.0
#
# A RouterOS script to synchronise DNS with DHCP leases.
# This may be run regularly or whenever a DHCP event occurs.
#
# Copyright 2022 by Michael Paddon.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.

# default domain for DNS records
:global DefaultDomain "localdomain"

# TTL for DNS records
:global DnsTtl 5m

# magic comment for DNS records
:global Magic "automatic-from-dhcp"

# return the domain associated with an address
:global GetDomain do={
    :global DefaultDomain
    :local ipaddr [:toip $1]
    /ip dhcp-server network
    :foreach network in [find] do={
        :local netblock [get value-name=address $network]
        :if ($ipaddr in $netblock) do={
            :return [get value-name=domain $network]
        }
    }

    :return $DefaultDomain
}

# is a string a valid DNS name?
:global IsValidName do={
    :local string [:tostr $1]
    :return ($string ~ "^[A-Za-z]([A-Za-z0-9-_]{0,61}[A-Za-z0-9])?\$")
}

# return an array of fqdn=ipaddr items
:global GetFqdns do={
    :global GetDomain
    :global IsValidName

    /ip dhcp-server lease
    :local fqdns [:toarray ""]
    :foreach lease in [find] do={
        :local hostname [get value-name=host-name $lease]
        :if ([$IsValidName $hostname]) do={
            :local ipaddr [get value-name=address $lease]
            :local domain [$GetDomain $ipaddr]
            :local fqdn "$hostname.$domain"
            :if ([:len ($fqdns->$fqdn)] = 0) do={
                :set ($fqdns->$fqdn) $ipaddr
            }
        }
    }

    :return $fqdns
}

# update a DNS record, returning true if a change was made
:global Update do={
    :global DnsTtl
    :global Magic

    :local fqdn [:tostr $1]
    :local ipaddr [:toip $2]

    # correct record already present?
    /ip dns static
    :if ([:len [find where name=$fqdn address=$ipaddr ttl=$DnsTtl comment=$Magic]] > 0) do={
        :return false
    }

    # if record exists it must be updated
    :local exists [find where name=$fqdn comment=$Magic]
    :if ([:len $exists] > 0) do={
        :log info "syncdns: update $fqdn -> $ipaddr"
        set address=$ipaddr ttl=$DnsTtl $exists
        :return true
    }

    # if no record of that name exists at all, create record
    # note: a manually created record may exist which MUST NOT be touched
    :if ([:len [find where name=$fqdn]] = 0) do={
        :log info "syncdns: add $fqdn -> $ipaddr"
        add name=$fqdn address=$ipaddr ttl=$DnsTtl comment=$Magic
        :return true
    }

    :return false
}

# synchronize DNS
:global SyncDns do={
    :global GetFqdns
    :global Magic
    :global Update

    # update dns records
    :local fqdns [$GetFqdns]
    :foreach fqdn,ipaddr in=$fqdns do={
        [$Update $fqdn $ipaddr]
    }

    # remove obsolete dns records
    /ip dns static
    :foreach entry in [find where comment=$Magic] do={
        :local fqdn [get value-name=name $entry]
        :if ([:len ($fqdns->$fqdn)] = 0) do={
            :log info "syncdns: remove $fqdn"
            remove $entry
        }
    } 
}

:log info "syncdns: synchronize"
[$SyncDns]
 
ovdeathiam
just joined
Posts: 21
Joined: Wed Jul 16, 2014 11:15 am

Re: Yet Another DNS from DHCP Script

Sun May 21, 2023 2:53 pm

I see that all the scripts posted here are super complicated and designed to be run on a schedule.

Why not just use something simple as a lease script in the dhcp server to have instantaneous changes?
Image
:local ttl 600s
:local debug 0

:if ($leaseBound = 1) do={
    :local HostName [/ip dhcp-server lease get number=[find where mac-address=$leaseActMAC] host-name]
    :if ([:len $HostName] > 0) do={
        :local Domain [/ip dhcp-server network get number=[find where mac-address=$leaseActMAC] domain]
        /ip dns static remove numbers=[find where name=($HostName . "." . $Domain)]
        /ip dns static add name=($HostName . "." . $Domain) address=$leaseActIP ttl=$ttl
    }
} else={
    /ip dns static remove numbers=[find where address=$leaseActIP ttl=$ttl]
}

:if ($debug = 1) do={
    :log info message=("leaseBound = " . $leaseBound)
    :log info message=("leaseServerName = " . $leaseServerName)
    :log info message=("leaseActMAC = " . $leaseActMAC)
    :log info message=("leaseActIP = " . $leaseActIP)
    :log info message=("lease-hostname = " . $lease-hostname)
    :log info message=("lease-options = " . $lease-options)
}
 
User avatar
rextended
Forum Guru
Forum Guru
Posts: 11982
Joined: Tue Feb 25, 2014 12:49 pm
Location: Italy
Contact:

Re: Yet Another DNS from DHCP Script

Sun May 21, 2023 7:04 pm

I haven't tried it, but it works by accident, because there is a big error in the script:
/ip dhcp-server network get number=[find where mac-address=$leaseActMAC] domain
The DHCP Networks do not have "mac-address"

In fact valerio's script correctly searches the name among the networks, using the IP...
:global GetDomain do={
    :global DefaultDomain
    :local ipaddr [:toip $1]
    /ip dhcp-server network
    :foreach network in [find] do={
        :local netblock [get value-name=address $network]
        :if ($ipaddr in $netblock) do={
            :return [get value-name=domain $network]
        }
    }

    :return $DefaultDomain
}


Also is useless search and read the hostname with
:local HostName [/ip dhcp-server lease get number=[find where mac-address=$leaseActMAC] host-name]
When already exist on purpose the variable lease-hostname (but probably you are unable to use it for next point)
Just use $"lease-hostname"


And also the "debug" part is wrong, because both lease-hostname and lease-options must be surrounded by quotes or do not work correctly and broken the script
    :log info message=("lease-hostname = " . $lease-hostname)
    :log info message=("lease-options = " . $lease-options)
correct are $"lease-hostname" and $"lease-options"



Instead, regarding the fact that it is more logical to be done by the DHCP script, instead of the scheduler, it is more logical and correct.
 
ovdeathiam
just joined
Posts: 21
Joined: Wed Jul 16, 2014 11:15 am

Re: Yet Another DNS from DHCP Script

Mon May 22, 2023 12:30 am

I really appreciate your pointers. I was able to simplify that lease script and add your dns validation regex and OP's GetDomain function.
:local ttl 600s

:local GetDomain do={
    :local ipaddr [:toip $1]
    /ip dhcp-server network
    :foreach network in [find] do={
        :local netblock [get value-name=address $network]
        :if ($ipaddr in $netblock) do={
            :return [get value-name=domain $network]
        }
    }
}

:local IsValidFQDN do={
    :local string [:tostr $1]
    :return ($string~"^(([a-zA-Z0-9][a-zA-Z0-9-]{0,61}){0,1}[a-zA-Z]\\.){1,9}[a-zA-Z][a-zA-Z0-9-]{0,28}[a-zA-Z]\$")
}

/ip dns static
:if ($leaseBound = 1) do={
    :local FQDN "$($"lease-hostname").$[$GetDomain $leaseActIP]"
    :if ([$IsValidFQDN $FQDN]) do={
        remove numbers=[find where name=$FQDN]
        add name=$FQDN address=$leaseActIP ttl=$ttl
    }
} else={
    remove numbers=[find where address=$leaseActIP ttl=$ttl]
}

Who is online

Users browsing this forum: rogerioqueiroz and 18 guests