diff mbox series

[7/7] mountd: add logging of NFSv4 clients attaching and detaching.

Message ID 161490500401.15291.5928922406076481746.stgit@noble (mailing list archive)
State New
Headers show
Series nfs-utils: provide audit-logging of NFSv4 access | expand

Commit Message

NeilBrown March 5, 2021, 12:43 a.m. UTC
NFSv4 does not have a MOUNT request like NFSv3 does (via the MOUNT
protocol).  So these cannot be logged.
NFSv4 does have SETCLIENTID and EXCHANGE_ID.  These are indirectly
visible though changes in /proc/fs/nfsd/clients.
When a new client attaches, a directory appears.  When the client
detaches, through a timeout (v4.0) or DESTROY_SESSION (v4.1+)
the directory disappears.

This patch adds tracking of these changes using inotify, with log
messages when a client attaches or detaches.

Unfortuantely clients are created in two steps, the second being a
confirmation.  This results in a temporary client appearing and
disappearing.  It is not possible (in Linux 5.10) to detect the
unconfirmed client, so extra attach/detach messages are generated.

This patch also moves some cache* function declarations into a header
file, and makes a few related changes to #includes.

Signed-off-by: NeilBrown <neilb@suse.de>
---
 support/export/Makefile.am |    3 -
 support/export/cache.c     |    9 --
 support/export/export.h    |    9 ++
 support/export/v4clients.c |  177 ++++++++++++++++++++++++++++++++++++++++++++
 utils/exportd/exportd.c    |    1 
 utils/mountd/mountd.c      |    2 
 utils/mountd/mountd.h      |    5 -
 utils/mountd/svc_run.c     |    5 +
 8 files changed, 195 insertions(+), 16 deletions(-)
 create mode 100644 support/export/v4clients.c
diff mbox series

Patch

diff --git a/support/export/Makefile.am b/support/export/Makefile.am
index a9e710c00ae8..eec737f66149 100644
--- a/support/export/Makefile.am
+++ b/support/export/Makefile.am
@@ -12,7 +12,8 @@  EXTRA_DIST	= mount.x
 noinst_LIBRARIES = libexport.a
 libexport_a_SOURCES = client.c export.c hostname.c \
 		      xtab.c mount_clnt.c mount_xdr.c \
-			  cache.c auth.c v4root.c fsloc.c
+		      cache.c auth.c v4root.c fsloc.c \
+		      v4clients.c
 BUILT_SOURCES 	= $(GENFILES)
 
 noinst_HEADERS = mount.h
diff --git a/support/export/cache.c b/support/export/cache.c
index c0848c3e437b..3e4f53c0a32e 100644
--- a/support/export/cache.c
+++ b/support/export/cache.c
@@ -42,13 +42,6 @@ 
 #include "blkid/blkid.h"
 #endif
 
-/*
- * Invoked by RPC service loop
- */
-void	cache_set_fds(fd_set *fdset);
-int	cache_process_req(fd_set *readfds);
-void cache_process_loop(void);
-
 enum nfsd_fsid {
 	FSID_DEV = 0,
 	FSID_NUM,
@@ -1537,6 +1530,7 @@  void cache_process_loop(void)
 	for (;;) {
 
 		cache_set_fds(&readfds);
+		v4clients_set_fds(&readfds);
 
 		selret = select(FD_SETSIZE, &readfds,
 				(void *) 0, (void *) 0, (struct timeval *) 0);
@@ -1552,6 +1546,7 @@  void cache_process_loop(void)
 
 		default:
 			cache_process_req(&readfds);
+			v4clients_process(&readfds);
 		}
 	}
 }
diff --git a/support/export/export.h b/support/export/export.h
index 4296db1ad9b1..8d5a0d3004ef 100644
--- a/support/export/export.h
+++ b/support/export/export.h
@@ -3,13 +3,14 @@ 
  *
  * support/export/export.h
  *
- * Declarations for export support 
+ * Declarations for export support
  */
 
 #ifndef EXPORT_H
 #define EXPORT_H
 
 #include "nfslib.h"
+#include "exportfs.h"
 
 unsigned int	auth_reload(void);
 nfs_export *	auth_authenticate(const char *what,
@@ -17,8 +18,14 @@  nfs_export *	auth_authenticate(const char *what,
 					const char *path);
 
 void		cache_open(void);
+void		cache_set_fds(fd_set *fdset);
+int		cache_process_req(fd_set *readfds);
 void		cache_process_loop(void);
 
+void		v4clients_init(void);
+void		v4clients_set_fds(fd_set *fdset);
+int		v4clients_process(fd_set *fdset);
+
 struct nfs_fh_len *
 		cache_get_filehandle(nfs_export *exp, int len, char *p);
 int		cache_export(nfs_export *exp, char *path);
diff --git a/support/export/v4clients.c b/support/export/v4clients.c
new file mode 100644
index 000000000000..ab1454e4c34e
--- /dev/null
+++ b/support/export/v4clients.c
@@ -0,0 +1,177 @@ 
+/*
+ * support/export/v4clients.c
+ *
+ * Montior clients appearing in, and disappearing from, /proc/fs/nfsd/clients
+ * and log relevant information.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/inotify.h>
+#include <errno.h>
+#include "export.h"
+
+/* search.h declares 'struct entry' and nfs_prot.h
+ * does too.  Easiest fix is to trick search.h into
+ * calling its struct "struct Entry".
+ */
+#define entry Entry
+#include <search.h>
+#undef entry
+
+static int clients_fd = -1;
+
+void v4clients_init(void)
+{
+	if (clients_fd >= 0)
+		return;
+	clients_fd = inotify_init1(IN_NONBLOCK);
+	if (clients_fd < 0) {
+		xlog_err("Unable to initialise v4clients watcher: %s\n",
+			 strerror(errno));
+		return;
+	}
+	if (inotify_add_watch(clients_fd, "/proc/fs/nfsd/clients",
+			      IN_CREATE | IN_DELETE) < 0) {
+		xlog_err("Unable to watch /proc/fs/nfsd/clients: %s\n",
+			 strerror(errno));
+		close(clients_fd);
+		clients_fd = -1;
+		return;
+	}
+}
+
+void v4clients_set_fds(fd_set *fdset)
+{
+	if (clients_fd >= 0)
+		FD_SET(clients_fd, fdset);
+}
+
+static void *tree_root;
+
+struct ent {
+	unsigned long num;
+	char *clientid;
+	char *addr;
+	int vers;
+};
+
+static int ent_cmp(const void *av, const void *bv)
+{
+	const struct ent *a = av;
+	const struct ent *b = bv;
+
+	if (a->num < b->num)
+		return -1;
+	if (a->num > b->num)
+		return 1;
+	return 0;
+}
+
+static void free_ent(struct ent *ent)
+{
+	free(ent->clientid);
+	free(ent->addr);
+	free(ent);
+}
+
+static char *dup_line(char *line)
+{
+	char *ret;
+	char *e = strchr(line, '\n');
+	if (!e)
+		e = line + strlen(line);
+	ret = malloc(e - line + 1);
+	if (ret) {
+		memcpy(ret, line, e - line);
+		ret[e-line] = 0;
+	}
+	return ret;
+}
+
+static void add_id(int id)
+{
+	char buf[2048];
+	struct ent **ent;
+	struct ent *key;
+	char *path;
+	FILE *f;
+
+	asprintf(&path, "/proc/fs/nfsd/clients/%d/info", id);
+	f = fopen(path, "r");
+	if (!f) {
+		free(path);
+		return;
+	}
+	key = calloc(1, sizeof(*key));
+	if (!key) {
+		fclose(f);
+		free(path);
+		return;
+	}
+	key->num = id;
+	while (fgets(buf, sizeof(buf), f)) {
+		if (strncmp(buf, "clientid: ", 10) == 0)
+			key->clientid = dup_line(buf+10);
+		if (strncmp(buf, "address: ", 9) == 0)
+			key->addr = dup_line(buf+9);
+		if (strncmp(buf, "minor version: ", 15) == 0)
+			key->vers = atoi(buf+15);
+	}
+	fclose(f);
+	free(path);
+
+	xlog(L_NOTICE, "v4.%d client attached: %s from %s",
+	     key->vers, key->clientid, key->addr);
+
+	ent = tsearch(key, &tree_root, ent_cmp);
+
+	if (!ent || *ent != key)
+		/* Already existed, or insertion failed */
+		free_ent(key);
+}
+
+static void del_id(int id)
+{
+	struct ent key = {.num = id};
+	struct ent **e, *ent;
+
+	e = tfind(&key, &tree_root, ent_cmp);
+	if (!e || !*e)
+		return;
+	ent = *e;
+	tdelete(ent, &tree_root, ent_cmp);
+	xlog(L_NOTICE, "v4.%d client detached: %s from %s",
+	     ent->vers, ent->clientid, ent->addr);
+	free_ent(ent);
+}
+
+int v4clients_process(fd_set *fdset)
+{
+	char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
+	const struct inotify_event *ev;
+	ssize_t len;
+	char *ptr;
+
+	if (clients_fd < 0 ||
+	    !FD_ISSET(clients_fd, fdset))
+		return 0;
+
+	while ((len = read(clients_fd, buf, sizeof(buf))) > 0) {
+		for (ptr = buf; ptr < buf + len;
+		     ptr += sizeof(struct inotify_event) + ev->len) {
+			int id;
+			ev = (const struct inotify_event *)ptr;
+
+			id = atoi(ev->name);
+			if (id <= 0)
+				continue;
+			if (ev->mask & IN_CREATE)
+				add_id(id);
+			if (ev->mask & IN_DELETE)
+				del_id(id);
+		}
+	}
+	return 1;
+}
+
diff --git a/utils/exportd/exportd.c b/utils/exportd/exportd.c
index 76aad97375dc..25c41be6bc77 100644
--- a/utils/exportd/exportd.c
+++ b/utils/exportd/exportd.c
@@ -297,6 +297,7 @@  main(int argc, char **argv)
 
 	/* Open files now to avoid sharing descriptors among forked processes */
 	cache_open();
+	v4clients_init();
 
 	/* Process incoming upcalls */
 	cache_process_loop();
diff --git a/utils/mountd/mountd.c b/utils/mountd/mountd.c
index fce389661e7a..39e85fd53a87 100644
--- a/utils/mountd/mountd.c
+++ b/utils/mountd/mountd.c
@@ -31,6 +31,7 @@ 
 #include "pseudoflavors.h"
 #include "nfsd_path.h"
 #include "nfslib.h"
+#include "export.h"
 
 extern void my_svc_run(void);
 
@@ -924,6 +925,7 @@  main(int argc, char **argv)
 	nfsd_path_init();
 	/* Open files now to avoid sharing descriptors among forked processes */
 	cache_open();
+	v4clients_init();
 
 	xlog(L_NOTICE, "Version " VERSION " starting");
 	my_svc_run();
diff --git a/utils/mountd/mountd.h b/utils/mountd/mountd.h
index f058f01d3584..d30775313f66 100644
--- a/utils/mountd/mountd.h
+++ b/utils/mountd/mountd.h
@@ -60,9 +60,4 @@  bool ipaddr_client_matches(nfs_export *exp, struct addrinfo *ai);
 bool namelist_client_matches(nfs_export *exp, char *dom);
 bool client_matches(nfs_export *exp, char *dom, struct addrinfo *ai);
 
-static inline bool is_ipaddr_client(char *dom)
-{
-	return dom[0] == '$';
-}
-
 #endif /* MOUNTD_H */
diff --git a/utils/mountd/svc_run.c b/utils/mountd/svc_run.c
index 41b96d7fd1c3..167b9757bde2 100644
--- a/utils/mountd/svc_run.c
+++ b/utils/mountd/svc_run.c
@@ -56,10 +56,9 @@ 
 #ifdef HAVE_LIBTIRPC
 #include <rpc/rpc_com.h>
 #endif
+#include "export.h"
 
 void my_svc_run(void);
-void cache_set_fds(fd_set *fdset);
-int cache_process_req(fd_set *readfds);
 
 #if defined(__GLIBC__) && LONG_MAX != INT_MAX
 /* bug in glibc 2.3.6 and earlier, we need
@@ -101,6 +100,7 @@  my_svc_run(void)
 
 		readfds = svc_fdset;
 		cache_set_fds(&readfds);
+		v4clients_set_fds(&readfds);
 
 		selret = select(FD_SETSIZE, &readfds,
 				(void *) 0, (void *) 0, (struct timeval *) 0);
@@ -116,6 +116,7 @@  my_svc_run(void)
 
 		default:
 			selret -= cache_process_req(&readfds);
+			selret -= v4clients_process(&readfds);
 			if (selret)
 				svc_getreqset(&readfds);
 		}