blob: 396b310660faeb733206ba1d4889d4106d02a596 [file] [log] [blame]
roman5cbb6532023-06-22 12:53:17 +02001/**
2 * @file test_ch.c
3 * @author Roman Janota <xjanot04@fit.vutbr.cz>
4 * @brief libnetconf2 Call-home test
5 *
6 * @copyright
7 * Copyright (c) 2023 CESNET, z.s.p.o.
8 *
9 * This source code is licensed under BSD 3-Clause License (the "License").
10 * You may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * https://opensource.org/licenses/BSD-3-Clause
14 */
15
16#define _GNU_SOURCE
17
18#include <errno.h>
19#include <pthread.h>
20#include <setjmp.h>
21#include <stdarg.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25
26#include <cmocka.h>
27
28#include "tests/config.h"
29
30#define NC_PS_POLL_TIMEOUT 2000
31
32#define NC_ACCEPT_TIMEOUT 2000
33
34struct ly_ctx *ctx;
35
36struct test_state {
37 pthread_barrier_t barrier;
romanb6f44032023-06-30 15:07:56 +020038 struct lyd_node *ssh_tree;
39 struct lyd_node *tls_tree;
roman5cbb6532023-06-22 12:53:17 +020040};
41
romanba93eac2023-07-18 14:36:48 +020042char buffer[512];
43char expected[512];
44
45static void
46test_msg_callback(const struct nc_session *session, NC_VERB_LEVEL level, const char *msg)
47{
48 (void) level;
49 (void) session;
50
51 if (strstr(msg, expected)) {
Michal Vaskod2ef3402023-11-21 11:03:52 +010052 strncpy(buffer, msg, 511);
romanba93eac2023-07-18 14:36:48 +020053 }
54
55 printf("%s\n", msg);
56}
57
roman5cbb6532023-06-22 12:53:17 +020058/* acquire ctx cb for dispatch */
59const struct ly_ctx *
60ch_session_acquire_ctx_cb(void *cb_data)
61{
62 (void) cb_data;
63 return ctx;
64}
65
66/* release ctx cb for dispatch */
67void
68ch_session_release_ctx_cb(void *cb_data)
69{
70 (void) cb_data;
71 return;
72}
73
74/* new session cb for dispatch */
75int
76ch_new_session_cb(const char *client_name, struct nc_session *new_session, void *user_data)
77{
78 int ret = 0;
79 struct nc_pollsession *ps = (struct nc_pollsession *)user_data;
80
81 (void) client_name;
82
83 ret = nc_ps_add_session(ps, new_session);
84 assert_int_equal(ret, 0);
85 return 0;
86}
87
88static void *
romanb6f44032023-06-30 15:07:56 +020089server_thread_ssh(void *arg)
roman5cbb6532023-06-22 12:53:17 +020090{
91 int ret;
92 struct test_state *state = arg;
93 struct nc_pollsession *ps;
roman5cbb6532023-06-22 12:53:17 +020094
romanba93eac2023-07-18 14:36:48 +020095 /* set print clb so we get access to messages */
96 nc_set_print_clb_session(test_msg_callback);
97 buffer[0] = '\0';
98 strcpy(expected, "reconnecting in");
99
roman5cbb6532023-06-22 12:53:17 +0200100 /* prepare data for deleting the call-home client */
Roytakb2794852023-10-18 14:30:22 +0200101 ret = nc_server_config_del_ch_client("ch_ssh", &state->ssh_tree);
roman5cbb6532023-06-22 12:53:17 +0200102 assert_int_equal(ret, 0);
103
104 /* new poll session */
105 ps = nc_ps_new();
106 assert_non_null(ps);
107
108 pthread_barrier_wait(&state->barrier);
109 /* create the call-home client thread */
romanb6f44032023-06-30 15:07:56 +0200110 ret = nc_connect_ch_client_dispatch("ch_ssh", ch_session_acquire_ctx_cb,
roman5cbb6532023-06-22 12:53:17 +0200111 ch_session_release_ctx_cb, NULL, ch_new_session_cb, ps);
112 assert_int_equal(ret, 0);
113
114 /* poll */
115 do {
116 ret = nc_ps_poll(ps, NC_PS_POLL_TIMEOUT, NULL);
117 if (ret & (NC_PSPOLL_TIMEOUT | NC_PSPOLL_NOSESSIONS)) {
118 usleep(500);
119 }
romanba93eac2023-07-18 14:36:48 +0200120 } while (!strlen(buffer));
roman5cbb6532023-06-22 12:53:17 +0200121
122 /* delete the call-home client, the thread should end */
romanb6f44032023-06-30 15:07:56 +0200123 ret = nc_server_config_setup_data(state->ssh_tree);
roman5cbb6532023-06-22 12:53:17 +0200124 assert_int_equal(ret, 0);
125
roman5cbb6532023-06-22 12:53:17 +0200126 nc_ps_clear(ps, 1, NULL);
127 nc_ps_free(ps);
128 nc_server_destroy();
129 return NULL;
130}
131
132static void *
romanb6f44032023-06-30 15:07:56 +0200133client_thread_ssh(void *arg)
roman5cbb6532023-06-22 12:53:17 +0200134{
135 int ret;
136 struct nc_session *session = NULL;
137 struct test_state *state = arg;
138
139 /* skip all hostkey and known_hosts checks */
140 nc_client_ssh_set_knownhosts_mode(NC_SSH_KNOWNHOSTS_SKIP);
141
142 /* set directory where to search for modules */
143 ret = nc_client_set_schema_searchpath(MODULES_DIR);
144 assert_int_equal(ret, 0);
145
146 /* set ssh username */
romanb6f44032023-06-30 15:07:56 +0200147 ret = nc_client_ssh_ch_set_username("test_ch_ssh");
roman5cbb6532023-06-22 12:53:17 +0200148 assert_int_equal(ret, 0);
149
150 /* add client's key pair */
151 ret = nc_client_ssh_ch_add_keypair(TESTS_DIR "/data/id_ed25519.pub", TESTS_DIR "/data/id_ed25519");
152 assert_int_equal(ret, 0);
153
154 /* add call-home bind */
155 ret = nc_client_ssh_ch_add_bind_listen("127.0.0.1", 10009);
156 assert_int_equal(ret, 0);
157
158 pthread_barrier_wait(&state->barrier);
159 /* connect */
160 ret = nc_accept_callhome(NC_ACCEPT_TIMEOUT, NULL, &session);
161 assert_int_equal(ret, 1);
162
163 ret = nc_client_ssh_ch_del_bind("127.0.0.1", 10009);
164 assert_int_equal(ret, 0);
165
166 nc_session_free(session, NULL);
167 return NULL;
168}
169
roman5cbb6532023-06-22 12:53:17 +0200170static int
romanb6f44032023-06-30 15:07:56 +0200171setup_ssh(void **state)
roman5cbb6532023-06-22 12:53:17 +0200172{
173 int ret;
roman5cbb6532023-06-22 12:53:17 +0200174 struct test_state *test_state;
175
176 nc_verbosity(NC_VERB_VERBOSE);
177
178 /* init barrier */
179 test_state = malloc(sizeof *test_state);
180 assert_non_null(test_state);
181
182 ret = pthread_barrier_init(&test_state->barrier, NULL, 2);
183 assert_int_equal(ret, 0);
184
romanb6f44032023-06-30 15:07:56 +0200185 test_state->ssh_tree = NULL;
roman5cbb6532023-06-22 12:53:17 +0200186 *state = test_state;
187
188 /* create new context */
189 ret = ly_ctx_new(MODULES_DIR, 0, &ctx);
190 assert_int_equal(ret, 0);
191
192 /* load default modules into context */
193 ret = nc_server_init_ctx(&ctx);
194 assert_int_equal(ret, 0);
195
196 /* load ietf-netconf-server module and it's imports into context */
197 ret = nc_server_config_load_modules(&ctx);
198 assert_int_equal(ret, 0);
199
romanb6f44032023-06-30 15:07:56 +0200200 /* set call-home address and port */
Roytakb2794852023-10-18 14:30:22 +0200201 ret = nc_server_config_add_ch_address_port(ctx, "ch_ssh", "endpt", NC_TI_LIBSSH, "127.0.0.1", "10009", &test_state->ssh_tree);
roman5cbb6532023-06-22 12:53:17 +0200202 assert_int_equal(ret, 0);
203
romanba93eac2023-07-18 14:36:48 +0200204 /* set connection type to persistent */
Roytakb2794852023-10-18 14:30:22 +0200205 ret = nc_server_config_add_ch_persistent(ctx, "ch_ssh", &test_state->ssh_tree);
roman5cbb6532023-06-22 12:53:17 +0200206 assert_int_equal(ret, 0);
207
romanba93eac2023-07-18 14:36:48 +0200208 /* set the period of the periodic connection type, this should remove the persistent connection type */
Roytakb2794852023-10-18 14:30:22 +0200209 ret = nc_server_config_add_ch_period(ctx, "ch_ssh", 3, &test_state->ssh_tree);
romanb6f44032023-06-30 15:07:56 +0200210 assert_int_equal(ret, 0);
211
212 /* set call-home server hostkey */
Roytakb2794852023-10-18 14:30:22 +0200213 ret = nc_server_config_add_ch_ssh_hostkey(ctx, "ch_ssh", "endpt", "hostkey", TESTS_DIR "/data/key_ecdsa", NULL, &test_state->ssh_tree);
romanb6f44032023-06-30 15:07:56 +0200214 assert_int_equal(ret, 0);
215
216 /* set call-home client's pubkey */
Roytakb2794852023-10-18 14:30:22 +0200217 ret = nc_server_config_add_ch_ssh_user_pubkey(ctx, "ch_ssh", "endpt", "test_ch_ssh", "pubkey", TESTS_DIR "/data/id_ed25519.pub", &test_state->ssh_tree);
roman5cbb6532023-06-22 12:53:17 +0200218 assert_int_equal(ret, 0);
219
220 /* configure the server based on the data */
romanb6f44032023-06-30 15:07:56 +0200221 ret = nc_server_config_setup_data(test_state->ssh_tree);
roman5cbb6532023-06-22 12:53:17 +0200222 assert_int_equal(ret, 0);
223
224 /* initialize server */
225 ret = nc_server_init();
226 assert_int_equal(ret, 0);
227
romaneadc4782023-09-14 10:10:08 +0200228 /* initialize client */
229 ret = nc_client_init();
230 assert_int_equal(ret, 0);
231
roman5cbb6532023-06-22 12:53:17 +0200232 return 0;
233}
234
235static int
romanb6f44032023-06-30 15:07:56 +0200236teardown_ssh(void **state)
roman5cbb6532023-06-22 12:53:17 +0200237{
238 int ret = 0;
239 struct test_state *test_state;
240
241 assert_non_null(state);
242 test_state = *state;
243
244 ret = pthread_barrier_destroy(&test_state->barrier);
245 assert_int_equal(ret, 0);
246
romanb6f44032023-06-30 15:07:56 +0200247 lyd_free_tree(test_state->ssh_tree);
248
249 free(*state);
250 nc_client_destroy();
251 ly_ctx_destroy(ctx);
252
253 return 0;
254}
255
256static void
257test_nc_ch_ssh(void **state)
258{
259 int ret, i;
260 pthread_t tids[2];
261
262 assert_non_null(state);
263
264 /* client */
265 ret = pthread_create(&tids[0], NULL, client_thread_ssh, *state);
266 assert_int_equal(ret, 0);
267
268 /* server */
269 ret = pthread_create(&tids[1], NULL, server_thread_ssh, *state);
270 assert_int_equal(ret, 0);
271
272 for (i = 0; i < 2; i++) {
273 pthread_join(tids[i], NULL);
274 }
275}
276
277static void *
278server_thread_tls(void *arg)
279{
280 int ret;
281 struct test_state *state = arg;
282 struct nc_pollsession *ps;
283
284 /* prepare data for deleting the call-home client */
Roytakb2794852023-10-18 14:30:22 +0200285 ret = nc_server_config_del_ch_client("ch_tls", &state->tls_tree);
romanb6f44032023-06-30 15:07:56 +0200286 assert_int_equal(ret, 0);
287
288 /* new poll session */
289 ps = nc_ps_new();
290 assert_non_null(ps);
291
292 pthread_barrier_wait(&state->barrier);
293 /* create the call-home client thread */
294 ret = nc_connect_ch_client_dispatch("ch_tls", ch_session_acquire_ctx_cb,
295 ch_session_release_ctx_cb, NULL, ch_new_session_cb, ps);
296 assert_int_equal(ret, 0);
297
298 /* poll */
299 do {
300 ret = nc_ps_poll(ps, NC_PS_POLL_TIMEOUT, NULL);
301 if (ret & (NC_PSPOLL_TIMEOUT | NC_PSPOLL_NOSESSIONS)) {
302 usleep(500);
303 }
304 } while (!(ret & NC_PSPOLL_SESSION_TERM));
305
306 /* delete the call-home client, the thread should end */
307 ret = nc_server_config_setup_data(state->tls_tree);
308 assert_int_equal(ret, 0);
309
310 nc_ps_clear(ps, 1, NULL);
311 nc_ps_free(ps);
312 nc_server_destroy();
313 return NULL;
314}
315
316static void *
317client_thread_tls(void *arg)
318{
319 int ret;
320 struct nc_session *session = NULL;
321 struct test_state *state = arg;
322
323 /* set directory where to search for modules */
324 ret = nc_client_set_schema_searchpath(MODULES_DIR);
325 assert_int_equal(ret, 0);
326
327 /* add client's cert */
328 ret = nc_client_tls_ch_set_cert_key_paths(TESTS_DIR "/data/client.crt", TESTS_DIR "/data/client.key");
329 assert_int_equal(ret, 0);
330
331 /* set client ca */
332 ret = nc_client_tls_ch_set_trusted_ca_paths(TESTS_DIR "/data/serverca.pem", NULL);
333 assert_int_equal(ret, 0);
334
335 /* add call-home bind */
336 ret = nc_client_tls_ch_add_bind_listen("127.0.0.1", 10010);
337 assert_int_equal(ret, 0);
338
339 pthread_barrier_wait(&state->barrier);
340 /* connect */
341 ret = nc_accept_callhome(NC_ACCEPT_TIMEOUT, NULL, &session);
342 assert_int_equal(ret, 1);
343
344 ret = nc_client_tls_ch_del_bind("127.0.0.1", 10010);
345 assert_int_equal(ret, 0);
346
347 nc_session_free(session, NULL);
348 return NULL;
349}
350
351static void
352test_nc_ch_tls(void **state)
353{
354 int ret, i;
355 pthread_t tids[2];
356
357 assert_non_null(state);
358
359 /* client */
360 ret = pthread_create(&tids[0], NULL, client_thread_tls, *state);
361 assert_int_equal(ret, 0);
362
363 /* server */
364 ret = pthread_create(&tids[1], NULL, server_thread_tls, *state);
365 assert_int_equal(ret, 0);
366
367 for (i = 0; i < 2; i++) {
368 pthread_join(tids[i], NULL);
369 }
370}
371
372static int
373setup_tls(void **state)
374{
375 int ret;
376 struct test_state *test_state;
377
378 nc_verbosity(NC_VERB_VERBOSE);
379
380 /* init barrier */
381 test_state = malloc(sizeof *test_state);
382 assert_non_null(test_state);
383
384 ret = pthread_barrier_init(&test_state->barrier, NULL, 2);
385 assert_int_equal(ret, 0);
386
387 test_state->tls_tree = NULL;
388 *state = test_state;
389
390 /* create new context */
391 ret = ly_ctx_new(MODULES_DIR, 0, &ctx);
392 assert_int_equal(ret, 0);
393
394 /* load default modules into context */
395 ret = nc_server_init_ctx(&ctx);
396 assert_int_equal(ret, 0);
397
398 /* load ietf-netconf-server module and it's imports into context */
399 ret = nc_server_config_load_modules(&ctx);
400 assert_int_equal(ret, 0);
401
402 /* set call-home address and port */
Roytakb2794852023-10-18 14:30:22 +0200403 ret = nc_server_config_add_ch_address_port(ctx, "ch_tls", "endpt", NC_TI_OPENSSL, "127.0.0.1", "10010", &test_state->tls_tree);
romanb6f44032023-06-30 15:07:56 +0200404 assert_int_equal(ret, 0);
405
406 /* set call-home server certificate */
romane6ec60e2023-10-19 15:21:52 +0200407 ret = nc_server_config_add_ch_tls_server_cert(ctx, "ch_tls", "endpt", TESTS_DIR "/data/server.key", NULL, TESTS_DIR "/data/server.crt", &test_state->tls_tree);
romanb6f44032023-06-30 15:07:56 +0200408 assert_int_equal(ret, 0);
409
410 /* set call-home client end entity certificate */
romane6ec60e2023-10-19 15:21:52 +0200411 ret = nc_server_config_add_ch_tls_client_cert(ctx, "ch_tls", "endpt", "ee-cert", TESTS_DIR "/data/client.crt", &test_state->tls_tree);
romanb6f44032023-06-30 15:07:56 +0200412 assert_int_equal(ret, 0);
413
414 /* set call-home client certificate authority certificate */
romane6ec60e2023-10-19 15:21:52 +0200415 ret = nc_server_config_add_ch_tls_ca_cert(ctx, "ch_tls", "endpt", "ca-cert", TESTS_DIR "/data/serverca.pem", &test_state->tls_tree);
romanb6f44032023-06-30 15:07:56 +0200416 assert_int_equal(ret, 0);
417
418 /* set call-home CTN */
Roytakb2794852023-10-18 14:30:22 +0200419 ret = nc_server_config_add_ch_tls_ctn(ctx, "ch_tls", "endpt", 1,
romanb6f44032023-06-30 15:07:56 +0200420 "04:85:6B:75:D1:1A:86:E0:D8:FE:5B:BD:72:F5:73:1D:07:EA:32:BF:09:11:21:6A:6E:23:78:8E:B6:D5:73:C3:2D",
421 NC_TLS_CTN_SPECIFIED, "ch_client_tls", &test_state->tls_tree);
422 assert_int_equal(ret, 0);
423
424 /* configure the server based on the data */
425 ret = nc_server_config_setup_data(test_state->tls_tree);
426 assert_int_equal(ret, 0);
427
428 /* initialize server */
429 ret = nc_server_init();
430 assert_int_equal(ret, 0);
431
romaneadc4782023-09-14 10:10:08 +0200432 /* initialize client */
433 ret = nc_client_init();
434 assert_int_equal(ret, 0);
435
romanb6f44032023-06-30 15:07:56 +0200436 return 0;
437}
438
439static int
440teardown_tls(void **state)
441{
442 int ret = 0;
443 struct test_state *test_state;
444
445 assert_non_null(state);
446 test_state = *state;
447
448 ret = pthread_barrier_destroy(&test_state->barrier);
449 assert_int_equal(ret, 0);
450
451 lyd_free_tree(test_state->tls_tree);
roman142718b2023-06-29 09:15:29 +0200452
roman5cbb6532023-06-22 12:53:17 +0200453 free(*state);
454 nc_client_destroy();
455 ly_ctx_destroy(ctx);
456
457 return 0;
458}
459
460int
461main(void)
462{
463 const struct CMUnitTest tests[] = {
romanb6f44032023-06-30 15:07:56 +0200464 cmocka_unit_test_setup_teardown(test_nc_ch_ssh, setup_ssh, teardown_ssh),
465 cmocka_unit_test_setup_teardown(test_nc_ch_tls, setup_tls, teardown_tls),
roman5cbb6532023-06-22 12:53:17 +0200466 };
467
468 setenv("CMOCKA_TEST_ABORT", "1", 1);
469 return cmocka_run_group_tests(tests, NULL, NULL);
470}