So what needs changing? Let's look at the current behavior:
Fig. 1: Current behavior
Code: Select all
[user@router] > :put [ :resolve www.google.com ]
216.58.219.36
[user@router] > :put [ :resolve ipv4.google.com ]
216.58.193.206
[user@router] > :put [ :resolve ipv6.google.com ]
2607:f8b0:4007:80b::200e
[user@router] > :put [ :resolve _ldap._tcp.dc._msdcs.contoso.com ]
failure: dns name exists, but no appropriate record
Some things to note:
- Only one answer is ever returned, even if there are multiple answers.
- If both A and AAAA records exist, RouterOS chooses the A record.
- If the record is of another type, the resolution fails.
Taken together, I would guess that the RouterOS developers chose to turn DNS resolution into a single-answer succeed/fail operation so that parsing the results of :resolve would not require much effort. Assuming this was the logic, my suggestion also tries to preserve as many of these properties as possible.
So here are the proposed changes:
1. Continue the default behavior to only return one record, but provide an option full-answer=true|false to return the full answer.
The :resolve command is used for both debugging and scripting purposes and this change benefits both purposes. Since RouterOS has DNS server functionality, it's always frustrating to debug DNS-related issues when the router itself doesn't have a good DNS client. Currently, the only recourse is to use dig or nslookup from a client device and then inspect :ip dns cache on the router to see what happened. From a scripting perspective, it would be nice to be given all of the answers for a query for round-robin connections, health checks, etc.
2. Provide an option in :ip dns called client-behavior: prefer-v4|dual-stack
- prefer-v4 preserves the legacy behavior and will return A record(s) if both A and AAAA are available. Since some users are undoubtedly relying on this quirk, this can remain the RouterOS default for several versions to give them time to migrate.
- dual-stack follows RFC 8305 and attempts dual-stack resolution like a standard DNS client. If both address families are present and RouterOS has a configured IPv6 address it can use as a source, the AAAA record(s) are returned. After a sufficient amount of time, this should become the RouterOS default.
The fact that RouterOS unconditionally prefers IPv4 makes it ill-suited as a modern dual-stack client. I don't fully understand why the choice was made in the first place. Although not in my ask here (because of the amount of work that would be involved), I do hope that RouterOS 7 has a proper RFC-8305-compliant control plane for any connections the router makes.
3. Provide typed answers for some common record types (CNAME, NS, MX, SRV, and TXT please!) and provide string answer dumps for unimplemented/unknown record types.
- For backward compatibility, only provide IPv4 or IPv6 answers unless the type=a|aaaa|cname|ns|mx|srv|txt|.* option is used.
- As an additional measure of backward compability, :resolve should fail if the record cannot be resolved to an IPv4 or IPv6 address, regardless of whether a full answer could be provided.
- type=auto represents these behaviors and is the default.
:resolve already returns ip and ip6 typed answers, so script authors already have to type inspect if they care about the type of the returned address. What's more, RouterOS' shell language already has the right datatypes to handle more complicated answers (arrays!).
Finally, here are some specific examples of what it all looks like put together:
Fig. 3: Proposed type behavior for auto
Code: Select all
#Get to a single answer, even if that means picking one. Actual answer depends on dual-stack setting. With the right dual-stack setting (see 2. above), this is fully backward compatible with existing scripts.
[user@router] > :put [ :typeof [ :resolve type=auto www.google.com ] ]
ip|ip6
#Actual array is {ip|ip6,[, ip|ip6...]} of the full RRset.
[user@router] > :put [ :typeof [ :resolve type=auto full-answer=true www.google.com ] ]
array
Fig. 4: Proposed type behavior for A
Code: Select all
#Get to a single answer, even if that means picking one. Because the user asked for A specifically, dual-stack setting has no bearing here.
[user@router] > :put [ :typeof [ :resolve type=a www.google.com ] ]
ip
#Actual array is {ip,[, ip...]} of the full RRset. Because the user asked for A specifically, dual-stack setting has no bearing here.
[user@router] > :put [ :typeof [ :resolve type=a full-answer=true www.google.com ] ]
array
Fig. 5: Proposed type behavior for AAAA
Code: Select all
#Get to a single answer, even if that means picking one. Because the user asked for AAAA specifically, dual-stack setting has no bearing here.
[user@router] > :put [ :typeof [ :resolve type=aaaa www.google.com ] ]
ip6
#Actual array is {ip6,[, ip6...]} of the full RRset. Because the user asked for AAAA specifically, dual-stack setting has no bearing here.
[user@router] > :put [ :typeof [ :resolve type=aaaa full-answer=true www.google.com ] ]
array
Fig. 6: Proposed type behavior for CNAME
Code: Select all
#Get to a single answer, even if that means picking one. Unlike type=auto, the answer is just a resolved canonical name.
[user@router] > :put [ :typeof [ :resolve type=cname ipv4.google.com ] ]
str
#Actual array is {str,[, str...]} of the full RRset. Unlike type=auto, the answer is all resolved canonical names.
[user@router] > :put [ :typeof [ :resolve type=cname full-answer=true ipv4.google.com ] ]
array
Fig. 7: Proposed type behavior for NS
Code: Select all
#Get to a single answer. Actual answer depends on dual-stack setting.
[user@router] > :put [ :typeof [ :resolve type=ns ns1.google.com ] ]
ip|ip6
#Actual array is {ip|ip6,[, ip|ip6...]}.
[user@router] > :put [ :typeof [ :resolve type=ns full-answer=true ns1.google.com ] ]
array
Fig. 8: Proposed type behavior for MX
Code: Select all
#Get to a single answer, even if that means MX -> pick an answer -> lookup CNAME/A/AAAA -> pick an answer. Ignore the priority; if users want that they can parse the full answer. Actual answer depends on dual-stack setting.
[user@router] > :put [ :typeof [ :resolve type=mx google.com ] ]
ip|ip6
#Actual array is {array,[, array...]} of the full RRset.
#Each internal array is an answer and is {hostname=str, priority=num}.
[user@router] > :put [ :typeof [ :resolve type=mx full-answer=true google.com ] ]
array
Fig. 9: Proposed type behavior for SRV
Code: Select all
#Get to a single answer, even if that means SRV -> pick an answer -> lookup CNAME/A/AAAA -> pick an answer. Ignore the priority, weight, and port; if users want that they can parse the full answer. Actual answer depends on dual-stack setting.
[user@router] > :put [ :typeof [ :resolve type=srv _ldap._tcp.dc._msdcs.contoso.com ] ]
ip|ip6
#Actual array is {array,[, array...]} of the full RRset.
#Each internal array is an answer and is {hostname=str, port=num, priority=num, weight=num}.
[user@router] > :put [ :typeof [ :resolve type=srv full-answer=true _ldap._tcp.dc._msdcs.contoso.com ] ]
array
Fig. 10: Proposed type behavior for TXT (and unimplemented/unknown records)
Code: Select all
#Concatenate the full RRset (here TXT) into a single string.
[user@router] > :put [ :typeof [ :resolve type=txt www.google.com ] ]
str
#Actual array is {str,[, str...]} of the full RRset (here TXT).
[user@router] > :put [ :typeof [ :resolve type=txt full-answer=true www.google.com ] ]
array
Even though the explanation of the changes is lengthy, I think this preserves a balance between backward compatibility and new features and tries to be specific about the extent of the changes requested. There are a lot of "shorter" feature requests that involve entire new technologies and protocol implementations . Many of them would require much more effort and would still be a niche feature, whereas DNS improvements are almost universally applicable.
Taken together, these changes would modernize :resolve, make it a more useful troubleshooting tool, and continue to open the door for even more scripting flexibility. What do others think?