From: Alberto Leiva Popper Date: Fri, 10 Jul 2026 00:19:50 +0000 (-0600) Subject: Add --server.max-rtr-version X-Git-Tag: 1.7.0.experimental~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b0854ce386cbfc3c5f8fa51769cc4ee3903c0f87;p=thirdparty%2FFORT-validator.git Add --server.max-rtr-version 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 --- diff --git a/src/config.c b/src/config.c index 137c6c6..1bd8b5f 100644 --- a/src/config.c +++ b/src/config.c @@ -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 = >_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) { diff --git a/src/config.h b/src/config.h index 0aba828..f4491a9 100644 --- a/src/config.h +++ b/src/config.h @@ -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); diff --git a/src/rtr/pdu_handler.c b/src/rtr/pdu_handler.c index 303b477..ec86a22 100644 --- a/src/rtr/pdu_handler.c +++ b/src/rtr/pdu_handler.c @@ -5,6 +5,7 @@ #include #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; diff --git a/src/rtr/pdu_stream.c b/src/rtr/pdu_stream.c index 64c2a9f..1d6fc1a 100644 --- a/src/rtr/pdu_stream.c +++ b/src/rtr/pdu_stream.c @@ -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; } diff --git a/src/rtr/rtr.c b/src/rtr/rtr.c index 0ccea90..ddd8303 100644 --- a/src/rtr/rtr.c +++ b/src/rtr/rtr.c @@ -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) diff --git a/test/rtr/pdu_handler_test.c b/test/rtr/pdu_handler_test.c index 46ad4be..52779a9 100644 --- a/test/rtr/pdu_handler_test.c +++ b/test/rtr/pdu_handler_test.c @@ -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) diff --git a/test/rtr/pdu_stream_test.c b/test/rtr/pdu_stream_test.c index a921fc9..07048da 100644 --- a/test/rtr/pdu_stream_test.c +++ b/test/rtr/pdu_stream_test.c @@ -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,