From 6c95daf0ae3e3e2997acd8ec2fca1c810ea6d91b Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 7 Nov 2024 17:17:30 +0200 Subject: [PATCH] net: dns: Bind DNS server to a network interface Allow user to specify a network interface in the DNS server list. User can append "%" and network interface name to the DNS server to use this. If the network interface is mentioned in the server list, then the DNS queries are sent via this network interface. For example setting the interfaces like this: 192.0.2.2%eth1 [2001:db8::2]:5353%ppp0 would cause the DNS queries to sent to 192.0.2.1 via eth1 in the first example, and to 2001:db8::2 via ppp0 in the second example. Signed-off-by: Jukka Rissanen --- include/zephyr/net/dns_resolve.h | 5 ++ subsys/net/lib/dns/Kconfig | 6 ++ subsys/net/lib/dns/resolve.c | 98 +++++++++++++++++++++++++++++--- 3 files changed, 102 insertions(+), 7 deletions(-) diff --git a/include/zephyr/net/dns_resolve.h b/include/zephyr/net/dns_resolve.h index 9e1591c0b86..33398dabdf1 100644 --- a/include/zephyr/net/dns_resolve.h +++ b/include/zephyr/net/dns_resolve.h @@ -344,6 +344,11 @@ struct dns_resolve_context { /** Connection to the DNS server */ int sock; + /** Network interface index if the DNS resolving should be done + * via this interface. Value 0 indicates any interface can be used. + */ + int if_index; + /** Is this server mDNS one */ uint8_t is_mdns : 1; diff --git a/subsys/net/lib/dns/Kconfig b/subsys/net/lib/dns/Kconfig index 654f7c94013..8067e85653d 100644 --- a/subsys/net/lib/dns/Kconfig +++ b/subsys/net/lib/dns/Kconfig @@ -87,6 +87,12 @@ config DNS_SERVER1 192.0.2.1:5353 2001:db8::1 [2001:db8::1]:5353 + It is possible to bind the DNS connection via a certain network + interface by appending "%" and network interface name to the server + address. For example: 192.0.2.1%eth1 would bind the connection socket + to the network interface eth1. This is optional and by default the + resolver connects to server by selecting the output network interface + using normal IP routing. It is not mandatory to use this Kconfig option at all. The one calling dns_resolve_init() can use this option or not to populate the server list. If the DNS server addresses are diff --git a/subsys/net/lib/dns/resolve.c b/subsys/net/lib/dns/resolve.c index ff17b6b6257..5b9402768da 100644 --- a/subsys/net/lib/dns/resolve.c +++ b/subsys/net/lib/dns/resolve.c @@ -27,6 +27,7 @@ LOG_MODULE_REGISTER(net_dns_resolve, CONFIG_DNS_RESOLVER_LOG_LEVEL); #include #include #include +#include "../../ip/net_private.h" #include "dns_pack.h" #include "dns_internal.h" #include "dns_cache.h" @@ -319,6 +320,31 @@ static int register_dispatcher(struct dns_resolve_context *ctx, return dns_dispatcher_register(&server->dispatcher); } +static int bind_to_iface(int sock, const struct sockaddr *addr, int if_index) +{ + struct ifreq ifreq = { 0 }; + int ret; + + ret = net_if_get_name(net_if_get_by_index(if_index), ifreq.ifr_name, + sizeof(ifreq.ifr_name)); + if (ret < 0) { + LOG_DBG("Cannot get interface name for %d (%d)", if_index, ret); + return ret; + } + + ret = zsock_setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, + &ifreq, sizeof(ifreq)); + if (ret < 0) { + ret = -errno; + + NET_DBG("Cannot bind %s to %d (%d)", + net_sprint_addr(addr->sa_family, &net_sin(addr)->sin_addr), + if_index, ret); + } + + return ret; +} + /* Must be invoked with context lock held */ static int dns_resolve_init_locked(struct dns_resolve_context *ctx, const char *servers[], @@ -363,24 +389,56 @@ static int dns_resolve_init_locked(struct dns_resolve_context *ctx, 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++; + + if (server_len == 0) { + NET_DBG("Empty server name"); + 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); + } + + } else { + server_len = strlen(servers[i]); + ctx->servers[idx].if_index = 0; + } + (void)memset(addr, 0, sizeof(*addr)); - ret = net_ipaddr_parse(servers[i], strlen(servers[i]), - 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", + server_len, servers[i]); + } + continue; } dns_postprocess_server(ctx, idx); - NET_DBG("[%d] %s%s%s", i, servers[i], + NET_DBG("[%d] %.*s%s%s%s%s", i, 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" : "") : ""); + (ctx->servers[i].is_llmnr ? " LLMNR" : "") : "", + iface_str != NULL ? " via " : "", + iface_str != NULL ? iface_str : ""); idx++; } } @@ -441,14 +499,40 @@ static int dns_resolve_init_locked(struct dns_resolve_context *ctx, ctx->servers[i].sock = ret; + /* Try to bind to the interface if it is set */ + if (ctx->servers[i].if_index > 0) { + ret = bind_to_iface(ctx->servers[i].sock, + &ctx->servers[i].dns_server, + ctx->servers[i].if_index); + if (ret < 0) { + zsock_close(ctx->servers[i].sock); + ctx->servers[i].sock = -1; + continue; + } + + iface = net_if_get_by_index(ctx->servers[i].if_index); + NET_DBG("Binding %s to %d", + net_sprint_addr(ctx->servers[i].dns_server.sa_family, + &net_sin(&ctx->servers[i].dns_server)->sin_addr), + ctx->servers[i].if_index); + } else { + iface = NULL; + } + if (ctx->servers[i].dns_server.sa_family == AF_INET6) { - iface = net_if_ipv6_select_src_iface( + if (iface == NULL) { + iface = net_if_ipv6_select_src_iface( &net_sin6(&ctx->servers[i].dns_server)->sin6_addr); + } + addr6 = net_if_ipv6_select_src_addr(iface, &net_sin6(&ctx->servers[i].dns_server)->sin6_addr); } else { - iface = net_if_ipv4_select_src_iface( + if (iface == NULL) { + iface = net_if_ipv4_select_src_iface( &net_sin(&ctx->servers[i].dns_server)->sin_addr); + } + addr4 = net_if_ipv4_select_src_addr(iface, &net_sin(&ctx->servers[i].dns_server)->sin_addr); }