-
Notifications
You must be signed in to change notification settings - Fork 130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(dns): Dynamic DNS sorting (RFC 6724) to work in IPv6-only network (IDFGH-12553) #66
base: 2.1.3-esp
Are you sure you want to change the base?
fix(dns): Dynamic DNS sorting (RFC 6724) to work in IPv6-only network (IDFGH-12553) #66
Conversation
fdcd480
to
efccf13
Compare
6287920
to
009dc00
Compare
This fix adds an option to dynamically set the order based on whether an the host has a global (or ULA) IPv6 address, allowing a device with both IPv4 and IPv6 enabled to work in any network configuration, including IPv4-only, IPv6-only, and dual-stack. Without the option there is a static ordering preferring IPv4, which means on an IPv6-only network, a dual-stack destination returns the IPv4 address, which fails. Setting the static preference to IPv6 would have the opposite problem. A dynamic sort, based on available source addresses, is required to work on all networks.
009dc00
to
fd387d8
Compare
Testing with Arduino-ESP32. I have built a version of the Arduino-ESP32 libraries with the fix included, coupled with some fixes in the Arduino-ESP32 project. The results work with most networks and destinations, although there are still issues with TLS / HTTPS. (Most production systems should be using TLS):
Tests were done using a custom sample app with an M5Stack Core2, https://github.com/sgryphon/iot-demo-build/tree/feature/arduino-esp32-v3-update/m5stack/m5unified_wifi_https PlatformIO was used to build, with the following custom configuration:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for implementing this useful feature and sorry for the slow response time.
We're currently in the process of making (other) changes to the DNS module, as well, supporting multiple addresses, so there might be some conflicts after merging.
We'd like to accept your changes, but it would still take some time I'm afraid. I performed a basic formal review for now. (would be good if we make the CI running as well).
Thank you again and the contribution, and for your patience.
#include "lwip/ip_addr.h" | ||
#include "lwip/ip6_addr.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you revert this change? I think including ip_addr.h
should be enough
ip6_addr_t * dns_select_destination_address(ip6_addr_t *cand_list[], const int cand_list_length); | ||
u8_t dns_addr_get_scope(const ip6_addr_t *addr); | ||
u8_t dns_precedence_for_label(u8_t label); | ||
u8_t dns_get_precedence_label(const ip6_addr_t *addr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these functions supposed to be internal only? If so, could you please make them static?
*/ | ||
u8_t | ||
dns_get_precedence_label(const ip6_addr_t *addr) { | ||
/* IDEA: Allow this function to be overriden by a customisation hook */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 good idea, you can look at other places in lwip, for example:
Lines 1998 to 2003 in a45be9e
#ifdef LWIP_HOOK_ND6_GET_GW | |
} else if ((next_hop_addr = LWIP_HOOK_ND6_GET_GW(netif, ip6addr)) != NULL) { | |
/* Next hop for destination provided by hook function. */ | |
destination_cache[nd6_cached_destination_index].pmtu = netif->mtu; | |
ip6_addr_set(&destination_cache[nd6_cached_destination_index].next_hop_addr, next_hop_addr); | |
#endif /* LWIP_HOOK_ND6_GET_GW */ |
u8_t cand_0_precedence = dns_precedence_for_label(cand_0_label); | ||
u8_t cand_1_precedence = dns_precedence_for_label(cand_1_label); | ||
|
||
LWIP_DEBUGF(DNS_DEBUG, ("dns_select: rule 6, cand_0 precedence %d, cand_1 precedence %d\n", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these parameters are u8_t
, could you please use lwip format macros (S16_U
, X8_U
, etc), or simply declare them as int
?
u32_t has_ipv4_source_scope_flags = 0x0; | ||
u32_t has_source_precedence_label_flags = 0x0; | ||
struct netif *netif; | ||
for(netif = netif_list; netif != NULL; netif = netif->next) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use:
for(netif = netif_list; netif != NULL; netif = netif->next) { | |
NETIF_FOREACH(netif) { |
addr_list[index] = ip_2_ip6(&addr6); | ||
index++; | ||
} | ||
err_t err4 = netconn_gethostbyname_addrtype(nodename, &addr4, NETCONN_DNS_IPV4); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if this would compile. lwip still uses C90 (for using tests and some basic checks), so you need to define/declare all local vars before statements.
But saw that CI didn't run on this PR, strange, I'll try to fix that... you can try to update the triggers on your branch. It's good to have these basic tests running and passing:
on: [push, pull_request] |
ip_addr_t addr4; | ||
ip_addr_t addr6; | ||
ip6_addr_t *addr_list[2]; | ||
int index = 0; | ||
err_t err6 = netconn_gethostbyname_addrtype(nodename, &addr6, NETCONN_DNS_IPV6); | ||
if (err6 == ERR_OK) { | ||
addr_list[index] = ip_2_ip6(&addr6); | ||
index++; | ||
} | ||
err_t err4 = netconn_gethostbyname_addrtype(nodename, &addr4, NETCONN_DNS_IPV4); | ||
if (err4 == ERR_OK) { | ||
/* Convert native V4 address to a V4-mapped IPV6 address */ | ||
ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&addr4), ip_2_ip4(&addr4)); | ||
IP_SET_TYPE_VAL(addr4, IPADDR_TYPE_V6); | ||
addr_list[index] = ip_2_ip6(&addr4); | ||
index++; | ||
} | ||
err = err6 == ERR_OK || err4 == ERR_OK ? ERR_OK : EAI_FAIL; | ||
if (err != ERR_OK) { | ||
return EAI_FAIL; | ||
} | ||
ip6_addr_t *best_addr = dns_select_destination_address(addr_list, index); | ||
if (ip6_addr_isipv4mappedipv6(best_addr)) { | ||
unmap_ipv4_mapped_ipv6(ip_2_ip4(&addr), best_addr); | ||
} else { | ||
ip_addr_copy_from_ip6(addr, *best_addr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lwip implementation of getaddrinfo()
has certain limitations.
AI_V4MAPPED
and AI_ADDRCONFIG
are pending, as mentioned.
I was unable to find the details of this DNS_DYNAMIC_SORT
algorithm in the manual pages of getaddrinfo()
.
Moreover, this algorithm can also be implemented in the application layer by modifying the hints->ai_family
and hints->ai_flags
.
I would recommend contributing an example in the https://github.com/lwip-tcpip/lwip/tree/master/contrib with DNS_DYNAMIC_SORT
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am unsure which manual pages of getaddrinfo() you are referring to. LWIP does not have this sorting algorithm (yet), but it is common in Linux implementations, e.g. https://man7.org/linux/man-pages/man3/getaddrinfo.3.html
"The sorting function used within getaddrinfo() is defined in RFC 3484" (RFC 6724 is the new version that obsoletes RFC 3484). The actual algorithm is defined in the RFC.
The V4-mapped conversion is done because the RFC 6724 is defined in terms of mapped addresss. The result is unmapped before returning. When/if AI_V4MAPPED
is implemented, this would need to be updated.
AI_ADDRCONFIG
may help, but only when there are no addresses of the wrong type. Because link-local IPv4 addresses are not usually enabled, this may help the IPv6-only network to dual-stack bug (no v4 unless you turn on link-local RFC 3927), but doesn't provide a lot of benefit over the standard sorting algorithm.
I am looking to also contribute this upstream.
The This issue has been addressed in commit espressif/esp-idf@e2ae81a. However, the feature is currently disabled by default. |
To address issue: espressif/esp-idf#13255
Also useful is setting up to configure the option via KConfig (a separate PR to the main esp-idf repository).
This fix adds an option to dynamically set the order of DNS results based on RFC 6724, to return the best destination address based on the available source addresses, allowing a device with both IPv4 and IPv6 enabled to work in any network configuration, including IPv4-only, IPv6-only, and dual-stack.
Without the option there is a static ordering preferring IPv4, which means on an IPv6-only network, a dual-stack destination returns the IPv4 address, which fails. Setting the static preference to IPv6 would have the opposite problem. A dynamic sort, based on available source addresses, is required to work on all networks.
The dynamic sorting only applies if you have both IPv6 and IPv4 enabled.
Summary of behaviour:
Other IPv6 addresses (Toredo, ULA, etc) will still be used if there is no IPv4 address (see step 1), but if there is also an IPv4 address then the IPv4 address will be used in preference.
Replaces PR #65 that had a very basic implementation (not full RFC 6724).