DNS regexp pointing to multiple addresses - returns only the first

I'd like our (unrooted) Android phones to be able to make use of the local GNSS NTP servers when connected to LAN. The phones don't make use of DHCP option 42, and don't have any way of setting the NTP servers manually (without rooting them) so I have to override time.google.com to point to my servers.

I don't want other NTP clients that use reverse DNS lookup to show the servers as time.google.com, so I used a time\.google\.com regex instead of a plaintext entry, as this way it doesn't create the reverse DNS entries:

/ip dns static
add address=192.168.20.2 comment="NTP overrides" regexp="time\\.google\\.com" type=A
add address=192.168.20.3 regexp="time\\.google\\.com" type=A
add address=::ffff:192.168.20.2 regexp="time\\.google\\.com" type=AAAA
add address=::ffff:192.168.20.3 regexp="time\\.google\\.com" type=AAAA

However, when queried:

nslookup time.google.com
Server:  router.home.arpa
Address:  192.168.1.1

Non-authoritative answer:
Name:    time.google.com
Addresses:  ::ffff:192.168.20.2
          192.168.20.2

So it seems that with regex, only the first A and AAAA entries are matched. For non-regex entries this works properly and matches all addresses. Is this a RouterOS limitation? I'm on version 7.23.2.

I haven't found anything specifically about this limitation in DNS | RouterOS Manual .

It would be nice if a "Don't create reverse DNS entry for this entry" checkbox was added. Or maybe there already is a way to do this that I just haven't found yet?

You can use a CNAME record, and don't need to use regex. Example:

/ip dns static
add address=192.168.20.2 name=time.lan type=A
add address=192.168.20.3name=time.lan type=A
add address=::ffff:192.168.20.2 name=time.lan type=AAAA
add address=::ffff:192.168.20.3 name=time.lan type=AAAA
add cname=time.lan comment="NTP overrides" name=time.google.com type=CNAME

Replace the name time.lan with whatever you want to appear in the PTR records for the local addresses.

Thanks, this seems to work. I'll have to wait some time to see if Android's NTP implementation is able to use it, currently they still query the public servers but they might have them cached somewhere.

I did some more tests and I noticed an issue. This is how I have it currently (the servers should have separate domain names):

/ip dns static
add address=192.168.20.2 name=timeserver1.home.arpa type=A
add address=192.168.20.3 name=timeserver2.home.arpa type=A
add cname=timeserver1.home.arpa comment="NTP overrides" name=time.google.com type=CNAME
add cname=timeserver2.home.arpa name=time.google.com type=CNAME

Ping Tools Pro on Android shows both servers, but Windows nslookup shows only one.
It seems that a regular "A" query will only return one address (seems to be always timeserver2), but an "ANY" query will return both.

Standard query 0x7fcf A time.google.com
Standard query response 0x7fcf A time.google.com CNAME timeserver2.home.arpa A 192.168.20.3
Standard query 0xc338 ANY time.google.com OPT
Standard query response 0xc338 ANY time.google.com CNAME timeserver1.home.arpa CNAME timeserver2.home.arpa A 192.168.20.2 A 192.168.20.3

Is there anything that can be done about this, besides assigning the same domain address to both servers?

You are only allowed to have one CNAME record per sub-domain name. Which mean you can not add two CNAME entries for time.google.com and expect that clients that follow the standard use both of them.

If grouping both 192.168.20.2 and 192.168.20.3 under the same name timeserver.home.arpa is not suitable for you, your original goal can be reached by another way:

Instead of trying to modify the DNS answer (which also does not work if the phone clients turn on secure DNS / private DNS in their setting and use DoT/DoH), you can use firewall DSTNAT rules to redirect the NTP traffic:

First, make an address list for time.google.com:

/ip firewall address-list
add address=time.google.com list=GOOGLE_NTP

You'll see dynamic address list entries appearing for each of the addresses, in the GOOGLE_NTP list.

Now you can add DSTNAT rule for redirection:

/ip firewall nat
# chain output, for traffic from the router
add action=dst-nat chain=output dst-address-list=GOOGLE_NTP dst-port=123 \
    log=yes log-prefix="First IP" nth=2,1 protocol=udp to-addresses=192.168.20.2
add action=dst-nat chain=output dst-address-list=GOOGLE_NTP dst-port=123 \
   log=yes log-prefix="Second IP" protocol=udp to-addresses=192.168.20.3
# chain dstnat, for forwarded traffic
add action=dst-nat chain=dstnat dst-address-list=GOOGLE_NTP dst-port=123 \
    log=yes log-prefix="First IP" nth=2,1 protocol=udp to-addresses=192.168.20.2
add action=dst-nat chain=dstnat dst-address-list=GOOGLE_NTP dst-port=123 \
   log=yes log-prefix="Second IP" protocol=udp to-addresses=192.168.20.3

You can later turn off logging after having confirmed that both destinations are used equally.

And also add a masquerade rule for hairpin-NAT:

/ip firewall nat
add action=masquerade chain=srcnat protocol=udp dst-port=123 \
    src-address=192.168.20.0/24 dst-address=192.168.20.0/24

However, it won't work for IPv6 if your local time server does not support IPv6. Here you can just drop the IPv6 traffic destined for time.google.com and force the client to try IPv4:

/ipv6 firewall address-list
add address=time.google.com list=GOOGLE_NTP

/ipv6 firewall filter
add action=drop chain=forward \
    dst-address-list=GOOGLE_NTP dst-port=123 protocol=udp

This solution has the benefit (compared to the one that change DNS answers) that it also works if the client devices use secure DNS (DoH / DoT).

For now I'll keep the CNAME for one of the servers as a temporary solution, as now I'm not sure if the phones are actually synchronizing to time.google.com. I will have to wait and see.

So far the only NTP servers I've seen the phones connect to seem to be from pl.pool.ntp.org, but I haven't seen the phones query DNS for that, while they do send DNS queries for time.google.com.

If it turned out that the phones actually only use pl.pool.ntp.org servers, the only solution would probably be to root the phones, as I use pl.pool.ntp.org on some devices alongside the local servers.

It would probably be better to use the local servers on the phones alongside the public ones as well, instead of redirecting to the local servers, but I suppose that this is currently only achievable after rooting.

You can create a (transparent) redirect rule to intercept NTP queries from your local network and send them to the MikroTik itself. To do this, use the redirect action in the dstnat chain of the NAT firewall.
/ip firewall nat add chain=dstnat protocol=udp dst-port=123 dst-address=!192.168.88.1 action=redirect to-ports=123 comment="Redirect local NTP to MikroTik"
This allows you to use the MikroTik itself as the NTP server.
If you have set up a Linux server as an NTP server, you can create a DNAT rule on the MikroTik to redirect NTP queries. To do this, exclude the Linux server's own IP address from the redirection to avoid an infinite loop, and ensure the rule affects only traffic coming from the local network.
/ip firewall nat
add chain=dstnat protocol=udp dst-port=123 in-interface-list=LAN !dst-address=192.168.1.100 action=dst-nat to-addresses=192.168.1.100 to-ports=123

As @marcotmv also wrote, you can just redirect all outbound NTP connections to the two local servers. You can use the previous 4 action=dst-nat rules from my previous post, with slight modifications:

  • We remove the dst-address-list=GOOGLE_NTP condition, so that not only time.google.com targets are caught.
  • Add a condition to allow the two local servers themselves to bypass the redirection, in case they need to contact upstream servers.

So the rules are:

/ip firewall address-list
add address=192.168.20.2 list=LOCAL_NTP
add address=192.168.20.3 list=LOCAL_NTP

/ip firewall nat
# chain output, for traffic from the router
add action=dst-nat chain=output dst-address-list=!LOCAL_NTP dst-port=123 \
    log=yes log-prefix="First IP" nth=2,1 protocol=udp to-addresses=192.168.20.2
add action=dst-nat chain=output dst-address-list=!LOCAL_NTP dst-port=123 \
   log=yes log-prefix="Second IP" protocol=udp to-addresses=192.168.20.3
# chain dstnat, for forwarded traffic
add action=dst-nat chain=dstnat dst-address-list=!LOCAL_NTP dst-port=123 \
    log=yes log-prefix="First IP" nth=2,1 protocol=udp src-address-list=!LOCAL_NTP \
    to-addresses=192.168.20.2
add action=dst-nat chain=dstnat dst-address-list=!LOCAL_NTP dst-port=123 \
    log=yes log-prefix="Second IP" protocol=udp src-address-list=!LOCAL_NTP \
    to-addresses=192.168.20.3

/ip firewall nat
# Hairpin-NAT rule
add action=masquerade chain=srcnat protocol=udp dst-port=123 \
    src-address=192.168.20.0/24 dst-address=192.168.20.0/24

/ipv6 firewall filter
# block NTP with IPv6
add action=drop chain=forward dst-port=123 protocol=udp

If more devices should be excluded from the automatic redirection, add them to the address list.

I have described in my previous post why I don't want to redirect everything.

I might make static DHCP leases for the phones and set the NAT rules to work only on those source addresses, but I'm also busy with other things right now, the NTP for android phones is not that important of an issue.

Also, if I were to use the NAT setup, would it be possible to make it so that the phones still have at least 4 different servers available? Or is it only possible to redirect everything to my servers?

PS: I have the NTP servers on their own separate network, so hairpin wouldn't be needed (probably not as good for precision as having them on the same LAN as the clients, but Chrony and ntpd clients still prefer them over the public ones, and a device I'm planning on that will need precision time will also go on the NTP server LAN).

If you have a server that you always want the clients to be able to use, then add that in the LOCAL_NTP list.

If you want 2/5 of the NTP traffic to use your two servers, use the nth similar to the rules above. In the rules above, when then conditions (protocol, destination address / port, source address etc...) are met then half of the connections get handled by the 1st rule (with comment "First IP"), and the rest by the 2nd rule (with comment "Second IP") that has no nth specified.

To catch 1/5 of the connections each, use nth=5,1 for first (and 3rd) rules and nth=4,1 for 2nd (and 4th) rule. The 3/5 rest of the NTP connection don't get NAT-ed and is sent to the original destination addresses.

Or you make static leases for non-phone devices (less problematic because not affected by privacy/random MAC address setting), and apply NAT for src-address-list=!THE_LIST (in fact, it's already in the rule, just add the static devices to LOCAL_NTP).

I'm not sure that I understand correctly, but if your suggestion is to essentially randomly NAT NTP connections to different servers without the client being aware of it, that's a bad idea. NTP clients track all sorts of parameters like stratum, dispersion, rtt on a per-peer basis, so this is bound to mess with them.

Android most likely uses SNTP anyway so it might be fine, but it just feels... wrong somehow. Especially knowing how "proper" NTP is supposed to work.

It's a shame that (as far as I know) it's only possible to do NTP properly on a rooted phone, so there isn't any real "up to code" solution, which is making me feel a bit uncomfortable. At least it's fairly straightforward on all other NTP-using devices on the network.

At least one of the phones is so old now (Android 9) that it probably makes more sense to root it than not to. Though it's likely going to be replaced soon.

Fair point. It's probably tolerable for SNTP.

You are absolutely correct for classic Linux / Unix NTP clients. But for the current mobile client operating systems, both Android and iOS use SNTP. And even Linux clients if they use systemd-timesyncd, then it's also SNTP.

Because the nth is on the NAT rule, it acts per connection, not per packet. So if the client does not change the source port with every packet sent (it's not the case for Windows, which always use 123, and also not for the usual Linux clients), then the connection will be "tracked", if there is response traffic, then the timeout will be extended to 3 minutes with the default RouterOS settings.

Which means it is also not a problem for the Windows client (W32Time) because it sends burst of requests within a short timespan, before the tracked connection is able to timeout, then wait for a long time (in the default client setting this can be up to max 7 days) for the next poll. So, the only negative is that there might be difference in delay which will be considered as jitter between the two polls happening far apart anyway.

For the classic Linux / Unix implementations it would be a problem if the poll interval exceeds 3 minutes, because then yes, the DSTNAT will direct the packets to a new destination.

When we look at time.google.com, that sub-domain resolves to the same 4 IPv4 and 4 IPv6 addresses worldwide. Which means they most probably use anycast. Which means there is already the chance, although not frequent, that the requests that a client sends to the same IP address land in different datacenters if the routing of the anycast address changes.

Currently I'm using Chrony as the client on Linux devices and Meinberg NTP client (ntpd) on Windows. There's also some devices (switch, AP, CCTV etc.) that use SNTP, but at least I can specify the NTP server there.

The android phones seem to be the only devices that only have an on/off toggle for network time, no other settings and no other installable NTP client software that would sync the system clock without requiring root.