This is merely a historical archive of years 2008-2021, before the migration to mailman3.
A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.
Neels Hofmeyr gerrit-no-reply at lists.osmocom.orgHello Harald Welte, Jenkins Builder, I'd like you to reexamine a change. Please visit https://gerrit.osmocom.org/2081 to look at the new patch set (#2). UMTS AKA: implement SQN increment according to SEQ and IND Add ind_bitlen column to auc_3g to record each USIM's IND size according to 3GPP TS 33.102 -- default is 5 bits, as suggested by the spec. Introduce auc_3g_ind to each connecting GSUP client to use as IND index for generating auth tuples sent to this client. With osmo_gsup_server_add_conn(), implement a scheme where clients receive fixed auc_3g_ind indexes based on the order in which they connect; each new connection takes the lowest unused auc_3g_ind, so in case one of the clients restarts, it will most likely receive the same auc_3g_ind, and if one client disconnects, no other clients' auc_3g_ind are affected. Add gsup_server_test.c to test the auc_3g_ind index distribution scheme. Related: OS#1969 Change-Id: If4501ed4ff8e923fa6fe8b80c44c5ad647a8ed60 --- M configure.ac M sql/hlr.sql M src/db.c M src/db.h M src/db_auc.c M src/db_test.c M src/gsup_server.c M src/gsup_server.h M src/hlr.c M tests/Makefile.am A tests/gsup_server/Makefile.am A tests/gsup_server/gsup_server_test.c A tests/gsup_server/gsup_server_test.err A tests/gsup_server/gsup_server_test.ok M tests/testsuite.at 15 files changed, 361 insertions(+), 12 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-hlr refs/changes/81/2081/2 diff --git a/configure.ac b/configure.ac index a04185f..958b48b 100644 --- a/configure.ac +++ b/configure.ac @@ -50,4 +50,5 @@ tests/Makefile tests/auc/Makefile tests/auc/gen_ts_55_205_test_sets/Makefile + tests/gsup_server/Makefile ) diff --git a/sql/hlr.sql b/sql/hlr.sql index 9238871..5fbc712 100644 --- a/sql/hlr.sql +++ b/sql/hlr.sql @@ -63,7 +63,8 @@ k VARCHAR(32) NOT NULL, -- hex string: subscriber's secret key (128bit) op VARCHAR(32), -- hex string: operator's secret key (128bit) opc VARCHAR(32), -- hex string: derived from OP and K (128bit) - sqn INTEGER NOT NULL DEFAULT 0 -- sequence number of key usage + sqn INTEGER NOT NULL DEFAULT 0, -- sequence number of key usage + ind_bitlen INTEGER NOT NULL DEFAULT 5 -- nr of index bits at lower SQN end ); CREATE UNIQUE INDEX IF NOT EXISTS idx_subscr_imsi ON subscriber (imsi); diff --git a/src/db.c b/src/db.c index aa4726c..aaf6fe2 100644 --- a/src/db.c +++ b/src/db.c @@ -29,7 +29,7 @@ [SEL_BY_IMSI] = "SELECT id,imsi,msisdn,vlr_number,sgsn_number,sgsn_address,periodic_lu_tmr,periodic_rau_tau_tmr,nam_cs,nam_ps,lmsi,ms_purged_cs,ms_purged_ps FROM subscriber WHERE imsi = ?", [UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = ? WHERE id = ?", [UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = ? WHERE id = ?", - [AUC_BY_IMSI] = "SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn FROM subscriber LEFT JOIN auc_2g ON auc_2g.subscriber_id = subscriber.id LEFT JOIN auc_3g ON auc_3g.subscriber_id = subscriber.id WHERE imsi = ?", + [AUC_BY_IMSI] = "SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn, ind_bitlen FROM subscriber LEFT JOIN auc_2g ON auc_2g.subscriber_id = subscriber.id LEFT JOIN auc_3g ON auc_3g.subscriber_id = subscriber.id WHERE imsi = ?", [AUC_UPD_SQN] = "UPDATE auc_3g SET sqn = ? WHERE subscriber_id = ?", [UPD_PURGE_CS_BY_IMSI] = "UPDATE subscriber SET ms_purged_cs=1 WHERE imsi = ?", [UPD_PURGE_PS_BY_IMSI] = "UPDATE subscriber SET ms_purged_ps=1 WHERE imsi = ?", diff --git a/src/db.h b/src/db.h index 0b3df88..a60cf62 100644 --- a/src/db.h +++ b/src/db.h @@ -39,8 +39,9 @@ uint64_t new_sqn); int db_get_auc(struct db_context *dbc, const char *imsi, - struct osmo_auth_vector *vec, unsigned int num_vec, - const uint8_t *rand_auts, const uint8_t *auts); + unsigned int auc_3g_ind, struct osmo_auth_vector *vec, + unsigned int num_vec, const uint8_t *rand_auts, + const uint8_t *auts); #include <osmocom/core/linuxlist.h> #include <osmocom/gsm/protocol/gsm_23_003.h> diff --git a/src/db_auc.c b/src/db_auc.c index a24f27e..3a888bb 100644 --- a/src/db_auc.c +++ b/src/db_auc.c @@ -158,6 +158,7 @@ aud3g->u.umts.opc_is_op = 1; } aud3g->u.umts.sqn = sqlite3_column_int64(stmt, 7); + aud3g->u.umts.ind_bitlen = sqlite3_column_int(stmt, 8); /* FIXME: amf? */ aud3g->type = OSMO_AUTH_TYPE_UMTS; } else @@ -185,8 +186,9 @@ /* return -1 in case of error, 0 for unknown imsi, positive for number * of vectors generated */ int db_get_auc(struct db_context *dbc, const char *imsi, - struct osmo_auth_vector *vec, unsigned int num_vec, - const uint8_t *rand_auts, const uint8_t *auts) + unsigned int auc_3g_ind, struct osmo_auth_vector *vec, + unsigned int num_vec, const uint8_t *rand_auts, + const uint8_t *auts) { struct osmo_sub_auth_data aud2g, aud3g; uint64_t subscr_id; @@ -197,6 +199,16 @@ if (rc <= 0) return rc; + aud3g.u.umts.ind = auc_3g_ind; + if (aud3g.type == OSMO_AUTH_TYPE_UMTS + && aud3g.u.umts.ind >= (1U << aud3g.u.umts.ind_bitlen)) { + LOGAUC(imsi, LOGL_NOTICE, "3G auth: SQN's IND bitlen %u is" + " too small to hold an index of %u. Truncating. This" + " may cause numerous additional AUTS resyncing.\n", + aud3g.u.umts.ind_bitlen, aud3g.u.umts.ind); + aud3g.u.umts.ind &= (1U << aud3g.u.umts.ind_bitlen) - 1; + } + LOGAUC(imsi, LOGL_DEBUG, "Calling to generate %u vectors\n", num_vec); rc = auc_compute_vectors(vec, num_vec, &aud2g, &aud3g, rand_auts, auts); if (rc < 0) { diff --git a/src/db_test.c b/src/db_test.c index 998a37a..0e823f9 100644 --- a/src/db_test.c +++ b/src/db_test.c @@ -20,7 +20,7 @@ for (i = 0; i < ARRAY_SIZE(vec); i++) vec[i].res_len = 0; - rc = db_get_auc(dbc, imsi, vec, ARRAY_SIZE(vec), NULL, NULL); + rc = db_get_auc(dbc, imsi, 0, vec, ARRAY_SIZE(vec), NULL, NULL); if (rc <= 0) { LOGP(DMAIN, LOGL_ERROR, "Cannot obtain auth tuples for '%s'\n", imsi); return rc; diff --git a/src/gsup_server.c b/src/gsup_server.c index b0e1858..95f5522 100644 --- a/src/gsup_server.c +++ b/src/gsup_server.c @@ -211,6 +211,50 @@ return 0; } +/* being added to libosmocore in https://gerrit.osmocom.org/2076, + * so long use this local copy. */ +#define _llist_first_entry(ptr, type, member) \ + llist_entry((ptr)->next, type, member) +#define _llist_first_entry_or_null(ptr, type, member) \ + (!llist_empty(ptr) ? _llist_first_entry(ptr, type, member) : NULL) + +/* Add conn to the clients list in a way that conn->auc_3g_ind takes the lowest + * unused integer and the list of clients remains sorted by auc_3g_ind. + * Keep this function non-static to allow linking in a unit test. */ +void osmo_gsup_server_add_conn(struct llist_head *clients, + struct osmo_gsup_conn *conn) +{ + struct osmo_gsup_conn *c; + struct osmo_gsup_conn *prev_conn; + + c = _llist_first_entry_or_null(clients, struct osmo_gsup_conn, list); + + /* Is the first index, 0, unused? */ + if (!c || c->auc_3g_ind > 0) { + conn->auc_3g_ind = 0; + llist_add(&conn->list, clients); + return; + } + + /* Look for a gap later on */ + prev_conn = NULL; + llist_for_each_entry(c, clients, list) { + /* skip first item, we know it has auc_3g_ind == 0. */ + if (!prev_conn) { + prev_conn = c; + continue; + } + if (c->auc_3g_ind > prev_conn->auc_3g_ind + 1) + break; + prev_conn = c; + } + + OSMO_ASSERT(prev_conn); + + conn->auc_3g_ind = prev_conn->auc_3g_ind + 1; + llist_add(&conn->list, &prev_conn->list); +} + /* a client has connected to the server socket and we have accept()ed it */ static int osmo_gsup_server_accept_cb(struct ipa_server_link *link, int fd) { @@ -225,15 +269,15 @@ conn->conn = ipa_server_conn_create(gsups, link, fd, osmo_gsup_server_read_cb, osmo_gsup_server_closed_cb, conn); - conn->conn->ccm_cb = osmo_gsup_server_ccm_cb; OSMO_ASSERT(conn->conn); + conn->conn->ccm_cb = osmo_gsup_server_ccm_cb; /* link data structure with server structure */ conn->server = gsups; - llist_add_tail(&conn->list, &gsups->clients); + osmo_gsup_server_add_conn(&gsups->clients, conn); - LOGP(DLGSUP, LOGL_INFO, "New GSUP client %s:%d\n", - conn->conn->addr, conn->conn->port); + LOGP(DLGSUP, LOGL_INFO, "New GSUP client %s:%d (IND=%u)\n", + conn->conn->addr, conn->conn->port, conn->auc_3g_ind); /* request the identity of the client */ rc = ipa_ccm_send_id_req(fd); diff --git a/src/gsup_server.h b/src/gsup_server.h index 885fe52..74062d4 100644 --- a/src/gsup_server.h +++ b/src/gsup_server.h @@ -31,6 +31,8 @@ struct ipa_server_conn *conn; //struct oap_state oap_state; struct tlv_parsed ccm; + + unsigned int auc_3g_ind; /*!< IND index used for UMTS AKA SQN */ }; diff --git a/src/hlr.c b/src/hlr.c index 00a6459..b5777e2 100644 --- a/src/hlr.c +++ b/src/hlr.c @@ -63,7 +63,8 @@ memset(&gsup_out, 0, sizeof(gsup_out)); memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi)); - rc = db_get_auc(dbc, gsup->imsi, gsup_out.auth_vectors, + rc = db_get_auc(dbc, gsup->imsi, conn->auc_3g_ind, + gsup_out.auth_vectors, ARRAY_SIZE(gsup_out.auth_vectors), gsup->rand, gsup->auts); if (rc < 0) { diff --git a/tests/Makefile.am b/tests/Makefile.am index 21c0e21..0bd0820 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,5 +1,6 @@ SUBDIRS = \ auc \ + gsup_server \ $(NULL) # The `:;' works around a Bash 3.2 bug when the output is not writeable. diff --git a/tests/gsup_server/Makefile.am b/tests/gsup_server/Makefile.am new file mode 100644 index 0000000..fee60f5 --- /dev/null +++ b/tests/gsup_server/Makefile.am @@ -0,0 +1,40 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/src \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(NULL) + +AM_LDFLAGS = \ + $(NULL) + +EXTRA_DIST = \ + gsup_server_test.ok \ + gsup_server_test.err \ + $(NULL) + +noinst_PROGRAMS = \ + gsup_server_test \ + $(NULL) + +gsup_server_test_SOURCES = \ + gsup_server_test.c \ + $(NULL) + +gsup_server_test_LDADD = \ + $(top_srcdir)/src/gsup_server.c \ + $(top_srcdir)/src/gsup_router.c \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(NULL) + +.PHONY: update_exp +update_exp: + $(builddir)/gsup_server_test >"$(srcdir)/gsup_server_test.ok" 2>"$(srcdir)/gsup_server_test.err" diff --git a/tests/gsup_server/gsup_server_test.c b/tests/gsup_server/gsup_server_test.c new file mode 100644 index 0000000..cc475be --- /dev/null +++ b/tests/gsup_server/gsup_server_test.c @@ -0,0 +1,145 @@ +/* (C) 2017 by sysmocom s.f.m.c. GmbH <info at sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr <nhofmeyr at sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <osmocom/core/utils.h> +#include "gsup_server.h" + +#define comment_start() printf("\n===== %s\n", __func__) +#define comment_end() printf("===== %s: SUCCESS\n\n", __func__) +#define btw(fmt, args...) printf("\n" fmt "\n", ## args) + +#define VERBOSE_ASSERT(val, expect_op, fmt) \ + do { \ + printf(#val " == " fmt "\n", (val)); \ + OSMO_ASSERT((val) expect_op); \ + } while (0) + +void osmo_gsup_server_add_conn(struct llist_head *clients, + struct osmo_gsup_conn *conn); + +static void test_add_conn(void) +{ + struct llist_head _list; + struct llist_head *clients = &_list; + struct osmo_gsup_conn conn_inst[23] = {}; + struct osmo_gsup_conn *conn; + unsigned int i; + + comment_start(); + + INIT_LLIST_HEAD(clients); + + btw("Add 10 items"); + for (i = 0; i < 10; i++) { + osmo_gsup_server_add_conn(clients, &conn_inst[i]); + printf("conn_inst[%u].auc_3g_ind == %u\n", i, conn_inst[i].auc_3g_ind); + OSMO_ASSERT(clients->next == &conn_inst[0].list); + } + + btw("Expecting a list of 0..9"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + OSMO_ASSERT(conn->auc_3g_ind == i); + OSMO_ASSERT(conn == &conn_inst[i]); + i++; + } + + btw("Punch two holes in the sequence in arbitrary order," + " a larger one from 2..4 and a single one at 7."); + llist_del(&conn_inst[4].list); + llist_del(&conn_inst[2].list); + llist_del(&conn_inst[3].list); + llist_del(&conn_inst[7].list); + + btw("Expecting a list of 0,1, 5,6, 8,9"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + i++; + } + + btw("Add conns, expecting them to take the open slots"); + osmo_gsup_server_add_conn(clients, &conn_inst[12]); + VERBOSE_ASSERT(conn_inst[12].auc_3g_ind, == 2, "%u"); + + osmo_gsup_server_add_conn(clients, &conn_inst[13]); + VERBOSE_ASSERT(conn_inst[13].auc_3g_ind, == 3, "%u"); + + osmo_gsup_server_add_conn(clients, &conn_inst[14]); + VERBOSE_ASSERT(conn_inst[14].auc_3g_ind, == 4, "%u"); + + osmo_gsup_server_add_conn(clients, &conn_inst[17]); + VERBOSE_ASSERT(conn_inst[17].auc_3g_ind, == 7, "%u"); + + osmo_gsup_server_add_conn(clients, &conn_inst[18]); + VERBOSE_ASSERT(conn_inst[18].auc_3g_ind, == 10, "%u"); + + btw("Expecting a list of 0..10"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + OSMO_ASSERT(conn->auc_3g_ind == i); + i++; + } + + btw("Does it also work for the first item?"); + llist_del(&conn_inst[0].list); + + btw("Expecting a list of 1..10"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + OSMO_ASSERT(conn->auc_3g_ind == i + 1); + i++; + } + + btw("Add another conn, should take auc_3g_ind == 0"); + osmo_gsup_server_add_conn(clients, &conn_inst[20]); + VERBOSE_ASSERT(conn_inst[20].auc_3g_ind, == 0, "%u"); + + btw("Expecting a list of 0..10"); + i = 0; + llist_for_each_entry(conn, clients, list) { + printf("conn[%u].auc_3g_ind == %u\n", i, conn->auc_3g_ind); + OSMO_ASSERT(conn->auc_3g_ind == i); + i++; + } + + btw("If a client reconnects, it will (likely) get the same auc_3g_ind"); + VERBOSE_ASSERT(conn_inst[5].auc_3g_ind, == 5, "%u"); + llist_del(&conn_inst[5].list); + conn_inst[5].auc_3g_ind = 423; + osmo_gsup_server_add_conn(clients, &conn_inst[5]); + VERBOSE_ASSERT(conn_inst[5].auc_3g_ind, == 5, "%u"); + + comment_end(); +} + +int main(int argc, char **argv) +{ + printf("test_gsup_server.c\n"); + + test_add_conn(); + + printf("Done\n"); + return 0; +} diff --git a/tests/gsup_server/gsup_server_test.err b/tests/gsup_server/gsup_server_test.err new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/gsup_server/gsup_server_test.err diff --git a/tests/gsup_server/gsup_server_test.ok b/tests/gsup_server/gsup_server_test.ok new file mode 100644 index 0000000..80d944c --- /dev/null +++ b/tests/gsup_server/gsup_server_test.ok @@ -0,0 +1,94 @@ +test_gsup_server.c + +===== test_add_conn + +Add 10 items +conn_inst[0].auc_3g_ind == 0 +conn_inst[1].auc_3g_ind == 1 +conn_inst[2].auc_3g_ind == 2 +conn_inst[3].auc_3g_ind == 3 +conn_inst[4].auc_3g_ind == 4 +conn_inst[5].auc_3g_ind == 5 +conn_inst[6].auc_3g_ind == 6 +conn_inst[7].auc_3g_ind == 7 +conn_inst[8].auc_3g_ind == 8 +conn_inst[9].auc_3g_ind == 9 + +Expecting a list of 0..9 +conn[0].auc_3g_ind == 0 +conn[1].auc_3g_ind == 1 +conn[2].auc_3g_ind == 2 +conn[3].auc_3g_ind == 3 +conn[4].auc_3g_ind == 4 +conn[5].auc_3g_ind == 5 +conn[6].auc_3g_ind == 6 +conn[7].auc_3g_ind == 7 +conn[8].auc_3g_ind == 8 +conn[9].auc_3g_ind == 9 + +Punch two holes in the sequence in arbitrary order, a larger one from 2..4 and a single one at 7. + +Expecting a list of 0,1, 5,6, 8,9 +conn[0].auc_3g_ind == 0 +conn[1].auc_3g_ind == 1 +conn[2].auc_3g_ind == 5 +conn[3].auc_3g_ind == 6 +conn[4].auc_3g_ind == 8 +conn[5].auc_3g_ind == 9 + +Add conns, expecting them to take the open slots +conn_inst[12].auc_3g_ind == 2 +conn_inst[13].auc_3g_ind == 3 +conn_inst[14].auc_3g_ind == 4 +conn_inst[17].auc_3g_ind == 7 +conn_inst[18].auc_3g_ind == 10 + +Expecting a list of 0..10 +conn[0].auc_3g_ind == 0 +conn[1].auc_3g_ind == 1 +conn[2].auc_3g_ind == 2 +conn[3].auc_3g_ind == 3 +conn[4].auc_3g_ind == 4 +conn[5].auc_3g_ind == 5 +conn[6].auc_3g_ind == 6 +conn[7].auc_3g_ind == 7 +conn[8].auc_3g_ind == 8 +conn[9].auc_3g_ind == 9 +conn[10].auc_3g_ind == 10 + +Does it also work for the first item? + +Expecting a list of 1..10 +conn[0].auc_3g_ind == 1 +conn[1].auc_3g_ind == 2 +conn[2].auc_3g_ind == 3 +conn[3].auc_3g_ind == 4 +conn[4].auc_3g_ind == 5 +conn[5].auc_3g_ind == 6 +conn[6].auc_3g_ind == 7 +conn[7].auc_3g_ind == 8 +conn[8].auc_3g_ind == 9 +conn[9].auc_3g_ind == 10 + +Add another conn, should take auc_3g_ind == 0 +conn_inst[20].auc_3g_ind == 0 + +Expecting a list of 0..10 +conn[0].auc_3g_ind == 0 +conn[1].auc_3g_ind == 1 +conn[2].auc_3g_ind == 2 +conn[3].auc_3g_ind == 3 +conn[4].auc_3g_ind == 4 +conn[5].auc_3g_ind == 5 +conn[6].auc_3g_ind == 6 +conn[7].auc_3g_ind == 7 +conn[8].auc_3g_ind == 8 +conn[9].auc_3g_ind == 9 +conn[10].auc_3g_ind == 10 + +If a client reconnects, it will (likely) get the same auc_3g_ind +conn_inst[5].auc_3g_ind == 5 +conn_inst[5].auc_3g_ind == 5 +===== test_add_conn: SUCCESS + +Done diff --git a/tests/testsuite.at b/tests/testsuite.at index 46d1456..a969082 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -14,3 +14,10 @@ cat $abs_srcdir/auc/auc_ts_55_205_test_sets.err > experr AT_CHECK([$abs_top_builddir/tests/auc/auc_ts_55_205_test_sets], [], [expout], [experr]) AT_CLEANUP + +AT_SETUP([gsup_server]) +AT_KEYWORDS([gsup_server]) +cat $abs_srcdir/gsup_server/gsup_server_test.ok > expout +cat $abs_srcdir/gsup_server/gsup_server_test.err > experr +AT_CHECK([$abs_top_builddir/tests/gsup_server/gsup_server_test], [], [expout], [experr]) +AT_CLEANUP -- To view, visit https://gerrit.osmocom.org/2081 To unsubscribe, visit https://gerrit.osmocom.org/settings Gerrit-MessageType: newpatchset Gerrit-Change-Id: If4501ed4ff8e923fa6fe8b80c44c5ad647a8ed60 Gerrit-PatchSet: 2 Gerrit-Project: osmo-hlr Gerrit-Branch: master Gerrit-Owner: Neels Hofmeyr <nhofmeyr at sysmocom.de> Gerrit-Reviewer: Harald Welte <laforge at gnumonks.org> Gerrit-Reviewer: Jenkins Builder