DNS-from-DHCP, multi-homed hosts?

There are many examples of DNS-from-DHCP scripts, and my three MikroTik routers have a version running reliably. (Mostly). [All are running ROS v7 "current" channel].

There is some discussion of doing DNS-from-DHCP with script-on-DHCP-lease, though that seems to have the limitation of working only on explicitly a lease assignment, which under limited circumstances could become inconsistent. So, unless that's been solved and best practice now is indeed to use script-on-DHCP-lease, I assume I want to stick with the more-labor-intensive (for the MikroTiks' CPUs) but more capable and reliable scheduled scripts. (Right?)

There has been a little discussion of the problem of multi-homed devices (that is, devices with two or more network connection types, typically a notebook computer with both Ethernet and Wi-Fi) when more-than-one of the multi-homed device's interfaces connect to the same network; with my script, I'm having a problem with one notebook computer which sometimes has both its Ethernet cable plugged in and its Wi-Fi adapter enabled. (The best would be for the Wi-Fi to be automatically turned off when the Ethernet is connected, but sometimes users/Windows .... ).

I'm a bit fuzzy on this next part: I think that the hostname is supplied by the host to the DHCP server on the MikroTik. (Yes?) So when the DNS-from-DHCP script runs, it will get two active DHCP leases' IP addresses for the one hostname:

# Parse through all current DHCP leases
:foreach lease in [/ip dhcp-server lease find] do={
:local hostname [/ip dhcp-server lease get value-name=host-name $lease]
:local hostaddr [/ip dhcp-server lease get value-name=active-address $lease]
...

As a result, I get log entries like this:

2026-05-19 16:38:43 script,info DNS via DHCP v2 updated hostname JayThinkP51-2 to hostaddr 192.168.255.13
2026-05-19 16:38:43 system,info static dns entry changed by scheduler:DNS via DHCP v2/script:DNS via DHCP v2/action:1157 (/ip dns static set *6F address=192.168.255.13)
2026-05-19 16:38:43 script,info DNS via DHCP v2 updated hostname JayThinkP51-2 to hostaddr 192.168.255.15
2026-05-19 16:38:43 system,info static dns entry changed by scheduler:DNS via DHCP v2/script:DNS via DHCP v2/action:1158 (/ip dns static set *6F address=192.168.255.15)

How could multi-homed hosts be handled for DNS-from-DHCP? I don't want to have two different hostnames for the two interfaces of the one host.

Thoughts, please?

thank you,

  1. Your script seems to have a bad logic if it does that, because there can be multiple devices with active leases having the same hostname but with different MAC/IP addresses for each lease (leases are tied to MAC addresses, not hostnames).
  2. What exactly are the limitations of using a DHCP lease script vs your approach?
  3. Did you try the newly introduced add-dns and add-dns-suffix options in 7.23 (currently in rc state)?
    *) dhcpv4-server - added "add-dns" and "add-dns-suffix" properties for creating local DNS entries;
    Though I find the current state of the above mentioned implementation lacking, read my notes from the end of this post: V7.23beta [development] is released! - #253 by Znevna

Hi Znevna, cool stuff, in your post responding to the initial RC implementation of add-dns.

re: 3

No, I haven't tried the add-dns; I was unaware of it. (I'll look for more information about what MikroTik is doing with that).

re: 1

Looking at the lease script in your post, I think it would have the same trouble that my scheduled script has; they both rely on the hostname provided by the DHCP client (unless I'm mistaken, and $lease-hostname in a lease script gets that hostname from somewhere else?).

re: 2

I saw a post discussing lease scripts which suggest that they're only executed by the RouterOS DHCP server when a new lease is assigned; that post commented that if a host name was changed (I guess they meant, on the DHCP client itself) that the DNS records would become inconsistent. I'm really not sure of the detail. My script handles stale leases with the code below.

I wonder if maybe I didn't state my difficulty well-enough; apologies.

The dual-homed notebook computer on my LAN which causes my problem sends two different DHCP client requests to the DHCP server on the MikroTik. Each one gets its own IP address. But both contain the same client hostname.

What I want is one DNS entry on the MikroTik for each connected device - not one DNS entry for each interface on a two-or-more-homed multiply-connected device.

In a purely manually configured, totally static environment, this would be a single DNS A record for that host's name, containing two IP addresses.
Ugh. I think I just answered my own question :persevering_face:
I'd have to have a second, inner loop, each time a dynamic hostname is to be superceded, to check whether in fact the existing dynamically-added DNS entry for that hostname is also valid, and if so update it to also include the otherwise-would-have-been-superceding IP address.
:sweat_smile: I think I'm not so worried about this problem to go to that amount of work!

Anyway, here's the clean-stale-entries loop at the end of my runs-once-a-minute DNS-from-DHCP script:

:if ($mydebuglevel > 1) do={
  :log info "DNS via DHCP v2 scanning for stale existing DHCP-magically-added DNS entries"
}

:foreach dnsentry in [/ip dns static find where comment=$magiccomment] do={
  :local hostname [/ip dns static get value-name=name $dnsentry]
  :if ($mydebuglevel > 1) do={
    :log info "DNS via DHCP v2 checking hostname $hostname"
  }

:if ([:type [:find $activehosts $hostname]] = "nil") do={
  /ip dns static remove $dnsentry;
  :log info "DNS via DHCP v2 removed stale host $hostname from static DNS"
} else={
  :if ($mydebuglevel > 1) do={
    :log info "DNS via DHCP v2 host $hostname seems to still be active"
    }
  }
}

My script from the post mentioned above does come with another limitation, now that I think about it, but that's not it.
I do read the lease-hostname but I don't treat it as unique (see what commentTag is made of).
But because I rely on regex entries for the reasons mentioned in that post, if two leases have the same hostname, that hostname is resolved to only one IP, since the next regex entry for the next lease is never checked.

:local magicPrefix "DHCP-DNS-"
:local mydebuglevel 1

# 1. Loop through all active leases and add/update DNS entries tracked by MAC
:foreach lease in=[/ip dhcp-server lease find where status=bound] do={
  :local hostname [/ip dhcp-server lease get $lease host-name]
  :local hostaddr [/ip dhcp-server lease get $lease active-address]
  :local hostmac  [/ip dhcp-server lease get $lease mac-address]
  
  :if ([:len $hostname] > 0) do={
    # We use the MAC address in the comment to uniquely identify this specific DNS record
    :local commentTag ($magicPrefix . $hostmac)
    :local existingDns [/ip dns static find where comment=$commentTag]
    
    :if ([:len $existingDns] > 0) do={
       # Record for this MAC exists. Check if IP or Hostname changed.
       :local currentAddr [/ip dns static get $existingDns address]
       :local currentName [/ip dns static get $existingDns name]
       
       :if ($currentAddr != $hostaddr or $currentName != $hostname) do={
           /ip dns static set $existingDns address=$hostaddr name=$hostname
           :if ($mydebuglevel > 0) do={ :log info "DNS via DHCP: Updated $hostname ($hostmac) to $hostaddr" }
       }
    } else={
       # Record for this MAC does not exist. Create it.
       # This safely allows a second interface with the same hostname to get its own DNS entry.
       /ip dns static add name=$hostname address=$hostaddr comment=$commentTag ttl=5m
       :if ($mydebuglevel > 0) do={ :log info "DNS via DHCP: Added $hostname ($hostmac) to $hostaddr" }
    }
  }
}

# 2. Cleanup loop: Remove DNS entries for MACs that no longer have an active lease
:if ($mydebuglevel > 1) do={ :log info "DNS via DHCP: Scanning for stale entries" }

:foreach dnsentry in=[/ip dns static find where comment~"^$magicPrefix"] do={
  :local dnsComment [/ip dns static get $dnsentry comment]
  # Extract the MAC address from the comment
  :local dnsMac [:pick $dnsComment [:len $magicPrefix] [:len $dnsComment]]
  
  # Check if this specific MAC still has an active lease
  :local activeLease [/ip dhcp-server lease find where mac-address=$dnsMac and status=bound]
  
  :if ([:len $activeLease] = 0) do={
     :local dnsName [/ip dns static get $dnsentry name]
     /ip dns static remove $dnsentry
     :if ($mydebuglevel > 0) do={ :log info "DNS via DHCP: Removed stale host $dnsName ($dnsMac)" }
  }
}

Try this?

I was concerned that two /ip dns static add name=TheSameHostname would cause an error, but in fact it works fine on the MikroTik running ROS 7.22 where I just tested it.

Moreover, I aimed a DNS client at that MikroTik and asked for TheSameHostname and it returned both /ip dns static A records' IP addresses.

So, yeah, that works.

Thank you,
-Jay

I think that this would be the simplified version of my lease-script.
It doesn't include the zone logic since you don't seem to use it?

:local commentTag ("DHCP " . $leaseServerName . " " . $leaseActMAC)

:if ($leaseBound = 1) do={
    # Check if a record with this exact MAC and IP already exists
    :local existingRecords [/ip dns static find comment=$commentTag address=$leaseActIP]
    
    # We expect exactly 1 record. If it is missing or duplicated, rebuild it.
    :if ([:len $existingRecords] != 1) do={
        :local hostName $"lease-hostname"
        :local leaseTTL [/ip dhcp-server get [find name=$leaseServerName] lease-time]

        :if ([:len $hostName] > 0) do={
            # Clean up any stale records for this specific MAC first
            /ip dns static remove [find comment=$commentTag]
            
            # Add the new plain DNS record
            /ip dns static add name=$hostName address=$leaseActIP ttl=$leaseTTL comment=$commentTag
        }
    }
} else={
    # On lease expiration/deassignment, remove the record for this MAC
    /ip dns static remove [find comment=$commentTag]
}

Just for comparison with the scheduled script :slight_smile:

Indeed, I do not use a .lan zone, and I'm okay with my internal hosts sometimes (also) having single-word or dot-terminated names. And I'm not worried about information leakage. (While I agree that more layers of defense in depth is generally good, for years now I've relied heavily on each host being its own security perimeter - especially mobile hosts for which there's just no other option, unless a host is known to be unsecurable and needs an additional explicit exterior security perimeter).

Nice touch, adding ttl to the DNS entries. Which probably takes care of the "someone changes a host's self-name, resulting in DNS-from-DHCP records becoming inconsistent" concern.

Hm. Why, in the clause to create a DNS record for an active bound lease, use a TTL of 5m ?

As we don't 'refresh' still-active records, this creates a period of around one minute every five-or-six (assuming that the script is scheduled to run every minute, as I do presently) during which a record would expire prior to the next scheduled run of the script finding a still bound, active DHCP lease for which the DNS record had just expired off, before re-creating it.

Is there an argument for not setting a longer TTL, e.g. 24h, since the cleanup loop will remove entries within a minute or two after they become stale?

The TTL (let's call it X) of a DNS record just tells the client that queries it to cache the result for X amount of time.
After that time expires, if the client needs to reach that hostname again, its local resolver will simply ask the upstream DNS server (your router) for the IP address again.
It does NOT mean that the record just vanishes from the router's Static DNS table after X minutes.
Something still has to manually or programmatically remove the static entry when the lease expires, which is exactly what the cleanup loop (or the lease script's else block) does.

Oy, my achin' head... I knew that. These are (script dynamically updated, but) STATIC DNS records.

At least, I used to know that.

I either need to drink less wine, or more ...

Okay so while we're here ...

I don't find any documentation nor even discussion of the "active-address" (it's just mentioned once in the DHCP Server documentation as the Framed IP Address of the requesting DHCP client, which doesn't tell me anything. (Searching for Framed IP Address finds it in RADIUS context).

What/why does the MikroTik DHCP Server use the term "active-address"?

And, I suppose a DHCP entry can only ever become unbound if it is a reserved lease and the client has not renewed within the lease-time? This is how I would expect DHCP to work; the confusing factor for me is MikroTik's use of the term "bound", which does not seem to be standard terminology. (The Windows DHCP Server, for example, calls a DHCP Reservation "active" when the client of that reservation has made a DHCP request within the lease-time).

So, to allow DNS-from-DHCP records to be relatively promptly scavenged when their clients go inactive, the DHCP lease-time seems to need to be rather short. Right?

Sorry for the stupid questions. You should have seen me thirty years ago. I was smart, then ...

Thanks,

, and, when/ after how long will RouterOS determine that the DHCP Client is no longer "active" (at which time, I would expect it also to become unbound, yes?).

address is the configured IP for that lease (static or dynamic from a pool).
active-address is the IP actually in use right now. If a lease gets unbound (see below), active-address goes blank.
We script against active-address to ensure we only map live IPs to DNS.

Bound is the actual official DHCP RFC term. It simply means the IP and MAC are officially tied together for the lease duration (as set in your DHCP Server).

A lease unbinds instantly if a client shuts down gracefully (sends a DHCPRELEASE).
If a device just walks out of Wi-Fi range or goes to sleep, the router has no idea it left. It will stay "bound" until the lease-time timer for that lease hits zero.

Because of those silent disconnects, you might want shorter lease times and DNS TTLs.

You still didn't mention why you don't like the dhcp lease script instead?