]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Add --server.max-rtr-version
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Fri, 10 Jul 2026 00:19:50 +0000 (18:19 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Fri, 10 Jul 2026 00:19:50 +0000 (18:19 -0600)
Caps the maximum RTR version the server will be willing to serve.

Defaults to 0 because the RTRv1 implementation still doesn't do
anything useful, and the RTRv2 implementation is transient.
(They're still tweaking the RTRv2 spec.)

Also fixes a couple of bugs:

- Was not closing the connection when returning internal error PDUs
- Some error reports before the version validation would erroneously
  indicate v255 in the header

src/config.c
src/config.h
src/rtr/pdu_handler.c
src/rtr/pdu_stream.c
src/rtr/rtr.c
test/rtr/pdu_handler_test.c
test/rtr/pdu_stream_test.c

index 137c6c66b0a58b398f9ad904b409459a8eaaa419..1bd8b5f975fbcb99b13d835c6adb14551ca506b1 100644 (file)
@@ -73,6 +73,8 @@ struct rpki_config {
                } interval;
                /** Number of iterations the deltas will be stored. */
                unsigned int deltas_lifetime;
+
+               unsigned int max_rtr_version;
        } server;
 
        struct {
@@ -438,6 +440,14 @@ static const struct option_field options[] = {
                 * Must not overflow when multiplied by interval.validation.
                 */
                .max = 1000,
+       }, {
+               .id = 5008,
+               .name = "server.max-rtr-version",
+               .type = &gt_uint,
+               .offset = offsetof(struct rpki_config, server.max_rtr_version),
+               .doc = "Maximum RTR version to serve (0-2).",
+               .min = 0,
+               .max = 2,
        },
 
        /* Prometheus fields */
@@ -1017,6 +1027,7 @@ set_default_values(void)
        rpki_config.server.interval.retry = 600;
        rpki_config.server.interval.expire = 7200;
        rpki_config.server.deltas_lifetime = 6;
+       rpki_config.server.max_rtr_version = 0;
 
        rpki_config.prometheus.port = 0;
 
@@ -1307,6 +1318,12 @@ config_get_deltas_lifetime(void)
        return rpki_config.server.deltas_lifetime;
 }
 
+unsigned int
+max_rtr_version(void)
+{
+       return rpki_config.server.max_rtr_version;
+}
+
 unsigned int
 config_get_prometheus_port(void)
 {
index 0aba8281e41058bafe870cc45c565d13d0aa8b15..f4491a9391aa0e78d5c37de352ade85921e84e6a 100644 (file)
@@ -27,6 +27,7 @@ unsigned int config_get_interval_refresh(void);
 unsigned int config_get_interval_retry(void);
 unsigned int config_get_interval_expire(void);
 unsigned int config_get_deltas_lifetime(void);
+unsigned int max_rtr_version(void);
 unsigned int config_get_prometheus_port(void);
 char const *config_get_slurm(void);
 
index 303b4776e93e093337d80ca00f8be8caac24599b..ec86a225e09a177b63075fbac485bc2b0c7c3c8a 100644 (file)
@@ -5,6 +5,7 @@
 #include <string.h>
 
 #include "alloc.h"
+#include "config.h"
 #include "log.h"
 #include "data_structure/common.h"
 #include "rtr/err_pdu.h"
@@ -19,6 +20,42 @@ struct rtr_stream {
        int (*send)(struct rtr_stream *, unsigned char const *, int);
 };
 
+static int
+validate_rtr_version(struct rtr_request *request)
+{
+       struct pdu_stream *stream = request->stream;
+       enum rtr_version reqver = request->pdu.rtr_version;
+
+       if (stream->rtr_version == -1) {
+               if (RTR_V0 <= reqver && reqver <= max_rtr_version()) {
+                       pr_op_debug("Establishing RTR version: %d", reqver);
+                       stream->rtr_version = reqver;
+                       return 0;
+               }
+               pr_op_err("%s: Unsupported RTR version: %u",
+                   stream->addr, reqver);
+               return -err_pdu_send_unsupported_proto_version(
+                       stream->fd,
+                       max_rtr_version(),
+                       &request->pdu.raw,
+                       "RTR version number is too high."
+               );
+       }
+
+       if (stream->rtr_version != reqver) {
+               pr_op_err("%s: Client changed RTR version: %u -> %u",
+                   stream->addr, stream->rtr_version, reqver);
+               return err_pdu_send_unexpected_proto_version(
+                       stream->fd,
+                       stream->rtr_version,
+                       &request->pdu.raw,
+                       "The RTR version does not match the one we negotiated during the handshake."
+               );
+       }
+
+       return 0;
+}
+
 static uint32_t
 read_u32(unsigned char const *raw)
 {
@@ -260,6 +297,12 @@ handle_reset_query_pdu(struct rtr_request *request)
        pr_op_debug("Reset Query. Request version: %u",
            request->pdu.rtr_version);
 
+       error = validate_rtr_version(request);
+       if (error < 0)
+               return error;
+       if (error > 0)
+               return 0;
+
        stream.fd = request->fd;
        stream.ver = request->pdu.rtr_version;
 
@@ -546,6 +589,12 @@ handle_serial_query_pdu(struct rtr_request *request)
            request->pdu.obj.sq.session_id,
            request->pdu.obj.sq.serial_number);
 
+       error = validate_rtr_version(request);
+       if (error < 0)
+               return error;
+       if (error > 0)
+               return 0;
+
        stream.fd = request->fd;
        stream.ver = request->pdu.rtr_version;
 
index 64c2a9f87f95b76705ba0fa57ae8284453912ec4..1d6fc1a22747be1d3f167de6e810d10b0f2ca1fb 100644 (file)
@@ -30,7 +30,7 @@ struct pdu_header {
                uint16_t session_id;
                uint16_t reserved;
                uint16_t error_code;
-       } m; /* Note: "m" stands for "meh." I have no idea what to call this. */
+       } m; /* stands for "meh" */
        uint32_t len;
 };
 
@@ -57,6 +57,19 @@ pdustream_destroy(struct pdu_stream *stream)
        free(stream);
 }
 
+/* For when you need to send an error PDU before the version validation */
+static uint8_t
+guess_version(struct pdu_stream *stream, struct pdu_header *hdr)
+{
+       if (stream->rtr_version != -1)
+               return stream->rtr_version;
+
+       if (RTR_V0 <= hdr->version && hdr->version <= RTR_V2)
+               return hdr->version;
+
+       return RTR_V2;
+}
+
 static size_t
 get_length(struct pdu_stream *stream)
 {
@@ -225,30 +238,6 @@ read_hdr(struct pdu_stream *stream, struct pdu_header *header)
        header->len = read_uint32(stream->start + 4);
 }
 
-static int
-validate_rtr_version(struct pdu_stream *stream, struct pdu_header *hdr,
-    struct rtr_buffer *request)
-{
-       if (stream->rtr_version == -1) {
-               if (RTR_V0 <= hdr->version && hdr->version <= RTR_V2) {
-                       stream->rtr_version = hdr->version;
-                       return 0;
-               }
-               return err_pdu_send_unsupported_proto_version(
-                       stream->fd, RTR_V2, request,
-                       "The maximum supported RTR version is 2."
-               );
-       }
-
-       if (stream->rtr_version != hdr->version)
-               return err_pdu_send_unexpected_proto_version(
-                       stream->fd, stream->rtr_version, request,
-                       "The RTR version does not match the one we negotiated during the handshake."
-               );
-
-       return 0;
-}
-
 static int
 load_serial_query(struct pdu_stream *stream, struct pdu_header *hdr,
     struct rtr_request *result)
@@ -259,7 +248,9 @@ load_serial_query(struct pdu_stream *stream, struct pdu_header *hdr,
                pr_op_err("%s: Header length is not %u: %u",
                    stream->addr, RTRPDU_SERIAL_QUERY_LEN, hdr->len);
                return err_pdu_send_invalid_request(
-                       stream->fd, stream->rtr_version, &result->pdu.raw,
+                       stream->fd,
+                       guess_version(stream, hdr),
+                       &result->pdu.raw,
                        "Expected length 12 for Serial Query PDUs."
                );
        }
@@ -289,7 +280,9 @@ load_reset_query(struct pdu_stream *stream, struct pdu_header *hdr,
                pr_op_err("%s: Header length is not %u: %u",
                    stream->addr, RTRPDU_RESET_QUERY_LEN, hdr->len);
                return err_pdu_send_invalid_request(
-                       stream->fd, stream->rtr_version, &result->pdu.raw,
+                       stream->fd,
+                       guess_version(stream, hdr),
+                       &result->pdu.raw,
                        "Expected length 8 for Reset Query PDUs."
                );
        }
@@ -504,22 +497,13 @@ again:
                             stream->addr, hdr.len, RTRPDU_MAX_LEN2);
                        err_pdu_send_invalid_request(
                                stream->fd,
-                               (stream->rtr_version != -1)
-                                   ? stream->rtr_version
-                                   : hdr.version,
+                               guess_version(stream, &hdr),
                                &raw,
                                "PDU is too large."
                        );
                        goto fail;
                }
 
-               /* Validate version; Needs raw. */
-               if (validate_rtr_version(stream, &hdr, &raw) != 0) {
-                       pr_op_err("%s: Bad RTR version: %u",
-                           stream->addr, hdr.version);
-                       goto fail;
-               }
-
                request = create_request(stream, &hdr, &raw);
                raw.bytes = NULL; /* Ownership transferred */
 
@@ -535,9 +519,12 @@ again:
                        break;
                default:
                        pr_op_err("%s: Unknown PDU type: %u",
-                           stream->addr, hdr.version);
-                       err_pdu_send_unsupported_pdu_type(stream->fd,
-                           stream->rtr_version, &request->pdu.raw);
+                           stream->addr, hdr.type);
+                       err_pdu_send_unsupported_pdu_type(
+                               stream->fd,
+                               guess_version(stream, &hdr),
+                               &request->pdu.raw
+                       );
                        goto fail;
                }
 
index 0ccea902bd763123df0a66279b72ee6b7f8d3917..ddd83034ae191ab6cbd8dba7c7745f4e34a60c5a 100644 (file)
@@ -356,23 +356,25 @@ claim_client(void)
        return NULL;
 }
 
-static void
+static int
 handle_request(struct rtr_request *req)
 {
+       int error;
+
        switch (req->pdu.type) {
        case PDU_TYPE_SERIAL_QUERY:
-               handle_serial_query_pdu(req);
+               error = handle_serial_query_pdu(req);
                break;
        case PDU_TYPE_RESET_QUERY:
-               handle_reset_query_pdu(req);
+               error = handle_reset_query_pdu(req);
                break;
        default:
                /* Should have been catched during constructor */
-               pr_crit("Unexpected PDU type: %u",
-                   req->pdu.type);
+               pr_crit("Unexpected PDU type: %u", req->pdu.type);
        }
 
        rtreq_destroy(req);
+       return error;
 }
 
 static void *
@@ -404,7 +406,7 @@ handle_clients(void *arg)
 
                do {
                        if (!pdustream_parse(client, &reqs)) {
-                               pr_op_debug("RTR worker thread: %s errored.",
+kick_client:                   pr_op_debug("RTR worker thread: %s errored.",
                                    client->addr);
                                rtreqlist_clear(&reqs);
                                mutex_lock(&lock);
@@ -422,7 +424,8 @@ handle_clients(void *arg)
                        }
 
                        while ((req = rtreqlist_pop(&reqs)) != NULL)
-                               handle_request(req);
+                               if (handle_request(req) != 0)
+                                       goto kick_client;
                } while (true);
 
                if (write(worker2poller[1], &one, 1) < 0)
index 46ad4be61782bc71f6b64da12651fd345d6fba9b..52779a98671b7ee95f9cd2454a1ffbf5095fd599 100644 (file)
@@ -21,6 +21,7 @@ unsigned int deltas_lifetime = 5;
 
 MOCK(config_get_local_repository, char const *, "tmp", void)
 MOCK_UINT(config_get_deltas_lifetime, deltas_lifetime, void)
+MOCK_UINT(max_rtr_version, 2, void)
 MOCK_UINT(config_get_validation_interval, 3600, void)
 MOCK_UINT(config_get_max_aspa_providers, 10, void)
 
index a921fc915094b7b1f66e6931cbf6a9fbb8ca70bd..07048da21075fb92c65ead97dd93de97c2db3766 100644 (file)
@@ -10,6 +10,7 @@
 /* Mocks */
 
 MOCK(config_get_local_repository, char const *, "tmp", void)
+MOCK_UINT(max_rtr_version, 2, void)
 MOCK_ABORT_INT(err_pdu_send_invalid_request, int fd, uint8_t version,
     struct rtr_buffer const *request, char const *msg)
 MOCK_ABORT_INT(err_pdu_send_unsupported_proto_version, int fd, uint8_t version,