]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: ring: add support for a backing-file
authorWilly Tarreau <w@1wt.eu>
Thu, 11 Aug 2022 14:38:20 +0000 (16:38 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 12 Aug 2022 09:18:46 +0000 (11:18 +0200)
This mmaps a file which will serve as the backing-store for the ring's
contents. The idea is to provide a way to retrieve sensitive information
(last logs, debugging traces) even after the process stops and even after
a possible crash. Right now this was possible by connecting to the CLI
and dumping the contents of the ring live, but this is not handy and
consumes quite a bit of resources before it is needed.

With a backing file, the ring is effectively RAM-mapped file, so that
contents stored there are the same as those found in the file (the OS
doesn't guarantee immediate sync but if the process dies it will be OK).

Note that doing that on a filesystem backed by a physical device is a
bad idea, as it will induce slowdowns at high loads. It's really
important that the device is RAM-based.

Also, this may have security implications: if the file is corrupted by
another process, the storage area could be corrupted, causing haproxy
to crash or to overwrite its own memory. As such this should only be
used for debugging.

doc/configuration.txt
include/haproxy/sink-t.h
src/sink.c

index 6d1917c44149c7bc954478dcb93951b34c84a81b..678d0af32d91c1405af46efdf40ce2cb4974f4f7 100644 (file)
@@ -3580,6 +3580,33 @@ servers or traces.
 ring <ringname>
   Creates a new ring-buffer with name <ringname>.
 
+backing-file <path>
+  This replaces the regular memory allocation by a RAM-mapped file to store the
+  ring. This can be useful for collecting traces or logs for post-mortem
+  analysis, without having to attach a slow client to the CLI. Newer contents
+  will automatically replace older ones so that the latest contents are always
+  available. The contents written to the ring will be visible in that file once
+  the process stops (most often they will even be seen very soon after but
+  there is no such guarantee since writes are not synchronous).
+
+  When this option is used, the total storage area is reduced by the size of
+  the "struct ring" that starts at the beginning of the area, and that is
+  required to recover the area's contents. The file will be created with the
+  starting user's ownership, with mode 0600 and will be of the size configured
+  by the "size" directive.
+
+  WARNING: there are stability and security implications in using this feature.
+  First, backing the ring to a slow device (e.g. physical hard drive) may cause
+  perceptible slowdowns during accesses, and possibly even panics if too many
+  threads compete for accesses. Second, an external process modifying the area
+  could cause the haproxy process to crash or to overwrite some of its own
+  memory with traces. Third, if the file system fills up before the ring,
+  writes to the ring may cause the process to crash.
+
+  The information present in this ring are structured and are NOT directly
+  readable using a text editor (even though most of it looks barely readable).
+  The output of this file is only intended for developers.
+
 description <text>
   The description is an optional description string of the ring. It will
   appear on CLI. By default, <name> is reused to fill this field.
index a0de3d15a1559b43c9b812c192de1887413e7cf2..b5138c85da810f0af07f3a0cb917f19644e461a9 100644 (file)
@@ -50,6 +50,7 @@ struct sink {
        struct list sink_list;     // position in the sink list
        char *name;                // sink name
        char *desc;                // sink description
+       char *store;               // backing-store file when buffer
        enum log_fmt fmt;          // format expected by the sink
        enum sink_type type;       // type of storage
        uint32_t maxlen;           // max message length (truncated above)
index 04bc8c87bd35710ac03b7e7afc09181971779e34..5432c4db7616e83aa92ca424a94221fc2a78bcba 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
+#include <sys/mman.h>
+#include <errno.h>
+#include <fcntl.h>
+
 #include <import/ist.h>
 #include <haproxy/api.h>
 #include <haproxy/applet.h>
@@ -817,6 +821,12 @@ int cfg_parse_ring(const char *file, int linenum, char **args, int kwm)
                        goto err;
                }
 
+               if (cfg_sink->store) {
+                       ha_alert("parsing [%s:%d] : cannot resize an already mapped file, please specify 'size' before 'backing-file'.\n", file, linenum);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto err;
+               }
+
                if (size < cfg_sink->ctx.ring->buf.size) {
                        ha_warning("parsing [%s:%d] : ignoring new size '%llu' that is smaller than current size '%llu' for ring '%s'.\n",
                                   file, linenum, (ullong)size, (ullong)cfg_sink->ctx.ring->buf.size, cfg_sink->name);
@@ -831,6 +841,58 @@ int cfg_parse_ring(const char *file, int linenum, char **args, int kwm)
                        goto err;
                }
        }
+       else if (strcmp(args[0], "backing-file") == 0) {
+               /* This tries to mmap file <file> for size <size> and to use it as a backing store
+                * for ring <ring>. Existing data are delete. NULL is returned on error.
+                */
+               const char *backing = args[1];
+               size_t size;
+               void *area;
+               int fd;
+
+               if (!cfg_sink || (cfg_sink->type != SINK_TYPE_BUFFER)) {
+                       ha_alert("parsing [%s:%d] : 'backing-file' only usable with existing rings.\n", file, linenum);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto err;
+               }
+
+               if (cfg_sink->store) {
+                       ha_alert("parsing [%s:%d] : 'backing-file' already specified for ring '%s' (was '%s').\n", file, linenum, cfg_sink->name, cfg_sink->store);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto err;
+               }
+
+               fd = open(backing, O_RDWR | O_CREAT, S_IRUSR|S_IWUSR);
+               if (fd < 0) {
+                       ha_alert("parsing [%s:%d] : cannot open backing-file '%s' for ring '%s': %s.\n", file, linenum, backing, cfg_sink->name, strerror(errno));
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto err;
+               }
+
+               size = (cfg_sink->ctx.ring->buf.size + 4095UL) & -4096UL;
+               if (ftruncate(fd, size) != 0) {
+                       close(fd);
+                       ha_alert("parsing [%s:%d] : could not adjust size of backing-file for ring '%s': %s.\n", file, linenum, cfg_sink->name, strerror(errno));
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto err;
+               }
+
+               area = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+               if (area == MAP_FAILED) {
+                       close(fd);
+                       ha_alert("parsing [%s:%d] : failed to use '%s' as a backing file for ring '%s': %s.\n", file, linenum, backing, cfg_sink->name, strerror(errno));
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto err;
+               }
+
+               /* we don't need the file anymore */
+               close(fd);
+               cfg_sink->store = strdup(backing);
+
+               /* never fails */
+               ring_free(cfg_sink->ctx.ring);
+               cfg_sink->ctx.ring = ring_make_from_area(area, size);
+       }
        else if (strcmp(args[0],"server") == 0) {
                err_code |= parse_server(file, linenum, args, cfg_sink->forward_px, NULL,
                                         SRV_PARSE_PARSE_ADDR|SRV_PARSE_INITIAL_RESOLVE);
@@ -1267,8 +1329,12 @@ static void sink_deinit()
        struct sink *sink, *sb;
 
        list_for_each_entry_safe(sink, sb, &sink_list, sink_list) {
-               if (sink->type == SINK_TYPE_BUFFER)
-                       ring_free(sink->ctx.ring);
+               if (sink->type == SINK_TYPE_BUFFER) {
+                       if (sink->store)
+                               munmap(sink->ctx.ring->buf.area, sink->ctx.ring->buf.size);
+                       else
+                               ring_free(sink->ctx.ring);
+               }
                LIST_DELETE(&sink->sink_list);
                free(sink->name);
                free(sink->desc);