summaryrefslogtreecommitdiff
path: root/resolve_host.c
diff options
context:
space:
mode:
authorDavid Voit <david.voit@gmail.com>2024-04-03 07:24:48 +0200
committerPavel Shilovsky <pshilovsky@samba.org>2024-04-28 00:00:12 +0000
commitc6bf4d9a59809fbb0c22ef9eb167c099ab8089fb (patch)
tree9086346733731671d164c73131f1d3467d7022c6 /resolve_host.c
parent4718e09e4b15b957bf9d729793bc3de7caad8134 (diff)
downloadcifs-utils-for-next.tar.gz
cifs-utils-for-next.tar.bz2
cifs-utils-for-next.zip
Implement CLDAP Ping to find the closest sitefor-next
For domain based DFS we always need to contact the domain controllers. On setups, which are using bigger AD installations you could get random dc on the other side of the world, if you don't have site support. This can lead to network timeouts and other problems. CLDAP-Ping uses ASN.1 + UDP (CLDAP) and custom-DCE encoding including DName compressions without field separation. Finally after finding the sitename we now need to do a DNS SRV lookups to find the correct IPs to our closest site and fill up the remaining IPs from the global list. Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.com> Reviewed-by: Jacob Shivers <jshivers@redhat.com> Signed-off-by: David Voit <david.voit@gmail.com>
Diffstat (limited to 'resolve_host.c')
-rw-r--r--resolve_host.c256
1 files changed, 228 insertions, 28 deletions
diff --git a/resolve_host.c b/resolve_host.c
index 17cbd10..fc682e5 100644
--- a/resolve_host.c
+++ b/resolve_host.c
@@ -3,6 +3,7 @@
*
* Copyright (C) 2010 Jeff Layton (jlayton@samba.org)
* Copyright (C) 2010 Igor Druzhinin (jaxbrigs@gmail.com)
+ * Copyright (C) 2024 David Voit (david.voit@gmail.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,15 +28,16 @@
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
+#include <resolv.h>
#include "mount.h"
#include "util.h"
+#include "cldap_ping.h"
#include "resolve_host.h"
/*
* resolve hostname to comma-separated list of address(es)
*/
-int resolve_host(const char *host, char *addrstr)
-{
+int resolve_host(const char *host, char *addrstr) {
int rc;
/* 10 for max width of decimal scopeid */
char tmpbuf[NI_MAXHOST + 1 + 10 + 1];
@@ -44,6 +46,7 @@ int resolve_host(const char *host, char *addrstr)
struct addrinfo *addrlist, *addr;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
+ size_t count_v4 = 0, count_v6 = 0;
rc = getaddrinfo(host, NULL, NULL, &addrlist);
if (rc != 0)
@@ -59,34 +62,45 @@ int resolve_host(const char *host, char *addrstr)
}
switch (addr->ai_addr->sa_family) {
- case AF_INET6:
- sin6 = (struct sockaddr_in6 *)addr->ai_addr;
- ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf,
- sizeof(tmpbuf));
- if (!ipaddr) {
- rc = EX_SYSERR;
- goto resolve_host_out;
- }
+ case AF_INET6:
+ count_v6++;
+ if (count_v6 + count_v4 > MAX_ADDRESSES) {
+ addr = addr->ai_next;
+ continue;
+ }
- if (sin6->sin6_scope_id) {
- len = strnlen(tmpbuf, sizeof(tmpbuf));
- snprintf(tmpbuf + len, sizeof(tmpbuf) - len, "%%%u",
- sin6->sin6_scope_id);
- }
- break;
- case AF_INET:
- sin = (struct sockaddr_in *)addr->ai_addr;
- ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf,
- sizeof(tmpbuf));
- if (!ipaddr) {
- rc = EX_SYSERR;
- goto resolve_host_out;
- }
+ sin6 = (struct sockaddr_in6 *) addr->ai_addr;
+ ipaddr = inet_ntop(AF_INET6, &sin6->sin6_addr, tmpbuf,
+ sizeof(tmpbuf));
+ if (!ipaddr) {
+ rc = EX_SYSERR;
+ goto resolve_host_out;
+ }
- break;
- default:
- addr = addr->ai_next;
- continue;
+
+ if (sin6->sin6_scope_id) {
+ len = strnlen(tmpbuf, sizeof(tmpbuf));
+ snprintf(tmpbuf + len, sizeof(tmpbuf) - len, "%%%u",
+ sin6->sin6_scope_id);
+ }
+ break;
+ case AF_INET:
+ count_v4++;
+ if (count_v6 + count_v4 > MAX_ADDRESSES) {
+ addr = addr->ai_next;
+ continue;
+ }
+ sin = (struct sockaddr_in *) addr->ai_addr;
+ ipaddr = inet_ntop(AF_INET, &sin->sin_addr, tmpbuf,
+ sizeof(tmpbuf));
+ if (!ipaddr) {
+ rc = EX_SYSERR;
+ goto resolve_host_out;
+ }
+ break;
+ default:
+ addr = addr->ai_next;
+ continue;
}
if (addr == addrlist)
@@ -98,6 +112,192 @@ int resolve_host(const char *host, char *addrstr)
addr = addr->ai_next;
}
+
+ // Is this a DFS domain where we need to do a cldap ping to find the closest node?
+ if (count_v4 > 1 || count_v6 > 1) {
+ int res;
+ ns_msg global_domain_handle;
+ unsigned char global_domain_lookup[4096];
+ ns_msg site_domain_handle;
+ unsigned char site_domain_lookup[4096];
+ char dname[MAXCDNAME];
+ int srv_cnt;
+
+ res = res_init();
+ if (res != 0)
+ goto resolve_host_out;
+
+ res = snprintf(dname, MAXCDNAME, "_ldap._tcp.dc._msdcs.%s", host);
+ if (res < 0)
+ goto resolve_host_out;
+
+ res = res_query(dname, C_IN, ns_t_srv, global_domain_lookup, sizeof(global_domain_lookup));
+ if (res < 0)
+ goto resolve_host_out;
+
+ // res is also the size of the response_buffer
+ res = ns_initparse(global_domain_lookup, res, &global_domain_handle);
+ if (res < 0)
+ goto resolve_host_out;
+
+ srv_cnt = ns_msg_count (global_domain_handle, ns_s_an);
+
+ // No or just one DC we are done
+ if (srv_cnt < 2)
+ goto resolve_host_out;
+
+ char site_name[MAXCDNAME];
+ // We assume that AD always sends the ip addresses in the addtional data block
+ for (int i = 0; i < ns_msg_count(global_domain_handle, ns_s_ar); i++) {
+ ns_rr rr;
+ res = ns_parserr(&global_domain_handle, ns_s_ar, i, &rr);
+ if (res < 0)
+ goto resolve_host_out;
+
+ switch (ns_rr_type(rr)) {
+ case ns_t_aaaa:
+ if (ns_rr_rdlen(rr) != NS_IN6ADDRSZ)
+ continue;
+ res = cldap_ping((char *) host, AF_INET6, (void *)ns_rr_rdata(rr), site_name);
+ break;
+ case ns_t_a:
+ if (ns_rr_rdlen(rr) != NS_INADDRSZ)
+ continue;
+ res = cldap_ping((char *) host, AF_INET, (void *)ns_rr_rdata(rr), site_name);
+ break;
+ default:
+ continue;
+ }
+
+ if (res == CLDAP_PING_TRYNEXT) {
+ continue;
+ }
+
+ if (res < 0) {
+ goto resolve_host_out;
+ }
+
+ if (site_name[0] == '\0') {
+ goto resolve_host_out;
+ } else {
+ // site found - leave loop
+ break;
+ }
+ }
+
+ res = snprintf(dname, MAXCDNAME, "_ldap._tcp.%s._sites.dc._msdcs.%s", site_name, host);
+ if (res < 0) {
+ goto resolve_host_out;
+ }
+
+ res = res_query(dname, C_IN, ns_t_srv, site_domain_lookup, sizeof(site_domain_lookup));
+ if (res < 0)
+ goto resolve_host_out;
+
+ // res is also the size of the response_buffer
+ res = ns_initparse(site_domain_lookup, res, &site_domain_handle);
+ if (res < 0)
+ goto resolve_host_out;
+
+ int number_addresses = 0;
+ for (int i = 0; i < ns_msg_count(site_domain_handle, ns_s_ar); i++) {
+ if (i > MAX_ADDRESSES)
+ break;
+
+ ns_rr rr;
+ res = ns_parserr(&site_domain_handle, ns_s_ar, i, &rr);
+ if (res < 0)
+ goto resolve_host_out;
+
+ switch (ns_rr_type(rr)) {
+ case ns_t_aaaa:
+ if (ns_rr_rdlen(rr) != NS_IN6ADDRSZ)
+ continue;
+ ipaddr = inet_ntop(AF_INET6, ns_rr_rdata(rr), tmpbuf,
+ sizeof(tmpbuf));
+ if (!ipaddr) {
+ rc = EX_SYSERR;
+ goto resolve_host_out;
+ }
+ break;
+ case ns_t_a:
+ if (ns_rr_rdlen(rr) != NS_INADDRSZ)
+ continue;
+ ipaddr = inet_ntop(AF_INET, ns_rr_rdata(rr), tmpbuf,
+ sizeof(tmpbuf));
+ if (!ipaddr) {
+ rc = EX_SYSERR;
+ goto resolve_host_out;
+ }
+ break;
+ default:
+ continue;
+ }
+
+ number_addresses++;
+
+ if (i == 0)
+ *addrstr = '\0';
+ else
+ strlcat(addrstr, ",", MAX_ADDR_LIST_LEN);
+
+ strlcat(addrstr, tmpbuf, MAX_ADDR_LIST_LEN);
+ }
+
+ // Preferred site ips is now the first entry in addrstr, fill up with other sites till MAX_ADDRESS
+ for (int i = 0; i < ns_msg_count(global_domain_handle, ns_s_ar); i++) {
+ if (number_addresses > MAX_ADDRESSES)
+ break;
+
+ ns_rr rr;
+ res = ns_parserr(&global_domain_handle, ns_s_ar, i, &rr);
+ if (res < 0)
+ goto resolve_host_out;
+
+ switch (ns_rr_type(rr)) {
+ case ns_t_aaaa:
+ if (ns_rr_rdlen(rr) != NS_IN6ADDRSZ)
+ continue;
+ ipaddr = inet_ntop(AF_INET6, ns_rr_rdata(rr), tmpbuf,
+ sizeof(tmpbuf));
+ if (!ipaddr) {
+ rc = EX_SYSERR;
+ goto resolve_host_out;
+ }
+ break;
+ case ns_t_a:
+ if (ns_rr_rdlen(rr) != NS_INADDRSZ)
+ continue;
+ ipaddr = inet_ntop(AF_INET, ns_rr_rdata(rr), tmpbuf,
+ sizeof(tmpbuf));
+ if (!ipaddr) {
+ rc = EX_SYSERR;
+ goto resolve_host_out;
+ }
+ break;
+ default:
+ continue;
+ }
+
+ char *found = strstr(addrstr, tmpbuf);
+
+ if (found) {
+ // We only have a real match if the substring is between ',' or it's the last/first entry in the list
+ char previous_seperator = found > addrstr ? *(found-1) : '\0';
+ char next_seperator = *(found+strlen(tmpbuf));
+
+ if ((next_seperator == ',' || next_seperator == '\0')
+ && (previous_seperator == ',' || previous_seperator == '\0')) {
+ continue;
+ }
+ }
+
+ number_addresses++;
+ strlcat(addrstr, ",", MAX_ADDR_LIST_LEN);
+ strlcat(addrstr, tmpbuf, MAX_ADDR_LIST_LEN);
+ }
+ }
+
resolve_host_out:
freeaddrinfo(addrlist);
return rc;