Dear all, Claudio,

A series of RRDP outages around the world prompted me to study whether
the subsequent (perhaps even first-ever) RSYNC synchronization can be
optimised for both client and rsync server.

In the RSYNC protocol a file's last modification time and its size are
used to determine whether sending a copy over the wire is needed. When
RRDP data structures are serialized to disk, the mtime of the files in
DIR_VALID ends up being UTIME_NOW. Thus, the mtimes of files obtained
through RRDP will never match the mtimes of the same files made
available through RSYNC - in turn causing each and every file to be
added to the file transfer list.

With the below changeset an innate timestamp is extracted from RPKI
objects (in the case of ROAs, MFTs, and ASPAs the CMS signing-time, in
the case of .cer files the X.509 notBefore) to set the file's
modification time. This results in a surprising optimization for the
number files which have to be transfered!

A test can be constructed by as following:

First populate the cachedir using RSYNC only, this way the mtime of all
the files in the cachedir are set to the mtime of the RSYNC server, and
make a copy of the cachedir:

    # mkdir /var/cache/rpki-client/{rsynconly,copy}
    # chown _rpki-client /var/cache/rpki-client/{rsynconly,copy}
    # rpki-client -R -d /var/cache/rpki-client/rsynconly /tmp/
    # rsync -rt /var/cache/rpki-client/rsynconly/ /var/cache/rpki-client/copy/

Then touch all files in the copy directory (this emulates obtaining the
files through RRDP), now run rsync with the '--stats' command line
option to see the number of files to be transferred:

    # find /var/cache/rpki-client/copy/ -type f -exec touch {} \+
    # rsync -n -rt --stats /var/cache/rpki-client/rsynconly/ 
/var/cache/rpki-client/copy/
    Number of files: 329,232 (reg: 262,130, dir: 67,102)
    Number of regular files transferred: 262,130
    Total file size: 472,451,061 bytes
    Total transferred file size: 472,451,061 bytes

Now change the mtime of all the files in the copy from UTIME_NOW to the
CMS signing-time respectively X.509 notBefore using a newly devised
utility called 'rpkitouch': https://github.com/job/rpkitouch/
Run rsync again with '--stats' to see the number of files to be
transferred:

    # find /var/cache/rpki-client/copy/ -type f -not -name '*.crl' -exec 
rpkitouch {} \+
    # rsync -n -rt --stats /var/cache/rpki-client/rsynconly/ 
/var/cache/rpki-client/copy/
    Number of files: 329,232 (reg: 262,130, dir: 67,102)
    Number of regular files transferred: 190,246
    Total file size: 472,451,061 bytes
    Total transferred file size: 343,421,085 bytes

The number of regular files transferred was 25% less because the
copy cachedir's mtimes were primed using innate RPKI timestamps! :-)

I also tested the changeset using rpki-client with and without -R and
arrived at similar results as the synthesized test shown above.

Fallback from RRDP to RSYNC can be made quite seamless if the below
lands in rpki-client(8) and more publication point operators take
advantage of presenting deterministic mtimes (using for example
rpkitouch) via their RSYNC servers.

Kind regards,

Job

ps. Testing suggests that using a CRL's 'lastUpdate' timestamp as mtime
should yield another double digit percentage reduction in files to be
transfered, but I haven't yet implemented that in rpki-client.

Index: aspa.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/aspa.c,v
retrieving revision 1.17
diff -u -p -r1.17 aspa.c
--- aspa.c      26 Apr 2023 16:32:41 -0000      1.17
+++ aspa.c      19 May 2023 10:01:46 -0000
@@ -285,6 +285,7 @@ aspa_buffer(struct ibuf *b, const struct
        io_simple_buffer(b, &p->custasid, sizeof(p->custasid));
        io_simple_buffer(b, &p->talid, sizeof(p->talid));
        io_simple_buffer(b, &p->expires, sizeof(p->expires));
+       io_simple_buffer(b, &p->signtime, sizeof(p->signtime));
 
        io_simple_buffer(b, &p->providersz, sizeof(size_t));
        io_simple_buffer(b, p->providers,
@@ -312,6 +313,7 @@ aspa_read(struct ibuf *b)
        io_read_buf(b, &p->custasid, sizeof(p->custasid));
        io_read_buf(b, &p->talid, sizeof(p->talid));
        io_read_buf(b, &p->expires, sizeof(p->expires));
+       io_read_buf(b, &p->signtime, sizeof(p->signtime));
 
        io_read_buf(b, &p->providersz, sizeof(size_t));
        if ((p->providers = calloc(p->providersz,
Index: cert.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/cert.c,v
retrieving revision 1.108
diff -u -p -r1.108 cert.c
--- cert.c      9 May 2023 10:34:32 -0000       1.108
+++ cert.c      19 May 2023 10:01:47 -0000
@@ -984,6 +984,7 @@ cert_free(struct cert *p)
 void
 cert_buffer(struct ibuf *b, const struct cert *p)
 {
+       io_simple_buffer(b, &p->notbefore, sizeof(p->notbefore));
        io_simple_buffer(b, &p->notafter, sizeof(p->notafter));
        io_simple_buffer(b, &p->purpose, sizeof(p->purpose));
        io_simple_buffer(b, &p->talid, sizeof(p->talid));
@@ -1017,6 +1018,7 @@ cert_read(struct ibuf *b)
        if ((p = calloc(1, sizeof(struct cert))) == NULL)
                err(1, NULL);
 
+       io_read_buf(b, &p->notbefore, sizeof(p->notbefore));
        io_read_buf(b, &p->notafter, sizeof(p->notafter));
        io_read_buf(b, &p->purpose, sizeof(p->purpose));
        io_read_buf(b, &p->talid, sizeof(p->talid));
Index: extern.h
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/extern.h,v
retrieving revision 1.181
diff -u -p -r1.181 extern.h
--- extern.h    9 May 2023 10:34:32 -0000       1.181
+++ extern.h    19 May 2023 10:01:47 -0000
@@ -536,7 +536,13 @@ enum stype {
 };
 
 struct repo;
-struct filepath;
+
+struct filepath {
+       RB_ENTRY(filepath)       entry;
+       time_t                   mtime;
+       char                    *file;
+};
+
 RB_HEAD(filepath_tree, filepath);
 
 
@@ -756,6 +762,7 @@ void                 proc_rrdp(int) __attribute__((nor
 
 /* Repository handling */
 int             filepath_add(struct filepath_tree *, char *);
+struct filepath        *filepath_find(struct filepath_tree *, char *);
 void            rrdp_clear(unsigned int);
 void            rrdp_save_state(unsigned int, struct rrdp_session *);
 int             rrdp_handle_file(unsigned int, enum publish_type, char *,
Index: main.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/main.c,v
retrieving revision 1.236
diff -u -p -r1.236 main.c
--- main.c      27 Apr 2023 08:37:53 -0000      1.236
+++ main.c      19 May 2023 10:01:47 -0000
@@ -550,6 +550,7 @@ entity_process(struct ibuf *b, struct st
        struct roa      *roa;
        struct aspa     *aspa;
        struct repo     *rp;
+       struct filepath *fp;
        char            *file;
        unsigned int     id;
        int              talid;
@@ -575,6 +576,11 @@ entity_process(struct ibuf *b, struct st
                goto done;
        }
 
+       if ((fp = filepath_find(&fpt, file)) == NULL) {
+               warnx("%s: sudden memory loss", file);
+               goto done;
+       }
+
        rp = repo_byid(id);
        repo_stat_inc(rp, talid, type, STYPE_OK);
        switch (type) {
@@ -591,6 +597,7 @@ entity_process(struct ibuf *b, struct st
                        break;
                }
                cert = cert_read(b);
+               fp->mtime = cert->notbefore;
                switch (cert->purpose) {
                case CERT_PURPOSE_CA:
                        queue_add_from_cert(cert);
@@ -612,6 +619,7 @@ entity_process(struct ibuf *b, struct st
                        break;
                }
                mft = mft_read(b);
+               fp->mtime = mft->signtime;
                if (!mft->stale)
                        queue_add_from_mft(mft);
                else
@@ -629,6 +637,7 @@ entity_process(struct ibuf *b, struct st
                        break;
                }
                roa = roa_read(b);
+               fp->mtime = roa->signtime;
                if (roa->valid)
                        roa_insert_vrps(tree, roa, rp);
                else
@@ -644,6 +653,7 @@ entity_process(struct ibuf *b, struct st
                        break;
                }
                aspa = aspa_read(b);
+               fp->mtime = aspa->signtime;
                if (aspa->valid)
                        aspa_insert_vaps(vaptree, aspa, rp);
                else
Index: mft.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/mft.c,v
retrieving revision 1.91
diff -u -p -r1.91 mft.c
--- mft.c       26 Apr 2023 16:32:41 -0000      1.91
+++ mft.c       19 May 2023 10:01:47 -0000
@@ -471,6 +471,7 @@ mft_buffer(struct ibuf *b, const struct 
        io_simple_buffer(b, &p->stale, sizeof(p->stale));
        io_simple_buffer(b, &p->repoid, sizeof(p->repoid));
        io_simple_buffer(b, &p->talid, sizeof(p->talid));
+       io_simple_buffer(b, &p->signtime, sizeof(p->signtime));
        io_str_buffer(b, p->path);
 
        io_str_buffer(b, p->aia);
@@ -504,6 +505,7 @@ mft_read(struct ibuf *b)
        io_read_buf(b, &p->stale, sizeof(p->stale));
        io_read_buf(b, &p->repoid, sizeof(p->repoid));
        io_read_buf(b, &p->talid, sizeof(p->talid));
+       io_read_buf(b, &p->signtime, sizeof(p->signtime));
        io_read_str(b, &p->path);
 
        io_read_str(b, &p->aia);
Index: repo.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/repo.c,v
retrieving revision 1.45
diff -u -p -r1.45 repo.c
--- repo.c      16 May 2023 17:01:31 -0000      1.45
+++ repo.c      19 May 2023 10:01:47 -0000
@@ -16,9 +16,10 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <sys/types.h>
 #include <sys/queue.h>
+#include <sys/time.h>
 #include <sys/tree.h>
-#include <sys/types.h>
 #include <sys/stat.h>
 
 #include <assert.h>
@@ -113,14 +114,6 @@ unsigned int               repoid;
 static struct rsyncrepo        *rsync_get(const char *, const char *);
 static void             remove_contents(char *);
 
-/*
- * Database of all file path accessed during a run.
- */
-struct filepath {
-       RB_ENTRY(filepath)      entry;
-       char                    *file;
-};
-
 static inline int
 filepathcmp(struct filepath *a, struct filepath *b)
 {
@@ -139,6 +132,7 @@ filepath_add(struct filepath_tree *tree,
 
        if ((fp = malloc(sizeof(*fp))) == NULL)
                err(1, NULL);
+       fp->mtime = 0;
        if ((fp->file = strdup(file)) == NULL)
                err(1, NULL);
 
@@ -155,7 +149,7 @@ filepath_add(struct filepath_tree *tree,
 /*
  * Lookup a file path in the tree and return the object if found or NULL.
  */
-static struct filepath *
+struct filepath *
 filepath_find(struct filepath_tree *tree, char *file)
 {
        struct filepath needle = { .file = file };
@@ -1540,6 +1534,25 @@ repo_move_valid(struct filepath_tree *tr
 
                if (repo_mkpath(AT_FDCWD, fn) == -1)
                        continue;
+
+               /*
+                * When RRDP datastructures are serialized to disk set the
+                * file's modified time to the CMS signing-time or X.509
+                * notBefore. This helps reduce the burden of synchronization
+                * should fallback to RSYNC be necessary.
+                */
+               if (fp->mtime != 0 &&
+                   strncmp(fp->file, ".rrdp/", rrdpsz) == 0) {
+                       struct timespec ts[2];
+
+                       ts[0].tv_nsec = UTIME_OMIT;
+                       ts[1].tv_sec = fp->mtime;
+                       ts[1].tv_nsec = 0;
+                       if (utimensat(AT_FDCWD, fp->file, ts, 0) == -1) {
+                               warn("utimensat %s", fp->file);
+                               continue;
+                       }
+               }
 
                if (rename(fp->file, fn) == -1) {
                        warn("rename %s", fp->file);
Index: roa.c
===================================================================
RCS file: /cvs/src/usr.sbin/rpki-client/roa.c,v
retrieving revision 1.66
diff -u -p -r1.66 roa.c
--- roa.c       26 Apr 2023 16:32:41 -0000      1.66
+++ roa.c       19 May 2023 10:01:47 -0000
@@ -311,6 +311,7 @@ roa_buffer(struct ibuf *b, const struct 
        io_simple_buffer(b, &p->talid, sizeof(p->talid));
        io_simple_buffer(b, &p->ipsz, sizeof(p->ipsz));
        io_simple_buffer(b, &p->expires, sizeof(p->expires));
+       io_simple_buffer(b, &p->signtime, sizeof(p->signtime));
 
        io_simple_buffer(b, p->ips, p->ipsz * sizeof(p->ips[0]));
 
@@ -337,6 +338,7 @@ roa_read(struct ibuf *b)
        io_read_buf(b, &p->talid, sizeof(p->talid));
        io_read_buf(b, &p->ipsz, sizeof(p->ipsz));
        io_read_buf(b, &p->expires, sizeof(p->expires));
+       io_read_buf(b, &p->signtime, sizeof(p->signtime));
 
        if ((p->ips = calloc(p->ipsz, sizeof(struct roa_ip))) == NULL)
                err(1, NULL);

Reply via email to