Fixup file modification timestamps to optimize failover from RRDP to RSYNC
authorjob <job@openbsd.org>
Tue, 30 May 2023 16:02:28 +0000 (16:02 +0000)
committerjob <job@openbsd.org>
Tue, 30 May 2023 16:02:28 +0000 (16:02 +0000)
In the RSYNC protocol a file's last modification time and its size are
used to determine whether sending a (partial) copy over the wire is needed.
Previously, when RRDP data structures are serialized to disk, the mtime of
files in DIR_VALID ended up being UTIME_NOW.

Thus, the mtimes of files obtained through RRDP will never match the mtimes
of the same files available through RSYNC - causing each and every file to
be added to the file transfer list.

Instead, use the internal timestamps of RPKI files as the last modified
timestamp. Specifically, for Signed Objects (ROAs, MFTs, GBRs, TAKs, ASPAs)
the CMS signing-time, for .cer files the X.509 notBefore, and for .crl files
the CRL lastUpdate. This results in a surprising optimization for the number
files which have to be transfered.

OK claudio@

usr.sbin/rpki-client/extern.h
usr.sbin/rpki-client/filemode.c
usr.sbin/rpki-client/main.c
usr.sbin/rpki-client/parser.c
usr.sbin/rpki-client/repo.c

index 64c75cc..c3e3be8 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: extern.h,v 1.182 2023/05/30 12:14:48 claudio Exp $ */
+/*     $OpenBSD: extern.h,v 1.183 2023/05/30 16:02:28 job Exp $ */
 /*
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
  *
@@ -348,6 +348,7 @@ struct gbr {
        time_t           notbefore; /* EE cert's Not Before */
        time_t           notafter; /* Not After of the GBR EE */
        time_t           expires; /* when the signature path expires */
+       int              talid; /* TAL the GBR is chained up to */
 };
 
 struct aspa_provider {
@@ -755,7 +756,7 @@ void                 proc_http(char *, int) __attribute__((noreturn));
 void            proc_rrdp(int) __attribute__((noreturn));
 
 /* Repository handling */
-int             filepath_add(struct filepath_tree *, char *);
+int             filepath_add(struct filepath_tree *, char *, time_t);
 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 0acb238..6a0cbec 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: filemode.c,v 1.32 2023/05/30 12:02:22 claudio Exp $ */
+/*     $OpenBSD: filemode.c,v 1.33 2023/05/30 16:02:28 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -589,6 +589,7 @@ parse_file(struct entityq *q, struct msgbuf *msgq)
        struct entity   *entp;
        struct ibuf     *b;
        struct tal      *tal;
+       time_t           dummy = 0;
 
        while ((entp = TAILQ_FIRST(q)) != NULL) {
                TAILQ_REMOVE(q, entp, entries);
@@ -615,6 +616,7 @@ parse_file(struct entityq *q, struct msgbuf *msgq)
                io_simple_buffer(b, &entp->repoid, sizeof(entp->repoid));
                io_simple_buffer(b, &entp->talid, sizeof(entp->talid));
                io_str_buffer(b, entp->file);
+               io_simple_buffer(b, &dummy, sizeof(dummy));
                io_close_buffer(msgq, b);
                entity_free(entp);
        }
index 22c1c34..1982e27 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: main.c,v 1.240 2023/05/30 12:14:48 claudio Exp $ */
+/*     $OpenBSD: main.c,v 1.241 2023/05/30 16:02:28 job Exp $ */
 /*
  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -559,6 +559,7 @@ entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree,
        struct aspa     *aspa;
        struct repo     *rp;
        char            *file;
+       time_t           mtime;
        unsigned int     id;
        int              talid;
        int              c;
@@ -573,12 +574,13 @@ entity_process(struct ibuf *b, struct stats *st, struct vrp_tree *tree,
        io_read_buf(b, &id, sizeof(id));
        io_read_buf(b, &talid, sizeof(talid));
        io_read_str(b, &file);
+       io_read_buf(b, &mtime, sizeof(mtime));
 
        /* in filemode messages can be ignored, only the accounting matters */
        if (filemode)
                goto done;
 
-       if (filepath_add(&fpt, file) == 0) {
+       if (filepath_add(&fpt, file, mtime) == 0) {
                warnx("%s: File already visited", file);
                goto done;
        }
index 93c0eca..107375f 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parser.c,v 1.95 2023/05/30 12:14:48 claudio Exp $ */
+/*     $OpenBSD: parser.c,v 1.96 2023/05/30 16:02:28 job Exp $ */
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -352,7 +352,8 @@ proc_parser_mft_post(char *file, struct mft *mft, const char *path,
  * Load the most recent MFT by opening both options and comparing the two.
  */
 static char *
-proc_parser_mft(struct entity *entp, struct mft **mp, char **crlfile)
+proc_parser_mft(struct entity *entp, struct mft **mp, char **crlfile,
+    time_t *crlmtime)
 {
        struct mft      *mft1 = NULL, *mft2 = NULL;
        struct crl      *crl, *crl1, *crl2;
@@ -360,6 +361,7 @@ proc_parser_mft(struct entity *entp, struct mft **mp, char **crlfile)
        const char      *err1, *err2;
 
        *mp = NULL;
+       *crlmtime = 0;
 
        mft1 = proc_parser_mft_pre(entp, DIR_VALID, &file1, &crl1, &crl1file,
            &err1);
@@ -392,6 +394,7 @@ proc_parser_mft(struct entity *entp, struct mft **mp, char **crlfile)
        }
 
        if (*mp != NULL) {
+               *crlmtime = crl->lastupdate;
                if (!crl_insert(&crlt, crl)) {
                        warnx("%s: duplicate AKI %s", file, crl->aki);
                        crl_free(crl);
@@ -488,7 +491,7 @@ proc_parser_root_cert(char *file, const unsigned char *der, size_t len,
 /*
  * Parse a ghostbuster record
  */
-static void
+static struct gbr *
 proc_parser_gbr(char *file, const unsigned char *der, size_t len,
     const char *mftaki)
 {
@@ -499,17 +502,23 @@ proc_parser_gbr(char *file, const unsigned char *der, size_t len,
        const char      *errstr;
 
        if ((gbr = gbr_parse(&x509, file, der, len)) == NULL)
-               return;
+               return NULL;
 
        a = valid_ski_aki(file, &auths, gbr->ski, gbr->aki, mftaki);
        crl = crl_get(&crlt, a);
 
        /* return value can be ignored since nothing happens here */
-       if (!valid_x509(file, ctx, x509, a, crl, &errstr))
+       if (!valid_x509(file, ctx, x509, a, crl, &errstr)) {
                warnx("%s: %s", file, errstr);
-
+               X509_free(x509);
+               gbr_free(gbr);
+               return NULL;
+       }
        X509_free(x509);
-       gbr_free(gbr);
+
+       gbr->talid = a->cert->talid;
+
+       return gbr;
 }
 
 /*
@@ -618,8 +627,11 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
        struct mft      *mft;
        struct roa      *roa;
        struct aspa     *aspa;
+       struct gbr      *gbr;
+       struct tak      *tak;
        struct ibuf     *b;
        unsigned char   *f;
+       time_t           mtime, crlmtime;
        size_t           flen;
        char            *file, *crlfile;
        int              c;
@@ -642,9 +654,13 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
 
                file = NULL;
                f = NULL;
+               mtime = 0;
+               crlmtime = 0;
+
                switch (entp->type) {
                case RTYPE_TAL:
                        io_str_buffer(b, entp->file);
+                       io_simple_buffer(b, &mtime, sizeof(mtime));
                        if ((tal = tal_parse(entp->file, entp->data,
                            entp->datasz)) == NULL)
                                errx(1, "%s: could not parse tal file",
@@ -663,6 +679,9 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
                        else
                                cert = proc_parser_cert(file, f, flen,
                                    entp->mftaki);
+                       if (cert != NULL)
+                               mtime = cert->notbefore;
+                       io_simple_buffer(b, &mtime, sizeof(mtime));
                        c = (cert != NULL);
                        io_simple_buffer(b, &c, sizeof(int));
                        if (cert != NULL) {
@@ -676,8 +695,11 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
                         */
                        break;
                case RTYPE_MFT:
-                       file = proc_parser_mft(entp, &mft, &crlfile);
+                       file = proc_parser_mft(entp, &mft, &crlfile, &crlmtime);
                        io_str_buffer(b, file);
+                       if (mft != NULL)
+                               mtime = mft->signtime;
+                       io_simple_buffer(b, &mtime, sizeof(mtime));
                        c = (mft != NULL);
                        io_simple_buffer(b, &c, sizeof(int));
                        if (mft != NULL)
@@ -696,6 +718,8 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
                                io_simple_buffer(b2, &entp->talid,
                                    sizeof(entp->talid));
                                io_str_buffer(b2, crlfile);
+                               io_simple_buffer(b2, &crlmtime,
+                                   sizeof(crlmtime));
                                free(crlfile);
 
                                io_close_buffer(msgq, b2);
@@ -706,6 +730,9 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
                        file = parse_load_file(entp, &f, &flen);
                        io_str_buffer(b, file);
                        roa = proc_parser_roa(file, f, flen, entp->mftaki);
+                       if (roa != NULL)
+                               mtime = roa->signtime;
+                       io_simple_buffer(b, &mtime, sizeof(mtime));
                        c = (roa != NULL);
                        io_simple_buffer(b, &c, sizeof(int));
                        if (roa != NULL)
@@ -715,12 +742,19 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
                case RTYPE_GBR:
                        file = parse_load_file(entp, &f, &flen);
                        io_str_buffer(b, file);
-                       proc_parser_gbr(file, f, flen, entp->mftaki);
+                       gbr = proc_parser_gbr(file, f, flen, entp->mftaki);
+                       if (gbr != NULL)
+                               mtime = gbr->signtime;
+                       io_simple_buffer(b, &mtime, sizeof(mtime));
+                       gbr_free(gbr);
                        break;
                case RTYPE_ASPA:
                        file = parse_load_file(entp, &f, &flen);
                        io_str_buffer(b, file);
                        aspa = proc_parser_aspa(file, f, flen, entp->mftaki);
+                       if (aspa != NULL)
+                               mtime = aspa->signtime;
+                       io_simple_buffer(b, &mtime, sizeof(mtime));
                        c = (aspa != NULL);
                        io_simple_buffer(b, &c, sizeof(int));
                        if (aspa != NULL)
@@ -730,13 +764,18 @@ parse_entity(struct entityq *q, struct msgbuf *msgq)
                case RTYPE_TAK:
                        file = parse_load_file(entp, &f, &flen);
                        io_str_buffer(b, file);
-                       proc_parser_tak(file, f, flen, entp->mftaki);
+                       tak = proc_parser_tak(file, f, flen, entp->mftaki);
+                       if (tak != NULL)
+                               mtime = tak->signtime;
+                       io_simple_buffer(b, &mtime, sizeof(mtime));
+                       tak_free(tak);
                        break;
                case RTYPE_CRL:
                default:
                        file = parse_filepath(entp->repoid, entp->path,
                            entp->file, entp->location);
                        io_str_buffer(b, file);
+                       io_simple_buffer(b, &mtime, sizeof(mtime));
                        warnx("%s: unhandled type %d", file, entp->type);
                        break;
                }
index ee79b1e..009ce25 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: repo.c,v 1.46 2023/05/25 12:49:39 claudio Exp $ */
+/*     $OpenBSD: repo.c,v 1.47 2023/05/30 16:02:28 job Exp $ */
 /*
  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
@@ -119,6 +119,7 @@ static void          remove_contents(char *);
 struct filepath {
        RB_ENTRY(filepath)      entry;
        char                    *file;
+       time_t                   mtime;
 };
 
 static inline int
@@ -133,12 +134,13 @@ RB_PROTOTYPE(filepath_tree, filepath, entry, filepathcmp);
  * Functions to lookup which files have been accessed during computation.
  */
 int
-filepath_add(struct filepath_tree *tree, char *file)
+filepath_add(struct filepath_tree *tree, char *file, time_t mtime)
 {
        struct filepath *fp;
 
        if ((fp = malloc(sizeof(*fp))) == NULL)
                err(1, NULL);
+       fp->mtime = mtime;
        if ((fp->file = strdup(file)) == NULL)
                err(1, NULL);
 
@@ -838,7 +840,7 @@ rrdp_handle_file(unsigned int id, enum publish_type pt, char *uri,
 
        /* write new content or mark uri as deleted. */
        if (pt == PUB_DEL) {
-               filepath_add(&rr->deleted, uri);
+               filepath_add(&rr->deleted, uri, 0);
        } else {
                fp = filepath_find(&rr->deleted, uri);
                if (fp != NULL)
@@ -1536,6 +1538,28 @@ repo_move_valid(struct filepath_tree *tree)
                        base = strchr(fp->file + rrdpsz, '/');
                        assert(base != NULL);
                        fn = base + 1;
+
+                       /*
+                        * Adjust file last modification time in order to
+                        * minimize RSYNC synchronization load after transport
+                        * failover.
+                        * While serializing RRDP datastructures to disk, set
+                        * the last modified timestamp to the CMS signing-time,
+                        * the X.509 notBefore, or CRL lastUpdate timestamp.
+                        */
+                       if (fp->mtime != 0) {
+                               int ret;
+                               struct timespec ts[2];
+
+                               ts[0].tv_nsec = UTIME_OMIT;
+                               ts[1].tv_sec = fp->mtime;
+                               ts[1].tv_nsec = 0;
+                               ret = utimensat(AT_FDCWD, fp->file, ts, 0);
+                               if (ret == -1) {
+                                       warn("utimensat %s", fp->file);
+                                       continue;
+                               }
+                       }
                }
 
                if (repo_mkpath(AT_FDCWD, fn) == -1)