summaryrefslogtreecommitdiffstats
path: root/libnetdata
diff options
context:
space:
mode:
authorAndrew Moss <1043609+amoss@users.noreply.github.com>2019-10-24 20:44:56 +0200
committerGitHub <noreply@github.com>2019-10-24 20:44:56 +0200
commit01aaa909393a48d9db83c22dc95fffdd1cc074c9 (patch)
tree5ee12b67a8a570c3c5f009243cde336e4650c974 /libnetdata
parent88f966593abc5c7888e7c0be83780a97d4326ac2 (diff)
Fixing DNS-lookup performance issue on FreeBSD. (#7132)
Our default configuration includes: allow connections from = localhost * allow management from = localhost The problem occurs when a connection is received that passes the `allow connections` pattern match, but fails the ACL check for `allow management`. During the failure processing path the DNS lookup is triggered to allow the FQDN to be checked against the pattern. On a FreeBSD system this lookup fails more slowly than linux and causes a visible performance problem during stress-testing. The fix adds a heuristic to analyse the patterns and determine if it is possible to match a DNS name, or only match a numeric IP address (either IPv4 or IPv6), or only match a constant value. This heuristic is used to disable the DNS checks when they cannot produce anything that may match the pattern. Each heuristic is evaluated once, when the configuration is loaded, not per-connection to the agent. Because the heuristic is not exact it can be overridden using the new config options for each of the ACL connection filters to set it to "yes", "no" or "heuristic". The default for everything *except* the netdata.conf ACL is "heuristic". Because of the numeric-patterns in the netdata.conf ACL the default is set to "no".
Diffstat (limited to 'libnetdata')
-rw-r--r--libnetdata/simple_pattern/simple_pattern.c71
-rw-r--r--libnetdata/simple_pattern/simple_pattern.h3
-rw-r--r--libnetdata/socket/socket.c24
-rw-r--r--libnetdata/socket/socket.h6
4 files changed, 92 insertions, 12 deletions
diff --git a/libnetdata/simple_pattern/simple_pattern.c b/libnetdata/simple_pattern/simple_pattern.c
index 57b0aecc82..f5175a796c 100644
--- a/libnetdata/simple_pattern/simple_pattern.c
+++ b/libnetdata/simple_pattern/simple_pattern.c
@@ -260,3 +260,74 @@ void simple_pattern_free(SIMPLE_PATTERN *list) {
free_pattern(((struct simple_pattern *)list));
}
+
+/* Debugging patterns
+
+ This code should be dead - it is useful for debugging but should not be called by production code.
+ Feel free to comment it out, but please leave it in the file.
+*/
+extern void simple_pattern_dump(uint64_t debug_type, SIMPLE_PATTERN *p)
+{
+ struct simple_pattern *root = (struct simple_pattern *)p;
+ if(root==NULL) {
+ debug(debug_type,"dump_pattern(NULL)");
+ return;
+ }
+ debug(debug_type,"dump_pattern(%p) child=%p next=%p mode=%d match=%s", root, root->child, root->next, root->mode,
+ root->match);
+ if(root->child!=NULL)
+ simple_pattern_dump(debug_type, (SIMPLE_PATTERN*)root->child);
+ if(root->next!=NULL)
+ simple_pattern_dump(debug_type, (SIMPLE_PATTERN*)root->next);
+}
+
+/* Heuristic: decide if the pattern could match a DNS name.
+
+ Although this functionality is used directly by socket.c:connection_allowed() it must be in this file
+ because of the SIMPLE_PATTERN/simple_pattern structure hiding.
+ Based on RFC952 / RFC1123. We need to decide if the pattern may match a DNS name, or not. For the negative
+ cases we need to be sure that it can only match an ipv4 or ipv6 address:
+ * IPv6 addresses contain ':', which are illegal characters in DNS.
+ * IPv4 addresses cannot contain alpha- characters.
+ * DNS TLDs must be alphanumeric to distinguish from IPv4.
+ Some patterns (e.g. "*a*" ) could match multiple cases (i.e. DNS or IPv6).
+ Some patterns will be awkward (e.g. "192.168.*") as they look like they are intended to match IPv4-only
+ but could match DNS (i.e. "192.168.com" is a valid name).
+*/
+static void scan_is_potential_name(struct simple_pattern *p, int *alpha, int *colon, int *wildcards)
+{
+ while (p) {
+ if (p->match) {
+ if(p->mode == SIMPLE_PATTERN_EXACT && !strcmp("localhost", p->match)) {
+ p = p->child;
+ continue;
+ }
+ char const *scan = p->match;
+ while (*scan != 0) {
+ if ((*scan >= 'a' && *scan <= 'z') || (*scan >= 'A' && *scan <= 'Z'))
+ *alpha = 1;
+ if (*scan == ':')
+ *colon = 1;
+ scan++;
+ }
+ if (p->mode != SIMPLE_PATTERN_EXACT)
+ *wildcards = 1;
+ p = p->child;
+ }
+ }
+}
+
+extern int simple_pattern_is_potential_name(SIMPLE_PATTERN *p)
+{
+ int alpha=0, colon=0, wildcards=0;
+ struct simple_pattern *root = (struct simple_pattern*)p;
+ while (root != NULL) {
+ if (root->match != NULL) {
+ scan_is_potential_name(root, &alpha, &colon, &wildcards);
+ }
+ if (root->mode != SIMPLE_PATTERN_EXACT)
+ wildcards = 1;
+ root = root->next;
+ }
+ return (alpha || wildcards) && !colon;
+}
diff --git a/libnetdata/simple_pattern/simple_pattern.h b/libnetdata/simple_pattern/simple_pattern.h
index b96a018efe..cb5e7699dd 100644
--- a/libnetdata/simple_pattern/simple_pattern.h
+++ b/libnetdata/simple_pattern/simple_pattern.h
@@ -30,4 +30,7 @@ extern int simple_pattern_matches_extract(SIMPLE_PATTERN *list, const char *str,
// list can be NULL, in which case, this does nothing.
extern void simple_pattern_free(SIMPLE_PATTERN *list);
+extern void simple_pattern_dump(uint64_t debug_type, SIMPLE_PATTERN *p) ;
+extern int simple_pattern_is_potential_name(SIMPLE_PATTERN *p) ;
+
#endif //NETDATA_SIMPLE_PATTERN_H
diff --git a/libnetdata/socket/socket.c b/libnetdata/socket/socket.c
index fa1414dc0c..2289bf4c46 100644
--- a/libnetdata/socket/socket.c
+++ b/libnetdata/socket/socket.c
@@ -1007,13 +1007,16 @@ int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags) {
* of *writable* bytes (i.e. be aware of the strdup used to compact the pollinfo).
*/
extern int connection_allowed(int fd, char *client_ip, char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list,
- const char *patname) {
+ const char *patname, int allow_dns)
+{
+ debug(D_LISTENER,"checking %s... (allow_dns=%d)", patname, allow_dns);
if (!access_list)
return 1;
if (simple_pattern_matches(access_list, client_ip))
return 1;
// If the hostname is unresolved (and needed) then attempt the DNS lookups.
- if (client_host[0]==0)
+ //if (client_host[0]==0 && simple_pattern_is_potential_name(access_list))
+ if (client_host[0]==0 && allow_dns)
{
struct sockaddr_storage sadr;
socklen_t addrlen = sizeof(sadr);
@@ -1021,8 +1024,8 @@ extern int connection_allowed(int fd, char *client_ip, char *client_host, size_t
if (err != 0 ||
(err = getnameinfo((struct sockaddr *)&sadr, addrlen, client_host, (socklen_t)hostsize,
NULL, 0, NI_NAMEREQD)) != 0) {
- error("Incoming connection on '%s' does not match a numeric pattern, "
- "and host could not be resolved (err=%s)", client_ip, gai_strerror(err));
+ error("Incoming %s on '%s' does not match a numeric pattern, and host could not be resolved (err=%s)",
+ patname, client_ip, gai_strerror(err));
if (hostsize >= 8)
strcpy(client_host,"UNKNOWN");
return 0;
@@ -1074,9 +1077,8 @@ extern int connection_allowed(int fd, char *client_ip, char *client_host, size_t
// --------------------------------------------------------------------------------------------------------------------
// accept_socket() - accept a socket and store client IP and port
-
int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize,
- char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list) {
+ char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list, int allow_dns) {
struct sockaddr_storage sadr;
socklen_t addrlen = sizeof(sadr);
@@ -1088,7 +1090,7 @@ int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *clien
strncpyz(client_ip, "UNKNOWN", ipsize - 1);
strncpyz(client_port, "UNKNOWN", portsize - 1);
}
- if(!strcmp(client_ip, "127.0.0.1") || !strcmp(client_ip, "::1")) {
+ if (!strcmp(client_ip, "127.0.0.1") || !strcmp(client_ip, "::1")) {
strncpy(client_ip, "localhost", ipsize);
client_ip[ipsize - 1] = '\0';
}
@@ -1126,7 +1128,7 @@ int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *clien
debug(D_LISTENER, "New UNKNOWN web client from %s port %s on socket %d.", client_ip, client_port, fd);
break;
}
- if(!connection_allowed(nfd, client_ip, client_host, hostsize, access_list, "connection")) {
+ if (!connection_allowed(nfd, client_ip, client_host, hostsize, access_list, "connection", allow_dns)) {
errno = 0;
error("Permission denied for client '%s', port '%s'", client_ip, client_port);
close(nfd);
@@ -1135,7 +1137,7 @@ int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *clien
}
}
#ifdef HAVE_ACCEPT4
- else if(errno == ENOSYS)
+ else if (errno == ENOSYS)
error("netdata has been compiled with the assumption that the system has the accept4() call, but it is not here. Recompile netdata like this: ./configure --disable-accept4 ...");
#endif
@@ -1444,7 +1446,7 @@ static void poll_events_process(POLLJOB *p, POLLINFO *pi, struct pollfd *pf, sho
debug(D_POLLFD, "POLLFD: LISTENER: calling accept4() slot %zu (fd %d)", i, fd);
nfd = accept_socket(fd, SOCK_NONBLOCK, client_ip, INET6_ADDRSTRLEN, client_port, NI_MAXSERV,
- client_host, NI_MAXHOST, p->access_list);
+ client_host, NI_MAXHOST, p->access_list, p->allow_dns);
if (unlikely(nfd < 0)) {
// accept failed
@@ -1562,6 +1564,7 @@ void poll_events(LISTEN_SOCKETS *sockets
, int (*snd_callback)(POLLINFO * /*pi*/, short int * /*events*/)
, void (*tmr_callback)(void * /*timer_data*/)
, SIMPLE_PATTERN *access_list
+ , int allow_dns
, void *data
, time_t tcp_request_timeout_seconds
, time_t tcp_idle_timeout_seconds
@@ -1592,6 +1595,7 @@ void poll_events(LISTEN_SOCKETS *sockets
.checks_every = (tcp_idle_timeout_seconds / 3) + 1,
.access_list = access_list,
+ .allow_dns = allow_dns,
.timer_milliseconds = timer_milliseconds,
.timer_data = timer_data,
diff --git a/libnetdata/socket/socket.h b/libnetdata/socket/socket.h
index 227f054345..eb09b3f9a7 100644
--- a/libnetdata/socket/socket.h
+++ b/libnetdata/socket/socket.h
@@ -73,9 +73,9 @@ extern int sock_enlarge_in(int fd);
extern int sock_enlarge_out(int fd);
extern int connection_allowed(int fd, char *client_ip, char *client_host, size_t hostsize,
- SIMPLE_PATTERN *access_list, const char *patname);
+ SIMPLE_PATTERN *access_list, const char *patname, int allow_dns);
extern int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *client_port, size_t portsize,
- char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list);
+ char *client_host, size_t hostsize, SIMPLE_PATTERN *access_list, int allow_dns);
#ifndef HAVE_ACCEPT4
extern int accept4(int sock, struct sockaddr *addr, socklen_t *addrlen, int flags);
@@ -155,6 +155,7 @@ struct poll {
struct pollinfo *first_free;
SIMPLE_PATTERN *access_list;
+ int allow_dns;
void *(*add_callback)(POLLINFO *pi, short int *events, void *data);
void (*del_callback)(POLLINFO *pi);
@@ -193,6 +194,7 @@ extern void poll_events(LISTEN_SOCKETS *sockets
, int (*snd_callback)(POLLINFO *pi, short int *events)
, void (*tmr_callback)(void *timer_data)
, SIMPLE_PATTERN *access_list
+ , int allow_dns
, void *data
, time_t tcp_request_timeout_seconds
, time_t tcp_idle_timeout_seconds