diff --git a/drivers/modem/hl7800.c b/drivers/modem/hl7800.c index 58e4f663838..33064290639 100644 --- a/drivers/modem/hl7800.c +++ b/drivers/modem/hl7800.c @@ -1966,7 +1966,8 @@ static void dns_work_cb(struct k_work *work) } } else { LOG_DBG("Reconfiguring DNS resolver"); - ret = dns_resolve_reconfigure(dnsCtx, (const char **)dns_servers_str, NULL); + ret = dns_resolve_reconfigure(dnsCtx, (const char **)dns_servers_str, NULL, + DNS_SOURCE_MANUAL); if (ret < 0) { LOG_ERR("dns_resolve_reconfigure fail (%d)", ret); retry = true; diff --git a/drivers/wifi/esp_at/esp.c b/drivers/wifi/esp_at/esp.c index c68ea98ee0f..427d548350b 100644 --- a/drivers/wifi/esp_at/esp.c +++ b/drivers/wifi/esp_at/esp.c @@ -490,18 +490,24 @@ static void esp_dns_work(struct k_work *work) struct dns_resolve_context *dnsctx; struct sockaddr_in *addrs = data->dns_addresses; const struct sockaddr *dns_servers[ESP_MAX_DNS + 1] = {}; + int interfaces[ESP_MAX_DNS]; size_t i; - int err; + int err, ifindex; + + ifindex = net_if_get_by_ifindex(data->net_iface); for (i = 0; i < ESP_MAX_DNS; i++) { if (!addrs[i].sin_addr.s_addr) { break; } dns_servers[i] = (struct sockaddr *) &addrs[i]; + interfaces[i] = ifindex; } dnsctx = dns_resolve_get_default(); - err = dns_resolve_reconfigure(dnsctx, NULL, dns_servers); + err = dns_resolve_reconfigure_with_interfaces(dnsctx, NULL, dns_servers, + interfaces, + DNS_SOURCE_MANUAL); if (err) { LOG_ERR("Could not set DNS servers: %d", err); } diff --git a/include/zephyr/net/dns_resolve.h b/include/zephyr/net/dns_resolve.h index 3413a008395..400af6b77e6 100644 --- a/include/zephyr/net/dns_resolve.h +++ b/include/zephyr/net/dns_resolve.h @@ -42,6 +42,24 @@ enum dns_query_type { DNS_QUERY_TYPE_AAAA = 28 }; +/** + * Entity that added the DNS server. + */ +enum dns_server_source { + /** Source is unknown */ + DNS_SOURCE_UNKNOWN = 0, + /** Server information is added manually, for example by an application */ + DNS_SOURCE_MANUAL, + /** Server information is from DHCPv4 server */ + DNS_SOURCE_DHCPV4, + /** Server information is from DHCPv6 server */ + DNS_SOURCE_DHCPV6, + /** Server information is from IPv6 SLAAC (router advertisement) */ + DNS_SOURCE_IPV6_RA, + /** Server information is from PPP */ + DNS_SOURCE_PPP, +}; + /** Max size of the resolved name. */ #ifndef DNS_MAX_NAME_SIZE #define DNS_MAX_NAME_SIZE 20 @@ -320,6 +338,7 @@ typedef void (*dns_resolve_cb_t)(enum dns_resolve_status status, /** @cond INTERNAL_HIDDEN */ enum dns_resolve_context_state { + DNS_RESOLVE_CONTEXT_UNINITIALIZED = 0, DNS_RESOLVE_CONTEXT_ACTIVE, DNS_RESOLVE_CONTEXT_DEACTIVATING, DNS_RESOLVE_CONTEXT_INACTIVE, @@ -344,6 +363,9 @@ struct dns_resolve_context { */ int if_index; + /** Source of the DNS server, e.g., manual, DHCPv4/6, etc. */ + enum dns_server_source source; + /** Is this server mDNS one */ uint8_t is_mdns : 1; @@ -517,12 +539,65 @@ int dns_resolve_close(struct dns_resolve_context *ctx); * @param servers_sa DNS server addresses as struct sockaddr. The array * is NULL terminated. Port numbers are optional in struct sockaddr, the * default will be used if set to 0. + * @param source Source of the DNS servers, e.g., manual, DHCPv4/6, etc. * * @return 0 if ok, <0 if error. */ int dns_resolve_reconfigure(struct dns_resolve_context *ctx, const char *servers_str[], - const struct sockaddr *servers_sa[]); + const struct sockaddr *servers_sa[], + enum dns_server_source source); + +/** + * @brief Reconfigure DNS resolving context with new server list and + * allowing servers to be specified to a specific network interface. + * + * @param ctx DNS context + * @param servers_str DNS server addresses using textual strings. The + * array is NULL terminated. The port number can be given in the string. + * Syntax for the server addresses with or without port numbers: + * IPv4 : 10.0.9.1 + * IPv4 + port : 10.0.9.1:5353 + * IPv6 : 2001:db8::22:42 + * IPv6 + port : [2001:db8::22:42]:5353 + * @param servers_sa DNS server addresses as struct sockaddr. The array + * is NULL terminated. Port numbers are optional in struct sockaddr, the + * default will be used if set to 0. + * @param interfaces Network interfaces to which the DNS servers are bound. + * This is an array of network interface indices. The array must be + * the same length as the servers_str and servers_sa arrays. + * @param source Source of the DNS servers, e.g., manual, DHCPv4/6, etc. + * + * @return 0 if ok, <0 if error. + */ +int dns_resolve_reconfigure_with_interfaces(struct dns_resolve_context *ctx, + const char *servers_str[], + const struct sockaddr *servers_sa[], + int interfaces[], + enum dns_server_source source); + +/** + * @brief Remove servers from the DNS resolving context. + * + * @param ctx DNS context + * @param if_index Network interface from which the DNS servers are removed. + * + * @return 0 if ok, <0 if error. + */ +int dns_resolve_remove(struct dns_resolve_context *ctx, int if_index); + +/** + * @brief Remove servers from the DNS resolving context that were added by + * a specific source. + * + * @param ctx DNS context + * @param if_index Network interface from which the DNS servers are removed. + * @param source Source of the DNS servers, e.g., manual, DHCPv4/6, etc. + * + * @return 0 if ok, <0 if error. + */ +int dns_resolve_remove_source(struct dns_resolve_context *ctx, int if_index, + enum dns_server_source source); /** * @brief Cancel a pending DNS query. @@ -664,6 +739,15 @@ static inline int dns_cancel_addr_info(uint16_t dns_id) /** @cond INTERNAL_HIDDEN */ +/** + * @brief Get string representation of the DNS server source. + * + * @param source Source of the DNS server. + * + * @return String representation of the DNS server source. + */ +const char *dns_get_source_str(enum dns_server_source source); + /** * @brief Initialize DNS subsystem. */ diff --git a/subsys/net/ip/Kconfig.stack b/subsys/net/ip/Kconfig.stack index c02fe47bafc..ed8853d1394 100644 --- a/subsys/net/ip/Kconfig.stack +++ b/subsys/net/ip/Kconfig.stack @@ -17,6 +17,7 @@ config NET_TX_STACK_SIZE config NET_RX_STACK_SIZE int "RX thread stack size" + default 1792 if DNS_RESOLVER default 1500 help Set the RX thread stack size in bytes. The RX thread is waiting diff --git a/subsys/net/ip/ipv6_nbr.c b/subsys/net/ip/ipv6_nbr.c index b64a73ed537..1cf7e53ad48 100644 --- a/subsys/net/ip/ipv6_nbr.c +++ b/subsys/net/ip/ipv6_nbr.c @@ -2469,6 +2469,9 @@ static inline bool handle_ra_rdnss(struct net_pkt *pkt, uint8_t len) const struct sockaddr *dns_servers[] = { (struct sockaddr *)&dns, NULL }; + int interfaces[] = { + net_if_get_by_iface(net_pkt_iface(pkt)) + }; size_t rdnss_size; int ret; @@ -2505,7 +2508,9 @@ static inline bool handle_ra_rdnss(struct net_pkt *pkt, uint8_t len) /* TODO: Handle lifetime. */ ctx = dns_resolve_get_default(); - ret = dns_resolve_reconfigure(ctx, NULL, dns_servers); + ret = dns_resolve_reconfigure_with_interfaces(ctx, NULL, dns_servers, + interfaces, + DNS_SOURCE_IPV6_RA); if (ret < 0) { NET_DBG("Failed to set RDNSS resolve address: %d", ret); } diff --git a/subsys/net/l2/ppp/ipcp.c b/subsys/net/l2/ppp/ipcp.c index b345802e68e..35b7bac018b 100644 --- a/subsys/net/l2/ppp/ipcp.c +++ b/subsys/net/l2/ppp/ipcp.c @@ -327,6 +327,8 @@ static void ipcp_set_dns_servers(struct ppp_fsm *fsm) (struct sockaddr *) &dns2, NULL }; + int ifindex = net_if_get_by_iface(ctx->iface); + int interfaces[2] = { ifindex, ifindex }; int ret; if (!dns1.sin_addr.s_addr) { @@ -338,7 +340,9 @@ static void ipcp_set_dns_servers(struct ppp_fsm *fsm) } dnsctx = dns_resolve_get_default(); - ret = dns_resolve_reconfigure(dnsctx, NULL, dns_servers); + ret = dns_resolve_reconfigure_with_interfaces(dnsctx, NULL, dns_servers, + interfaces, + DNS_SOURCE_PPP); if (ret < 0) { NET_ERR("Could not set DNS servers"); return; diff --git a/subsys/net/lib/dhcpv4/Kconfig b/subsys/net/lib/dhcpv4/Kconfig index 52dcfc099b5..cf7603ae65a 100644 --- a/subsys/net/lib/dhcpv4/Kconfig +++ b/subsys/net/lib/dhcpv4/Kconfig @@ -96,6 +96,23 @@ config NET_DHCPV4_OPTION_PRINT_IGNORED received and ignored. If this is not set, then we print these as unknown options. +config NET_DHCPV4_DNS_SERVER_VIA_INTERFACE + bool "Make DNS servers specific to the network interface" + depends on NET_DHCPV4_OPTION_DNS_ADDRESS + default y + help + If this is set, then if the system has multiple network interfaces + and each has DHCP enabled, then assign DNS servers received from that + network interface, to that specific interface. + If this option is not set, then any interface can be used for all + the configured DNS server addresses when doing DNS queries. + Example: We receive DNS server 192.0.2.53 DHCPv4 option from Wi-Fi + interface and DNS server 198.51.100.53 from Ethernet interface. + When this option is set, the DNS resolver will use DNS server + 192.0.2.53 when sending DNS query to the Wi-Fi interface and DNS + server 198.51.100.53 when sending DNS query to the Ethernet + interface. + endif # NET_DHCPV4 config NET_DHCPV4_SERVER diff --git a/subsys/net/lib/dhcpv4/dhcpv4.c b/subsys/net/lib/dhcpv4/dhcpv4.c index f7d7447b27d..2096bf330d9 100644 --- a/subsys/net/lib/dhcpv4/dhcpv4.c +++ b/subsys/net/lib/dhcpv4/dhcpv4.c @@ -1171,7 +1171,27 @@ static bool dhcpv4_parse_options(struct net_pkt *pkt, for (uint8_t i = 0; i < dns_servers_cnt; i++) { dnses[i].sin_family = AF_INET; } - status = dns_resolve_reconfigure(ctx, NULL, dns_servers); + + if (IS_ENABLED(CONFIG_NET_DHCPV4_DNS_SERVER_VIA_INTERFACE)) { + /* If we are using the interface to resolve DNS servers, + * we need to save the interface index. + */ + int ifindex = net_if_get_by_iface(iface); + int interfaces[MAX_DNS_SERVERS]; + + for (uint8_t i = 0; i < dns_servers_cnt; i++) { + interfaces[i] = ifindex; + } + + status = dns_resolve_reconfigure_with_interfaces(ctx, NULL, + dns_servers, + interfaces, + DNS_SOURCE_DHCPV4); + } else { + status = dns_resolve_reconfigure(ctx, NULL, dns_servers, + DNS_SOURCE_DHCPV4); + } + if (status < 0) { NET_DBG("options_dns, failed to set " "resolve address: %d", status); @@ -1650,6 +1670,17 @@ static void dhcpv4_iface_event_handler(struct net_mgmt_event_callback *cb, if (!net_if_ipv4_addr_rm(iface, &iface->config.dhcpv4.requested_ip)) { NET_DBG("Failed to remove addr from iface"); } + + /* Remove DNS servers as interface is gone. We only need to + * do this for this interface. If using global setting, the + * DNS servers are removed automatically when the interface + * comes back up. + */ + if (IS_ENABLED(CONFIG_NET_DHCPV4_DNS_SERVER_VIA_INTERFACE)) { + dns_resolve_remove_source(dns_resolve_get_default(), + net_if_get_by_iface(iface), + DNS_SOURCE_DHCPV4); + } } } else if (mgmt_event == NET_EVENT_IF_UP) { NET_DBG("Interface %p coming up", iface); @@ -1914,6 +1945,12 @@ void net_dhcpv4_stop(struct net_if *iface) NET_DBG("state=%s", net_dhcpv4_state_name(iface->config.dhcpv4.state)); + if (IS_ENABLED(CONFIG_NET_DHCPV4_DNS_SERVER_VIA_INTERFACE)) { + dns_resolve_remove_source(dns_resolve_get_default(), + net_if_get_by_iface(iface), + DNS_SOURCE_DHCPV4); + } + sys_slist_find_and_remove(&dhcpv4_ifaces, &iface->config.dhcpv4.node); diff --git a/subsys/net/lib/dhcpv6/Kconfig b/subsys/net/lib/dhcpv6/Kconfig index dad049dac77..7fda850ec5e 100644 --- a/subsys/net/lib/dhcpv6/Kconfig +++ b/subsys/net/lib/dhcpv6/Kconfig @@ -31,6 +31,23 @@ config NET_DHCPV6_OPTION_DNS_ADDRESS option from the server, and if available, use obtained information to configure DNS resolver. +config NET_DHCPV6_DNS_SERVER_VIA_INTERFACE + bool "Make DNS servers specific to the network interface" + depends on NET_DHCPV6_OPTION_DNS_ADDRESS + default y + help + If this is set, then if the system has multiple network interfaces + and each has DHCP enabled, then assign DNS servers received from that + network interface, to that specific interface. + If this option is not set, then any interface can be used for all + the configured DNS server addresses when doing DNS queries. + Example: We receive DNS server 2001:db8::1:53 DHCPv6 option from Wi-Fi + interface and DNS server 2001:db8::2:53 from Ethernet interface. + When this option is set, the DNS resolver will use DNS server + 2001:db8::1:53 when sending DNS query to the Wi-Fi interface and DNS + server 2001:db8::2:53 when sending DNS query to the Ethernet + interface. + if NET_DHCPV6 module = NET_DHCPV6 module-dep = NET_LOG diff --git a/subsys/net/lib/dhcpv6/dhcpv6.c b/subsys/net/lib/dhcpv6/dhcpv6.c index 29dd652d428..c1282c2a6f1 100644 --- a/subsys/net/lib/dhcpv6/dhcpv6.c +++ b/subsys/net/lib/dhcpv6/dhcpv6.c @@ -1412,7 +1412,27 @@ static int dhcpv6_handle_dns_server_option(struct net_pkt *pkt) } ctx = dns_resolve_get_default(); - status = dns_resolve_reconfigure(ctx, NULL, dns_servers); + + if (IS_ENABLED(CONFIG_NET_DHCPV6_DNS_SERVER_VIA_INTERFACE)) { + /* If we are using the interface to resolve DNS servers, + * we need to save the interface index. + */ + int ifindex = net_if_get_by_iface(net_pkt_iface(pkt)); + int interfaces[MAX_DNS_SERVERS]; + + for (uint8_t i = 0; i < server_count; i++) { + interfaces[i] = ifindex; + } + + status = dns_resolve_reconfigure_with_interfaces(ctx, NULL, + dns_servers, + interfaces, + DNS_SOURCE_DHCPV6); + } else { + status = dns_resolve_reconfigure(ctx, NULL, dns_servers, + DNS_SOURCE_DHCPV6); + } + if (status < 0) { NET_DBG("Failed to reconfigure DNS resolver from DHCPv6 " "option: %d", status); @@ -2199,6 +2219,17 @@ static void dhcpv6_iface_event_handler(struct net_mgmt_event_callback *cb, if (mgmt_event == NET_EVENT_IF_DOWN) { NET_DBG("Interface %p going down", iface); dhcpv6_set_timeout(iface, UINT64_MAX); + + /* Remove DNS servers as interface is gone. We only need to + * do this for this interface. If using global setting, the + * DNS servers are removed automatically when the interface + * comes back up. + */ + if (IS_ENABLED(CONFIG_NET_DHCPV6_DNS_SERVER_VIA_INTERFACE)) { + dns_resolve_remove_source(dns_resolve_get_default(), + net_if_get_by_iface(iface), + DNS_SOURCE_DHCPV6); + } } else if (mgmt_event == NET_EVENT_IF_UP) { NET_DBG("Interface %p coming up", iface); dhcpv6_enter_state(iface, NET_DHCPV6_INIT); @@ -2293,6 +2324,12 @@ void net_dhcpv6_stop(struct net_if *iface) (void)dhcpv6_enter_state(iface, NET_DHCPV6_DISABLED); + if (IS_ENABLED(CONFIG_NET_DHCPV6_DNS_SERVER_VIA_INTERFACE)) { + dns_resolve_remove_source(dns_resolve_get_default(), + net_if_get_by_iface(iface), + DNS_SOURCE_DHCPV6); + } + sys_slist_find_and_remove(&dhcpv6_ifaces, &iface->config.dhcpv6.node); diff --git a/subsys/net/lib/dns/Kconfig b/subsys/net/lib/dns/Kconfig index c3f3ba6bc99..a413f04f656 100644 --- a/subsys/net/lib/dns/Kconfig +++ b/subsys/net/lib/dns/Kconfig @@ -121,6 +121,19 @@ config DNS_SERVER5 endif # DNS_SERVER_IP_ADDRESSES +config DNS_RECONFIGURE_CLEANUP + bool "Cleanup old DNS server entries when reconfiguring" + help + If calling dns_resolve_reconfigure() when new DNS servers + are being set, for example if receiving new ones from DHCP server, + remove the old entries before setting up the new ones. + If you have only one network interface, then this can be enabled. + If you have multiple network interfaces, then this should be disabled + because the later configuration update would remove the entries + set by the other network interface configuration. + The previous default in Zephyr 4.1 or earlier was to have this enabled. + The current default in Zephyr 4.2 is to disable this option. + config DNS_NUM_CONCUR_QUERIES int "Number of simultaneous DNS queries per one DNS context" default 1 diff --git a/subsys/net/lib/dns/resolve.c b/subsys/net/lib/dns/resolve.c index 813b7cd8f6c..9025501971e 100644 --- a/subsys/net/lib/dns/resolve.c +++ b/subsys/net/lib/dns/resolve.c @@ -81,6 +81,7 @@ NET_BUF_POOL_DEFINE(dns_qname_pool, DNS_RESOLVER_BUF_CTR, DNS_CACHE_DEFINE(dns_cache, CONFIG_DNS_RESOLVER_CACHE_MAX_ENTRIES); #endif /* CONFIG_DNS_RESOLVER_CACHE */ +static K_MUTEX_DEFINE(lock); static int init_called; static struct dns_resolve_context dns_default_ctx; @@ -414,12 +415,107 @@ static int bind_to_iface(int sock, const struct sockaddr *addr, int if_index) return ret; } +static bool is_server_name_found(struct dns_resolve_context *ctx, + const char *server, size_t server_len, + const char *iface_str) +{ + ARRAY_FOR_EACH(ctx->servers, i) { + if (ctx->servers[i].dns_server.sa_family == AF_INET || + ctx->servers[i].dns_server.sa_family == AF_INET6) { + char addr_str[INET6_ADDRSTRLEN]; + size_t addr_len; + + if (net_addr_ntop(ctx->servers[i].dns_server.sa_family, + &net_sin(&ctx->servers[i].dns_server)->sin_addr, + addr_str, sizeof(addr_str)) < 0) { + continue; + } + + addr_len = strlen(addr_str); + if (addr_len != server_len || + strncmp(addr_str, server, server_len) != 0) { + continue; + } + + if (iface_str != NULL && ctx->servers[i].if_index > 0) { + char iface_name[IFNAMSIZ]; + + net_if_get_name(net_if_get_by_index( + ctx->servers[i].if_index), + iface_name, sizeof(iface_name)); + + if (strcmp(iface_name, iface_str) != 0) { + continue; + } + } + + return true; + } + } + + return false; +} + +static bool is_server_addr_found(struct dns_resolve_context *ctx, + const struct sockaddr *addr, + int if_index) +{ + ARRAY_FOR_EACH(ctx->servers, i) { + if (ctx->servers[i].dns_server.sa_family == addr->sa_family && + memcmp(&ctx->servers[i].dns_server, addr, + sizeof(ctx->servers[i].dns_server)) == 0) { + if (if_index == 0 || + (if_index > 0 && + ctx->servers[i].if_index != if_index)) { + continue; + } + + return true; + } + } + + return false; +} + +static int get_free_slot(struct dns_resolve_context *ctx) +{ + ARRAY_FOR_EACH(ctx->servers, i) { + if (ctx->servers[i].dns_server.sa_family == 0) { + return i; + } + } + + return -ENOENT; +} + +const char *dns_get_source_str(enum dns_server_source source) +{ + switch (source) { + case DNS_SOURCE_UNKNOWN: + return "unknown"; + case DNS_SOURCE_MANUAL: + return "manual"; + case DNS_SOURCE_DHCPV4: + __fallthrough; + case DNS_SOURCE_DHCPV6: + return "DHCP"; + case DNS_SOURCE_IPV6_RA: + return "IPv6 RA"; + case DNS_SOURCE_PPP: + return "PPP"; + } + + return ""; +} + /* Must be invoked with context lock held */ static int dns_resolve_init_locked(struct dns_resolve_context *ctx, const char *servers[], const struct sockaddr *servers_sa[], const struct net_socket_service_desc *svc, - uint16_t port, int interfaces[]) + uint16_t port, int interfaces[], + bool do_cleanup, + enum dns_server_source source) { #if defined(CONFIG_NET_IPV6) struct sockaddr_in6 local_addr6 = { @@ -445,17 +541,20 @@ static int dns_resolve_init_locked(struct dns_resolve_context *ctx, return -ENOENT; } - if (ctx->state != DNS_RESOLVE_CONTEXT_INACTIVE) { - ret = -ENOTEMPTY; - goto fail; - } + if (do_cleanup) { + if (ctx->state != DNS_RESOLVE_CONTEXT_INACTIVE) { + ret = -ENOTEMPTY; + NET_DBG("DNS resolver context is not inactive (%d)", ctx->state); + goto fail; + } - ARRAY_FOR_EACH(ctx->servers, j) { - ctx->servers[j].sock = -1; - } + ARRAY_FOR_EACH(ctx->servers, j) { + ctx->servers[j].sock = -1; + } - ARRAY_FOR_EACH(ctx->fds, j) { - ctx->fds[j].fd = -1; + ARRAY_FOR_EACH(ctx->fds, j) { + ctx->fds[j].fd = -1; + } } /* If user has provided a list of servers in string format, then @@ -464,78 +563,156 @@ static int dns_resolve_init_locked(struct dns_resolve_context *ctx, * The interfaces parameter should point to an array that is the * the same length as the servers_sa parameter array. */ - if (servers) { - for (i = 0; idx < SERVER_COUNT && servers[i]; i++) { - const char *iface_str; - size_t server_len; - - struct sockaddr *addr = &ctx->servers[idx].dns_server; - - iface_str = strstr(servers[i], "%"); - if (iface_str) { - server_len = iface_str - servers[i]; - iface_str++; + for (i = 0; servers != NULL && idx < SERVER_COUNT && servers[i] != NULL; i++) { + const char *iface_str; + size_t server_len; + struct sockaddr *addr; + bool found; + + iface_str = strstr(servers[i], "%"); + if (iface_str != NULL) { + server_len = iface_str - servers[i]; + iface_str++; + + if (server_len == 0) { + NET_DBG("Empty server name"); + continue; + } - if (server_len == 0) { - NET_DBG("Empty server name"); - continue; - } + found = is_server_name_found(ctx, servers[i], + server_len, iface_str); + if (found) { + NET_DBG("Server %.*s already exists", + (int)server_len, servers[i]); + continue; + } - /* Skip empty interface name */ - if (iface_str[0] == '\0') { - ctx->servers[idx].if_index = 0; - iface_str = NULL; - } else { - ctx->servers[idx].if_index = - net_if_get_by_name(iface_str); - } + /* Figure out if there are free slots where to add + * the server. + */ + idx = get_free_slot(ctx); + if (idx < 0) { + NET_DBG("No free slots for server %.*s", + (int)server_len, servers[i]); + break; + } - } else { - server_len = strlen(servers[i]); + /* Skip empty interface name */ + if (iface_str[0] == '\0') { ctx->servers[idx].if_index = 0; + iface_str = NULL; + } else { + ctx->servers[idx].if_index = + net_if_get_by_name(iface_str); } - (void)memset(addr, 0, sizeof(*addr)); - - ret = net_ipaddr_parse(servers[i], server_len, addr); - if (!ret) { - if (servers[i] != NULL && servers[i][0] != '\0') { - NET_DBG("Invalid server address %.*s", - (int)server_len, servers[i]); - } + } else { + server_len = strlen(servers[i]); + if (server_len == 0) { + NET_DBG("Empty server name"); + continue; + } + found = is_server_name_found(ctx, servers[i], + server_len, NULL); + if (found) { + NET_DBG("Server %.*s already exists", + (int)server_len, servers[i]); continue; } - dns_postprocess_server(ctx, idx); + idx = get_free_slot(ctx); + if (idx < 0) { + NET_DBG("No free slots for server %.*s", + (int)server_len, servers[i]); + break; + } + } + + ctx->servers[idx].source = source; + + addr = &ctx->servers[idx].dns_server; - NET_DBG("[%d] %.*s%s%s%s%s", i, (int)server_len, servers[i], - IS_ENABLED(CONFIG_MDNS_RESOLVER) ? - (ctx->servers[i].is_mdns ? " mDNS" : "") : "", - IS_ENABLED(CONFIG_LLMNR_RESOLVER) ? - (ctx->servers[i].is_llmnr ? " LLMNR" : "") : "", - iface_str != NULL ? " via " : "", - iface_str != NULL ? iface_str : ""); - idx++; + (void)memset(addr, 0, sizeof(*addr)); + + ret = net_ipaddr_parse(servers[i], server_len, addr); + if (!ret) { + if (servers[i][0] != '\0') { + NET_DBG("Invalid server address %.*s", + (int)server_len, servers[i]); + } + + continue; } + + dns_postprocess_server(ctx, idx); + + NET_DBG("[%d] %.*s%s%s%s%s%s%s%s", i, (int)server_len, servers[i], + IS_ENABLED(CONFIG_MDNS_RESOLVER) ? + (ctx->servers[i].is_mdns ? " mDNS" : "") : "", + IS_ENABLED(CONFIG_LLMNR_RESOLVER) ? + (ctx->servers[i].is_llmnr ? " LLMNR" : "") : "", + iface_str != NULL ? " via " : "", + iface_str != NULL ? iface_str : "", + source != DNS_SOURCE_UNKNOWN ? " (" : "", + source != DNS_SOURCE_UNKNOWN ? dns_get_source_str(source) : "", + source != DNS_SOURCE_UNKNOWN ? ")" : ""); + idx++; } - if (servers_sa) { - for (i = 0; idx < SERVER_COUNT && servers_sa[i]; i++) { - memcpy(&ctx->servers[idx].dns_server, servers_sa[i], - sizeof(ctx->servers[idx].dns_server)); + for (i = 0; servers_sa != NULL && idx < SERVER_COUNT && servers_sa[i] != NULL; i++) { + char iface_str[IFNAMSIZ] = { 0 }; + bool found; - if (interfaces != NULL) { - ctx->servers[idx].if_index = interfaces[idx]; - } + found = is_server_addr_found(ctx, servers_sa[i], interfaces[i]); + if (found) { + NET_DBG("Server %s already exists", + net_sprint_addr(ctx->servers[i].dns_server.sa_family, + &net_sin(&ctx->servers[i].dns_server)->sin_addr)); + continue; + } - dns_postprocess_server(ctx, idx); - idx++; + /* Figure out if there are free slots where to add the server. + */ + idx = get_free_slot(ctx); + if (idx < 0) { + NET_DBG("No free slots for server %s", + net_sprint_addr(ctx->servers[i].dns_server.sa_family, + &net_sin(&ctx->servers[i].dns_server)->sin_addr)); + break; } + + ctx->servers[idx].source = source; + + memcpy(&ctx->servers[idx].dns_server, servers_sa[i], + sizeof(ctx->servers[idx].dns_server)); + + if (interfaces != NULL) { + ctx->servers[idx].if_index = interfaces[i]; + + net_if_get_name(net_if_get_by_index(ctx->servers[idx].if_index), + iface_str, sizeof(iface_str)); + } + + dns_postprocess_server(ctx, idx); + + NET_DBG("[%d] %s%s%s%s%s%s%s%s", i, + net_sprint_addr(servers_sa[i]->sa_family, + &net_sin(servers_sa[i])->sin_addr), + IS_ENABLED(CONFIG_MDNS_RESOLVER) ? + (ctx->servers[i].is_mdns ? " mDNS" : "") : "", + IS_ENABLED(CONFIG_LLMNR_RESOLVER) ? + (ctx->servers[i].is_llmnr ? " LLMNR" : "") : "", + interfaces != NULL ? " via " : "", + interfaces != NULL ? iface_str : "", + source != DNS_SOURCE_UNKNOWN ? " (" : "", + source != DNS_SOURCE_UNKNOWN ? dns_get_source_str(source) : "", + source != DNS_SOURCE_UNKNOWN ? ")" : ""); + idx++; } for (i = 0, count = 0; - i < SERVER_COUNT && ctx->servers[i].dns_server.sa_family; i++) { + i < SERVER_COUNT && ctx->servers[i].dns_server.sa_family != 0; i++) { if (ctx->servers[i].dns_server.sa_family == AF_INET6) { #if defined(CONFIG_NET_IPV6) @@ -571,6 +748,16 @@ static int dns_resolve_init_locked(struct dns_resolve_context *ctx, goto fail; } + if (ctx->servers[i].sock >= 0) { + /* Socket already exists, so skip it */ + NET_DBG("Socket %d already exists for %s", + ctx->servers[i].sock, + net_sprint_addr(ctx->servers[i].dns_server.sa_family, + &net_sin(&ctx->servers[i].dns_server)->sin_addr)); + count++; + continue; + } + ret = zsock_socket(ctx->servers[i].dns_server.sa_family, SOCK_DGRAM, IPPROTO_UDP); if (ret < 0) { @@ -589,6 +776,7 @@ static int dns_resolve_init_locked(struct dns_resolve_context *ctx, if (ret < 0) { zsock_close(ctx->servers[i].sock); ctx->servers[i].sock = -1; + ctx->servers[i].dns_server.sa_family = 0; continue; } @@ -637,6 +825,8 @@ static int dns_resolve_init_locked(struct dns_resolve_context *ctx, if (ret < 0) { NET_DBG("Cannot set %s to socket (%d)", "polling", ret); zsock_close(ctx->servers[i].sock); + ctx->servers[i].sock = -1; + ctx->servers[i].dns_server.sa_family = 0; continue; } @@ -695,20 +885,28 @@ int dns_resolve_init_with_svc(struct dns_resolve_context *ctx, const char *serve const struct net_socket_service_desc *svc, uint16_t port, int interfaces[]) { + int ret; + if (!ctx) { return -ENOENT; } - (void)memset(ctx, 0, sizeof(*ctx)); + k_mutex_lock(&lock, K_FOREVER); - (void)k_mutex_init(&ctx->lock); - ctx->state = DNS_RESOLVE_CONTEXT_INACTIVE; + /* Do cleanup only if we are starting the context for the first time */ + if (init_called == 0) { + (void)memset(ctx, 0, sizeof(*ctx)); - /* As this function is called only once during system init, there is no - * reason to acquire lock. - */ - return dns_resolve_init_locked(ctx, servers, servers_sa, svc, port, - interfaces); + (void)k_mutex_init(&ctx->lock); + ctx->state = DNS_RESOLVE_CONTEXT_INACTIVE; + } + + ret = dns_resolve_init_locked(ctx, servers, servers_sa, svc, port, + interfaces, true, DNS_SOURCE_UNKNOWN); + + k_mutex_unlock(&lock); + + return ret; } int dns_resolve_init(struct dns_resolve_context *ctx, const char *servers[], @@ -1739,10 +1937,51 @@ int dns_resolve_name(struct dns_resolve_context *ctx, user_data, timeout, true); } +static int dns_server_close(struct dns_resolve_context *ctx, + int server_idx) +{ + struct net_if *iface; + + if (ctx->servers[server_idx].sock < 0) { + return -ENOENT; + } + + (void)dns_dispatcher_unregister(&ctx->servers[server_idx].dispatcher); + + if (ctx->servers[server_idx].dns_server.sa_family == AF_INET6) { + iface = net_if_ipv6_select_src_iface( + &net_sin6(&ctx->servers[server_idx].dns_server)->sin6_addr); + } else { + iface = net_if_ipv4_select_src_iface( + &net_sin(&ctx->servers[server_idx].dns_server)->sin_addr); + } + + if (IS_ENABLED(CONFIG_NET_MGMT_EVENT_INFO)) { + net_mgmt_event_notify_with_info( + NET_EVENT_DNS_SERVER_DEL, + iface, + (void *)&ctx->servers[server_idx].dns_server, + sizeof(struct sockaddr)); + } else { + net_mgmt_event_notify(NET_EVENT_DNS_SERVER_DEL, iface); + } + + zsock_close(ctx->servers[server_idx].sock); + + ctx->servers[server_idx].sock = -1; + ctx->servers[server_idx].dns_server.sa_family = 0; + + ARRAY_FOR_EACH(ctx->fds, j) { + ctx->fds[j].fd = -1; + } + + return 0; +} + /* Must be invoked with context lock held */ static int dns_resolve_close_locked(struct dns_resolve_context *ctx) { - int i; + int i, ret; if (ctx->state != DNS_RESOLVE_CONTEXT_ACTIVE) { return -ENOENT; @@ -1762,44 +2001,10 @@ static int dns_resolve_close_locked(struct dns_resolve_context *ctx) k_mutex_unlock(&ctx->lock); for (i = 0; i < SERVER_COUNT; i++) { - struct net_if *iface; - - if (ctx->servers[i].sock < 0) { - continue; - } - - (void)dns_dispatcher_unregister(&ctx->servers[i].dispatcher); - - if (ctx->servers[i].dns_server.sa_family == AF_INET6) { - iface = net_if_ipv6_select_src_iface( - &net_sin6(&ctx->servers[i].dns_server)->sin6_addr); - } else { - iface = net_if_ipv4_select_src_iface( - &net_sin(&ctx->servers[i].dns_server)->sin_addr); - } - - if (IS_ENABLED(CONFIG_NET_MGMT_EVENT_INFO)) { - net_mgmt_event_notify_with_info( - NET_EVENT_DNS_SERVER_DEL, - iface, - (void *)&ctx->servers[i].dns_server, - sizeof(struct sockaddr)); - } else { - net_mgmt_event_notify(NET_EVENT_DNS_SERVER_DEL, - iface); - } - - zsock_close(ctx->servers[i].sock); - - ARRAY_FOR_EACH(ctx->fds, j) { - if (ctx->fds[j].fd == ctx->servers[i].sock) { - ctx->fds[j].fd = -1; - } + ret = dns_server_close(ctx, i); + if (ret < 0) { + NET_DBG("Cannot close DNS server %d (%d)", i, ret); } - - (void)dns_dispatcher_unregister(&ctx->servers[i].dispatcher); - - ctx->servers[i].sock = -1; } if (--init_called <= 0) { @@ -1877,9 +2082,12 @@ static bool dns_servers_exists(struct dns_resolve_context *ctx, return true; } -int dns_resolve_reconfigure(struct dns_resolve_context *ctx, - const char *servers[], - const struct sockaddr *servers_sa[]) +static int do_dns_resolve_reconfigure(struct dns_resolve_context *ctx, + const char *servers[], + const struct sockaddr *servers_sa[], + int interfaces[], + bool do_close, + enum dns_server_source source) { int err; @@ -1887,6 +2095,7 @@ int dns_resolve_reconfigure(struct dns_resolve_context *ctx, return -ENOENT; } + k_mutex_lock(&lock, K_FOREVER); k_mutex_lock(&ctx->lock, K_FOREVER); if (dns_servers_exists(ctx, servers, servers_sa)) { @@ -1900,23 +2109,124 @@ int dns_resolve_reconfigure(struct dns_resolve_context *ctx, goto unlock; } - if (ctx->state == DNS_RESOLVE_CONTEXT_ACTIVE) { + if (ctx->state == DNS_RESOLVE_CONTEXT_ACTIVE && + (do_close || init_called == 0)) { dns_resolve_cancel_all(ctx); err = dns_resolve_close_locked(ctx); if (err) { goto unlock; } + + /* Make sure we do fresh start once */ + do_close = true; } - err = dns_resolve_init_locked(ctx, servers, servers_sa, &resolve_svc, 0, NULL); + err = dns_resolve_init_locked(ctx, servers, servers_sa, + &resolve_svc, 0, interfaces, + do_close, + source); unlock: k_mutex_unlock(&ctx->lock); + k_mutex_unlock(&lock); return err; } +int dns_resolve_reconfigure_with_interfaces(struct dns_resolve_context *ctx, + const char *servers[], + const struct sockaddr *servers_sa[], + int interfaces[], + enum dns_server_source source) +{ + return do_dns_resolve_reconfigure(ctx, + servers, + servers_sa, + interfaces, + IS_ENABLED(CONFIG_DNS_RECONFIGURE_CLEANUP) ? + true : false, + source); +} + +int dns_resolve_reconfigure(struct dns_resolve_context *ctx, + const char *servers[], + const struct sockaddr *servers_sa[], + enum dns_server_source source) +{ + return do_dns_resolve_reconfigure(ctx, + servers, + servers_sa, + NULL, + IS_ENABLED(CONFIG_DNS_RECONFIGURE_CLEANUP) ? + true : false, + source); +} + +static int dns_resolve_remove_and_check_source(struct dns_resolve_context *ctx, int if_index, + bool check_source, + enum dns_server_source source) +{ + int i; + int ret = -ENOENT; + int st = 0; + + if (!ctx) { + return -ENOENT; + } + + if (if_index <= 0) { + /* If network interface index is 0, then do nothing. + * If your want to remove all the servers, then just call + * dns_resolve_close() directly. + */ + return -EINVAL; + } + + k_mutex_lock(&ctx->lock, K_FOREVER); + + for (i = 0; i < SERVER_COUNT; i++) { + if (ctx->servers[i].if_index != if_index) { + continue; + } + + if (check_source && ctx->servers[i].source != source) { + continue; + } + + ctx->servers[i].if_index = 0; + + /* See comment in dns_resolve_close_locked() about + * releasing the lock before closing the server socket. + */ + k_mutex_unlock(&ctx->lock); + + ret = dns_server_close(ctx, i); + + k_mutex_lock(&ctx->lock, K_FOREVER); + + if (ret < 0) { + st = ret; + } + } + + k_mutex_unlock(&ctx->lock); + + return st; +} + +int dns_resolve_remove(struct dns_resolve_context *ctx, int if_index) +{ + return dns_resolve_remove_and_check_source(ctx, if_index, false, + DNS_SOURCE_UNKNOWN); +} + +int dns_resolve_remove_source(struct dns_resolve_context *ctx, int if_index, + enum dns_server_source source) +{ + return dns_resolve_remove_and_check_source(ctx, if_index, true, source); +} + struct dns_resolve_context *dns_resolve_get_default(void) { return &dns_default_ctx; diff --git a/subsys/net/lib/shell/conn.c b/subsys/net/lib/shell/conn.c index 32dc32fcf44..ca852f1d15d 100644 --- a/subsys/net/lib/shell/conn.c +++ b/subsys/net/lib/shell/conn.c @@ -89,6 +89,8 @@ static void conn_handler_cb(struct net_conn *conn, void *user_data) } else if (conn->local_addr.sa_family == AF_UNSPEC) { snprintk(addr_local, sizeof(addr_local), "AF_UNSPEC"); + } else if (conn->local_addr.sa_family == AF_PACKET) { + snprintk(addr_local, sizeof(addr_local), "AF_PACKET"); } else { snprintk(addr_local, sizeof(addr_local), "AF_UNK(%d)", conn->local_addr.sa_family); diff --git a/subsys/net/lib/shell/dns.c b/subsys/net/lib/shell/dns.c index 6c6285990d8..e01b58096c3 100644 --- a/subsys/net/lib/shell/dns.c +++ b/subsys/net/lib/shell/dns.c @@ -93,22 +93,30 @@ static void print_dns_info(const struct shell *sh, } if (ctx->servers[i].dns_server.sa_family == AF_INET) { - PR("\t%s:%u%s%s\n", + PR("\t%s:%u%s%s%s%s%s\n", net_sprint_ipv4_addr( &net_sin(&ctx->servers[i].dns_server)-> sin_addr), ntohs(net_sin(&ctx->servers[i].dns_server)->sin_port), printable_iface(iface_name, " via ", ""), - printable_iface(iface_name, iface_name, "")); + printable_iface(iface_name, iface_name, ""), + ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? " (" : "", + ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? + dns_get_source_str(ctx->servers[i].source) : "", + ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? ")" : ""); } else if (ctx->servers[i].dns_server.sa_family == AF_INET6) { - PR("\t[%s]:%u%s%s\n", + PR("\t[%s]:%u%s%s%s%s%s\n", net_sprint_ipv6_addr( &net_sin6(&ctx->servers[i].dns_server)-> sin6_addr), ntohs(net_sin6(&ctx->servers[i].dns_server)->sin6_port), printable_iface(iface_name, " via ", ""), - printable_iface(iface_name, iface_name, "")); + printable_iface(iface_name, iface_name, ""), + ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? " (" : "", + ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? + dns_get_source_str(ctx->servers[i].source) : "", + ctx->servers[i].source != DNS_SOURCE_UNKNOWN ? ")" : ""); } } diff --git a/tests/net/lib/dns_addremove/src/main.c b/tests/net/lib/dns_addremove/src/main.c index eb8d25a5fa5..1b6f55efd73 100644 --- a/tests/net/lib/dns_addremove/src/main.c +++ b/tests/net/lib/dns_addremove/src/main.c @@ -466,19 +466,22 @@ ZTEST(dns_addremove, test_dns_reconfigure_callback) "Timeout while waiting for DNS added callback"); } - ret = dns_resolve_reconfigure(&resv_ipv4, dns2_servers_str, NULL); + ret = dns_resolve_reconfigure(&resv_ipv4, dns2_servers_str, NULL, + DNS_SOURCE_MANUAL); zassert_equal(ret, 0, "Cannot reconfigure DNS server"); /* Wait for DNS removed callback after reconfiguring DNS */ - if (k_sem_take(&dns_removed, WAIT_TIME)) { - zassert_true(false, - "Timeout while waiting for DNS removed callback"); - } - - /* Wait for DNS added callback after reconfiguring DNS */ - if (k_sem_take(&dns_added, WAIT_TIME)) { - zassert_true(false, - "Timeout while waiting for DNS added callback"); + if (IS_ENABLED(CONFIG_DNS_RECONFIGURE_CLEANUP)) { + if (k_sem_take(&dns_removed, WAIT_TIME)) { + zassert_true(false, + "Timeout while waiting for DNS removed callback"); + } + + /* Wait for DNS added callback after reconfiguring DNS */ + if (k_sem_take(&dns_added, WAIT_TIME)) { + zassert_true(false, + "Timeout while waiting for DNS added callback"); + } } ret = dns_resolve_close(&resv_ipv4); diff --git a/tests/net/lib/dns_addremove/testcase.yaml b/tests/net/lib/dns_addremove/testcase.yaml index 5551a41cfba..a6f56003f46 100644 --- a/tests/net/lib/dns_addremove/testcase.yaml +++ b/tests/net/lib/dns_addremove/testcase.yaml @@ -14,3 +14,6 @@ tests: net.dns.no_ipv4: extra_configs: - CONFIG_NET_IPV4=n + net.dns.addremove.reconfigure_cleanup: + extra_configs: + - CONFIG_DNS_RECONFIGURE_CLEANUP=y diff --git a/tests/net/lib/dns_dispatcher/src/main.c b/tests/net/lib/dns_dispatcher/src/main.c index 21e60466066..e0353e8ac1f 100644 --- a/tests/net/lib/dns_dispatcher/src/main.c +++ b/tests/net/lib/dns_dispatcher/src/main.c @@ -175,11 +175,14 @@ static void *test_init(void) ZTEST(dns_dispatcher, test_dns_dispatcher) { struct dns_resolve_context *ctx; - int sock1, sock2 = -1; + int ret, sock1, sock2 = -1; ctx = dns_resolve_get_default(); - dns_resolve_init_default(ctx); + dns_resolve_close(ctx); + + ret = dns_resolve_init_default(ctx); + zassert_equal(ret, 0, "Cannot initialize DNS resolver (%d)", ret); sock1 = ctx->servers[0].sock; diff --git a/tests/net/lib/dns_dispatcher/testcase.yaml b/tests/net/lib/dns_dispatcher/testcase.yaml index 314097ece66..b3ffd7c6479 100644 --- a/tests/net/lib/dns_dispatcher/testcase.yaml +++ b/tests/net/lib/dns_dispatcher/testcase.yaml @@ -8,3 +8,9 @@ common: tests: net.dns.dispatch: min_ram: 21 + net.dns.dispatch.ctx_cleanup: + extra_configs: + - CONFIG_DNS_RECONFIGURE_CLEANUP=y + net.dns.dispatch.ctx_no_cleanup: + extra_configs: + - CONFIG_DNS_RECONFIGURE_CLEANUP=n