blob: aae0373bda34c2d74fc8730d3c09eb7306378b4a [file] [log] [blame]
Radek Krejci206fcd62015-10-07 15:42:48 +02001/**
2 * \file session.c
3 * \author Radek Krejci <rkrejci@cesnet.cz>
4 * \brief libnetconf2 - input/output functions
5 *
6 * Copyright (c) 2015 CESNET, z.s.p.o.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
17 * 3. Neither the name of the Company nor the names of its contributors
18 * may be used to endorse or promote products derived from this
19 * software without specific prior written permission.
20 *
21 */
22
23#include <assert.h>
24#include <errno.h>
Radek Krejci695d4fa2015-10-22 13:23:54 +020025#include <fcntl.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020026#include <stdlib.h>
27#include <string.h>
Radek Krejci695d4fa2015-10-22 13:23:54 +020028#include <sys/stat.h>
29#include <sys/types.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020030#include <pthread.h>
31#include <unistd.h>
32
33#include <libyang/libyang.h>
34
35#include "config.h"
36#include "libnetconf.h"
37#include "messages_p.h"
38#include "session_p.h"
Radek Krejci695d4fa2015-10-22 13:23:54 +020039#include "datastore_p.h"
Radek Krejci206fcd62015-10-07 15:42:48 +020040
41#define TIMEOUT_STEP 50
42
Radek Krejci695d4fa2015-10-22 13:23:54 +020043static NC_MSG_TYPE nc_send_hello_(struct nc_session *session);
44static NC_MSG_TYPE nc_recv_hello(struct nc_session *session);
45static NC_MSG_TYPE nc_send_rpc_(struct nc_session *session, struct lyd_node *op);
46
47static char *schema_searchpath = NULL;
48
49/* session configuration */
50static struct {
51 uint16_t hello_timeout; /**< hello-timeout in seconds, default is 600 */
52} cfg = {600};
53
54API int
55nc_schema_searchpath(const char *path)
56{
57 schema_searchpath = strdup(path);
58
59 return schema_searchpath ? 0 : 1;
60}
61
Radek Krejci5686ff72015-10-09 13:33:56 +020062/*
63 * @return 0 - success
64 * -1 - timeout
65 * >0 - error
66 */
67static int
68session_ti_lock(struct nc_session *session, int timeout)
Radek Krejci206fcd62015-10-07 15:42:48 +020069{
70 int r;
Radek Krejci206fcd62015-10-07 15:42:48 +020071
72 if (timeout >= 0) {
73 /* limited waiting for lock */
74 do {
Radek Krejci695d4fa2015-10-22 13:23:54 +020075 r = pthread_mutex_trylock(session->ti_lock);
Radek Krejci206fcd62015-10-07 15:42:48 +020076 if (r == EBUSY) {
77 /* try later until timeout passes */
78 usleep(TIMEOUT_STEP);
79 timeout = timeout - TIMEOUT_STEP;
80 continue;
81 } else if (r) {
82 /* error */
83 ERR("Acquiring session (%u) TI lock failed (%s).", session->id, strerror(r));
Radek Krejci5686ff72015-10-09 13:33:56 +020084 return r;
Radek Krejci206fcd62015-10-07 15:42:48 +020085 } else {
86 /* lock acquired */
Radek Krejci5686ff72015-10-09 13:33:56 +020087 return 0;
Radek Krejci206fcd62015-10-07 15:42:48 +020088 }
89 } while(timeout > 0);
90
Radek Krejci5686ff72015-10-09 13:33:56 +020091 /* timeout has passed */
92 return -1;
Radek Krejci206fcd62015-10-07 15:42:48 +020093 } else {
94 /* infinite waiting for lock */
Radek Krejci695d4fa2015-10-22 13:23:54 +020095 return pthread_mutex_lock(session->ti_lock);
Radek Krejci5686ff72015-10-09 13:33:56 +020096 }
97}
98
99static int
100session_ti_unlock(struct nc_session *session)
101{
Radek Krejci695d4fa2015-10-22 13:23:54 +0200102 return pthread_mutex_unlock(session->ti_lock);
103}
104
105static int
106connect_load_schemas(struct ly_ctx *ctx)
107{
108 int fd;
109 struct lys_module *ietfnc;
110
111 fd = open(SCHEMAS_DIR"ietf-netconf.yin", O_RDONLY);
112 if (fd < 0) {
113 ERR("Loading base NETCONF schema (%s) failed (%s).", SCHEMAS_DIR"ietf-netconf", strerror(errno));
114 return 1;
115 }
116 if (!(ietfnc = lys_read(ctx, fd, LYS_IN_YIN))) {
117 ERR("Loading base NETCONF schema (%s) failed.", SCHEMAS_DIR"ietf-netconf");
118 return 1;
119 }
120 close(fd);
121
122 /* set supported capabilities from ietf-netconf */
123 lys_features_enable(ietfnc, "writable-running");
124 lys_features_enable(ietfnc, "candidate");
125 //lys_features_enable(ietfnc, "confirmed-commit");
126 lys_features_enable(ietfnc, "rollback-on-error");
127 lys_features_enable(ietfnc, "validate");
128 lys_features_enable(ietfnc, "startup");
129 lys_features_enable(ietfnc, "url");
130 lys_features_enable(ietfnc, "xpath");
131
132 return 0;
133}
134
135API struct nc_session *
Radek Krejci1d06db42015-10-22 13:39:49 +0200136nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200137{
138 int r;
139 NC_MSG_TYPE type;
140 const char *str;
141 struct nc_session *session = NULL;
142
143 if (fdin < 0 || fdout < 0) {
144 ERR("%s: Invalid parameter", __func__);
145 return NULL;
146 }
147
148 /* prepare session structure */
149 session = calloc(1, sizeof *session);
150 if (!session) {
151 ERRMEM;
152 return NULL;
153 }
154 session->status = NC_STATUS_STARTING;
155 session->side = NC_CLIENT;
156 session->ti_type = NC_TI_FD;
157 session->ti.fd.in = fdin;
158 session->ti.fd.out = fdout;
159
160 /* transport lock */
161 session->ti_lock = malloc(sizeof *session->ti_lock);
162 if (!session->ti_lock) {
163 ERRMEM;
164 return NULL;
165 }
166 pthread_mutex_init(session->ti_lock, NULL);
167
168 /* YANG context for the session */
169 if (ctx) {
170 session->flags |= NC_SESSION_SHAREDCTX;
171 session->ctx = ctx;
172
173 /* check presence of the required schemas */
174 if (!ly_ctx_get_module(session->ctx, "ietf-netconf", NULL)) {
175 str = ly_ctx_get_searchdir(session->ctx);
176 ly_ctx_set_searchdir(session->ctx, SCHEMAS_DIR);
177 r = connect_load_schemas(session->ctx);
178 ly_ctx_set_searchdir(session->ctx, str);
179
180 if (r) {
181 goto error;
182 }
183 }
184 } else {
185 session->ctx = ly_ctx_new(SCHEMAS_DIR);
186
187 /* load basic NETCONF schemas required for libnetconf work */
188 if (connect_load_schemas(session->ctx)) {
189 goto error;
190 }
191
192 ly_ctx_set_searchdir(session->ctx, schema_searchpath);
193 }
194
195 /* NETCONF handshake */
196 type = nc_send_hello_(session);
197 if (type != NC_MSG_HELLO) {
198 goto error;
199 }
200
201 type = nc_recv_hello(session);
202 if (type != NC_MSG_HELLO) {
203 goto error;
204 }
205
206 session->status = NC_STATUS_RUNNING;
207 return session;
208
209error:
210 nc_session_free(session);
211 return NULL;
212}
213
214#ifdef ENABLE_LIBSSH
215
216API struct nc_session *
Radek Krejci1d06db42015-10-22 13:39:49 +0200217nc_connect_ssh(const char *host, unsigned short port, const char* username, struct ly_ctx *ctx)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200218{
219 (void) host;
220 (void) port;
221 (void) username;
222 (void) ctx;
223
224 return NULL;
225}
226
227API struct nc_session *
Radek Krejci1d06db42015-10-22 13:39:49 +0200228nc_connect_libssh(ssh_session ssh_session, struct ly_ctx *ctx)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200229{
230 (void) ssh_session;
231 (void) ctx;
232
233 return NULL;
234}
235
236API struct nc_session *
Radek Krejci1d06db42015-10-22 13:39:49 +0200237nc_connect_ssh_channel(struct nc_session *session)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200238{
239 (void) session;
240
241 return NULL;
242}
243
244#endif /* ENABLE_LIBSSH */
245
246#ifdef ENABLE_TLS
247
248API struct nc_session *
Radek Krejci1d06db42015-10-22 13:39:49 +0200249nc_connect_tls(const char *host, unsigned short port, const char *username, struct ly_ctx *ctx)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200250{
251 (void) host;
252 (void) port;
253 (void) username;
254 (void) ctx;
255
256 return NULL;
257}
258
259API struct nc_session *
Radek Krejci1d06db42015-10-22 13:39:49 +0200260nc_connect_libssl(SSL *tls, struct ly_ctx *ctx)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200261{
262 (void) tls;
263 (void) ctx;
264
265 return NULL;
266}
267
268#endif /* ENABLE_TLS */
269
270API void
271nc_session_free(struct nc_session *session)
272{
273 int r, i;
274 int multisession = 0; /* flag for more NETCONF session on a single SSH session */
275 struct nc_session *siter;
276 struct nc_notif_cont *ntfiter;
277 struct nc_reply_cont *rpliter;
278 struct lyd_node *close_rpc;
279 struct lys_module *ietfnc;
280 void *p;
281
282 if (!session || session->status < NC_STATUS_INVALID) {
283 return;
284 }
285
286 /* mark session for closing */
287 do {
288 r = session_ti_lock(session, 0);
289 } while (r < 0);
290 if (r) {
291 return;
292 }
293
294 /* stop notifications loop if any */
295 if (session->notif) {
296 pthread_cancel(*session->notif);
297 pthread_join(*session->notif, NULL);
298 }
299
300 if (session->side == NC_CLIENT && session->status == NC_STATUS_RUNNING) {
301 /* cleanup message queues */
302 /* notifications */
303 for (ntfiter = session->notifs; ntfiter; ) {
304 nc_notif_free(ntfiter->msg);
305
306 p = ntfiter;
307 ntfiter = ntfiter->next;
308 free(p);
309 }
310
311 /* rpc replies */
312 for (rpliter = session->replies; rpliter; ) {
313 nc_reply_free(rpliter->msg);
314
315 p = rpliter;
316 rpliter = rpliter->next;
317 free(p);
318 }
319
320 /* send closing info to the other side */
321 ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
322 if (!ietfnc) {
323 WRN("%s: Missing ietf-netconf schema in context (session %u), unable to send <close-session\\>", session->id);
324 } else {
325 close_rpc = lyd_new(NULL, ietfnc, "close-session");
326 nc_send_rpc_(session, close_rpc);
327 lyd_free(close_rpc);
328 }
329
330 /* list of server's capabilities */
331 if (session->cpblts) {
332 for (i = 0; session->cpblts[i]; i++) {
333 lydict_remove(session->ctx, session->cpblts[i]);
334 }
335 free(session->cpblts);
336 }
337 }
338
339 session->status = NC_STATUS_CLOSING;
340
341 /* transport implementation cleanup */
342 switch (session->ti_type) {
343 case NC_TI_FD:
344 /* nothing needed - file descriptors were provided by caller,
345 * so it is up to the caller to close them correctly
346 * TODO use callbacks
347 */
348 break;
349
350#ifdef ENABLE_LIBSSH
351 case NC_TI_LIBSSH:
352 ssh_channel_free(session->ti.libssh.channel);
353 /* There can be multiple NETCONF sessions on the same SSH session (NETCONF session maps to
354 * SSH channel). So destroy the SSH session only if there is no other NETCONF session using
355 * it.
356 */
357 if (!session->ti.libssh.next) {
358 ssh_disconnect(session->ti.libssh.session);
359 ssh_free(session->ti.libssh.session);
360 } else {
361 /* multiple NETCONF sessions on a single SSH session */
362 multisession = 1;
363 /* remove the session from the list */
364 for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next);
365 if (siter->ti.libssh.next == session->ti.libssh.next) {
366 /* there will be only one session */
367 siter->ti.libssh.next = NULL;
368 } else {
369 /* there are still multiple sessions, keep the ring list */
370 siter->ti.libssh.next = session->ti.libssh.next;
371 }
372 }
373 break;
374#endif
375
376#ifdef ENABLE_TLS
377 case NC_TI_OPENSSL:
378 SSL_shutdown(session->ti.tls);
379 SSL_free(session->ti.tls);
380 break;
381#endif
382 }
383
384 /* final cleanup */
385 if (multisession) {
386 session_ti_unlock(session);
387 } else {
388 pthread_mutex_destroy(session->ti_lock);
389 free(session->ti_lock);
390 }
391
392 if (!(session->flags & NC_SESSION_SHAREDCTX)) {
393 ly_ctx_destroy(session->ctx);
394 }
395
396 free(session);
397}
398
399static int
400parse_cpblts(struct lyxml_elem *xml, const char ***list)
401{
402 struct lyxml_elem *cpblt;
403 int ver = -1;
404 int i = 0;
405
406 if (list) {
407 /* get the storage for server's capabilities */
408 LY_TREE_FOR(xml->child, cpblt) {
409 i++;
410 }
411 *list = calloc(i, sizeof **list);
412 if (!*list) {
413 ERRMEM;
414 return -1;
415 }
416 i = 0;
417 }
418
419 LY_TREE_FOR(xml->child, cpblt) {
420 if (strcmp(cpblt->name, "capability") && cpblt->ns && cpblt->ns->value &&
421 !strcmp(cpblt->ns->value, NC_NS_BASE)) {
422 ERR("Unexpected <%s> element in client's <hello>.", cpblt->name);
423 return -1;
424 } else if (!cpblt->ns || !cpblt->ns->value || strcmp(cpblt->ns->value, NC_NS_BASE)) {
425 continue;
426 }
427
428 /* detect NETCONF version */
429 if (ver < 0 && !strcmp(cpblt->content, "urn:ietf:params:netconf:base:1.0")) {
430 ver = 0;
431 } else if (ver < 1 && !strcmp(cpblt->content, "urn:ietf:params:netconf:base:1.1")) {
432 ver = 1;
433 }
434
435 /* store capabilities */
436 if (list) {
437 (*list)[i] = cpblt->content;
438 cpblt->content = NULL;
439 i++;
440 }
441 }
442
443 if (ver == -1) {
444 ERR("Peer does not support compatible NETCONF version.");
445 }
446
447 return ver;
448}
449
450static NC_MSG_TYPE
451nc_recv_hello(struct nc_session *session)
452{
453 struct lyxml_elem *xml = NULL, *node;
454 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
455 int ver = -1;
456 char *str;
457 long long int id;
458 int flag = 0;
459
460 msgtype = nc_read_msg(session, cfg.hello_timeout * 1000, &xml);
461
462 switch(msgtype) {
463 case NC_MSG_HELLO:
464 /* parse <hello> data */
465 if (session->side == NC_SERVER) {
466 /* get know NETCONF version */
467 LY_TREE_FOR(xml->child, node) {
468 if (!node->ns || !node->ns->value || strcmp(node->ns->value, NC_NS_BASE)) {
469 continue;
470 } else if (strcmp(node->name, "capabilities")) {
471 ERR("Unexpected <%s> element in client's <hello>.", node->name);
472 goto error;
473 }
474
475 if (flag) {
476 /* multiple capabilities elements */
477 ERR("Invalid <hello> message (multiple <capabilities> elements)");
478 goto error;
479 }
480 flag = 1;
481
482 if ((ver = parse_cpblts(node, NULL)) < 0) {
483 goto error;
484 }
485 session->version = ver;
486 }
487 } else { /* NC_CLIENT */
488 LY_TREE_FOR(xml->child, node) {
489 if (!node->ns || !node->ns->value || strcmp(node->ns->value, NC_NS_BASE)) {
490 continue;
491 } else if (!strcmp(node->name, "session-id")) {
492 if (!node->content || !strlen(node->content)) {
493 ERR("No value of <session-id> element in server's <hello>");
494 goto error;
495 }
496 str = NULL;
497 id = strtoll(node->content, &str, 10);
498 if (*str || id < 1 || id > UINT32_MAX) {
499 ERR("Invalid value of <session-id> element in server's <hello>");
500 goto error;
501 }
502 session->id = (uint32_t)id;
503 continue;
504 } else if (strcmp(node->name, "capabilities")) {
505 ERR("Unexpected <%s> element in client's <hello>.", node->name);
506 goto error;
507 }
508
509 if (flag) {
510 /* multiple capabilities elements */
511 ERR("Invalid <hello> message (multiple <capabilities> elements)");
512 goto error;
513 }
514 flag = 1;
515
516 if ((ver = parse_cpblts(node, NULL)) < 0) {
517 goto error;
518 }
519 session->version = ver;
520 }
521
522 if (!session->id) {
523 ERR("Missing <session-id> in server's <hello>");
524 goto error;
525 }
526 }
527 break;
528 case NC_MSG_ERROR:
529 /* nothing special, just pass it out */
530 break;
531 default:
532 ERR("Unexpected message received instead of <hello>.");
533 msgtype = NC_MSG_ERROR;
534 }
535
536 /* cleanup */
537 lyxml_free_elem(session->ctx, xml);
538
539 return msgtype;
540
541error:
542 /* cleanup */
543 lyxml_free_elem(session->ctx, xml);
544
545 return NC_MSG_ERROR;
Radek Krejci5686ff72015-10-09 13:33:56 +0200546}
547
548API NC_MSG_TYPE
Radek Krejci695d4fa2015-10-22 13:23:54 +0200549nc_recv_rpc(struct nc_session *session, int timeout, struct nc_rpc_server **rpc)
Radek Krejci5686ff72015-10-09 13:33:56 +0200550{
551 int r;
552 struct lyxml_elem *xml = NULL;
553 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
554
555 if (!session || !rpc) {
556 ERR("%s: Invalid parameter", __func__);
557 return NC_MSG_ERROR;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200558 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_SERVER) {
559 ERR("%s: invalid session to receive RPCs.", __func__);
Radek Krejci5686ff72015-10-09 13:33:56 +0200560 return NC_MSG_ERROR;
561 }
562
563 r = session_ti_lock(session, timeout);
564 if (r > 0) {
565 /* error */
566 return NC_MSG_ERROR;
567 } else if (r < 0) {
568 /* timeout */
569 return NC_MSG_WOULDBLOCK;
Radek Krejci206fcd62015-10-07 15:42:48 +0200570 }
571
572 msgtype = nc_read_msg(session, timeout, &xml);
Radek Krejci5686ff72015-10-09 13:33:56 +0200573 session_ti_unlock(session);
Radek Krejci206fcd62015-10-07 15:42:48 +0200574
Radek Krejci5686ff72015-10-09 13:33:56 +0200575 switch(msgtype) {
576 case NC_MSG_RPC:
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200577 *rpc = malloc(sizeof **rpc);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200578 (*rpc)->type = NC_RPC_SERVER;
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200579 (*rpc)->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200580 (*rpc)->tree = lyd_parse_xml(session->ctx, xml, 0);
581 (*rpc)->root = xml;
582 break;
583 case NC_MSG_HELLO:
584 ERR("SESSION %u: Received another <hello> message.", session->id);
585 goto error;
586 case NC_MSG_REPLY:
587 ERR("SESSION %u: Received <rpc-reply> from NETCONF client.", session->id);
588 goto error;
589 case NC_MSG_NOTIF:
590 ERR("SESSION %u: Received <notification> from NETCONF client.", session->id);
591 goto error;
592 default:
593 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
594 * NC_MSG_NONE is not returned by nc_read_msg()
595 */
596 break;
597 }
Radek Krejci0dc69f72015-10-08 15:32:09 +0200598
Radek Krejci206fcd62015-10-07 15:42:48 +0200599 return msgtype;
Radek Krejci5686ff72015-10-09 13:33:56 +0200600
601error:
602
603 /* cleanup */
604 lyxml_free_elem(session->ctx, xml);
605
606 return NC_MSG_ERROR;
607}
608
609API NC_MSG_TYPE
Radek Krejci695d4fa2015-10-22 13:23:54 +0200610nc_recv_reply(struct nc_session *session, int timeout, struct nc_reply **reply)
Radek Krejci5686ff72015-10-09 13:33:56 +0200611{
612 int r;
613 struct lyxml_elem *xml;
614 struct nc_reply_cont *cont_r;
615 struct nc_notif_cont **cont_n;
616 struct nc_notif *notif;
617 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
618
619 if (!session || !reply) {
620 ERR("%s: Invalid parameter", __func__);
621 return NC_MSG_ERROR;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200622 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
623 ERR("%s: invalid session to receive RPC replies.", __func__);
Radek Krejci5686ff72015-10-09 13:33:56 +0200624 return NC_MSG_ERROR;
625 }
626
627 do {
628 if (msgtype && session->notif) {
629 /* second run, wait and give a chance to nc_recv_notif() */
630 usleep(TIMEOUT_STEP);
631 timeout = timeout - (TIMEOUT_STEP);
632 }
633 r = session_ti_lock(session, timeout);
634 if (r > 0) {
635 /* error */
636 return NC_MSG_ERROR;
637 } else if (r < 0) {
638 /* timeout */
639 return NC_MSG_WOULDBLOCK;
640 }
641
642 /* try to get message from the session's queue */
643 if (session->notifs) {
644 cont_r = session->replies;
645 session->replies = cont_r->next;
646
647 session_ti_unlock(session);
648
649 *reply = cont_r->msg;
650 free(cont_r);
651
652 return NC_MSG_REPLY;
653 }
654
655 /* read message from wire */
656 msgtype = nc_read_msg(session, timeout, &xml);
657 if (msgtype == NC_MSG_NOTIF) {
658 if (!session->notif) {
659 session_ti_unlock(session);
660 ERR("SESSION %u: Received Notification but session is not subscribed.", session->id);
661 goto error;
662 }
663
664 /* create notification object */
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200665 notif = malloc(sizeof *notif);
666 notif->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200667 notif->tree = lyd_parse_xml(session->ctx, xml, 0);
668 notif->root = xml;
669
670 /* store the message for nc_recv_notif() */
671 cont_n = &session->notifs;
672 while(*cont_n) {
673 cont_n = &((*cont_n)->next);
674 }
675 *cont_n = malloc(sizeof **cont_n);
676 (*cont_n)->msg = notif;
677 (*cont_n)->next = NULL;
678 }
679
680 session_ti_unlock(session);
681
682 switch(msgtype) {
683 case NC_MSG_REPLY:
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200684 *reply = malloc(sizeof **reply);
685 (*reply)->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200686 (*reply)->tree = lyd_parse_xml(session->ctx, xml, 0);
687 (*reply)->root = xml;
688 break;
689 case NC_MSG_HELLO:
690 ERR("SESSION %u: Received another <hello> message.", session->id);
691 goto error;
692 case NC_MSG_RPC:
693 ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
694 goto error;
695 default:
696 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
697 * NC_MSG_NOTIF already handled before the switch;
698 * NC_MSG_NONE is not returned by nc_read_msg()
699 */
700 break;
701 }
702
703 } while(msgtype == NC_MSG_NOTIF);
704
705 return msgtype;
706
707error:
708
709 /* cleanup */
710 lyxml_free_elem(session->ctx, xml);
711
712 return NC_MSG_ERROR;
713}
714
715API NC_MSG_TYPE
Radek Krejci695d4fa2015-10-22 13:23:54 +0200716nc_recv_notif(struct nc_session *session, int timeout, struct nc_notif **notif)
Radek Krejci5686ff72015-10-09 13:33:56 +0200717{
718 int r;
719 struct lyxml_elem *xml;
720 struct nc_notif_cont *cont_n;
721 struct nc_reply_cont **cont_r;
722 struct nc_reply *reply;
723 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
724
725 if (!session || !notif) {
726 ERR("%s: Invalid parameter", __func__);
727 return NC_MSG_ERROR;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200728 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
729 ERR("%s: invalid session to receive Notifications.", __func__);
Radek Krejci5686ff72015-10-09 13:33:56 +0200730 return NC_MSG_ERROR;
731 }
732
733 do {
734 if (msgtype) {
735 /* second run, wait and give a chance to nc_recv_reply() */
736 usleep(TIMEOUT_STEP);
737 timeout = timeout - (TIMEOUT_STEP);
738 }
739 r = session_ti_lock(session, timeout);
740 if (r > 0) {
741 /* error */
742 return NC_MSG_ERROR;
743 } else if (r < 0) {
744 /* timeout */
745 return NC_MSG_WOULDBLOCK;
746 }
747
748 /* try to get message from the session's queue */
749 if (session->notifs) {
750 cont_n = session->notifs;
751 session->notifs = cont_n->next;
752
753 session_ti_unlock(session);
754
755 *notif = cont_n->msg;
756 free(cont_n);
757
758 return NC_MSG_NOTIF;
759 }
760
761 /* read message from wire */
762 msgtype = nc_read_msg(session, timeout, &xml);
763 if (msgtype == NC_MSG_REPLY) {
764 /* create reply object */
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200765 reply = malloc(sizeof *reply);
766 reply->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200767 reply->tree = lyd_parse_xml(session->ctx, xml, 0);
768 reply->root = xml;
769
770 /* store the message for nc_recv_reply() */
771 cont_r = &session->replies;
772 while(*cont_r) {
773 cont_r = &((*cont_r)->next);
774 }
775 *cont_r = malloc(sizeof **cont_r);
776 (*cont_r)->msg = reply;
777 (*cont_r)->next = NULL;
778 }
779
780 session_ti_unlock(session);
781
782 switch(msgtype) {
783 case NC_MSG_NOTIF:
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200784 *notif = malloc(sizeof **notif);
785 (*notif)->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200786 (*notif)->tree = lyd_parse_xml(session->ctx, xml, 0);
787 (*notif)->root = xml;
788 break;
789 case NC_MSG_HELLO:
790 ERR("SESSION %u: Received another <hello> message.", session->id);
791 goto error;
792 case NC_MSG_RPC:
793 ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
794 goto error;
795 default:
796 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
797 * NC_MSG_REPLY already handled before the switch;
798 * NC_MSG_NONE is not returned by nc_read_msg()
799 */
800 break;
801 }
802
803 } while(msgtype == NC_MSG_REPLY);
804
805 return msgtype;
806
807error:
808
809 /* cleanup */
810 lyxml_free_elem(session->ctx, xml);
811
812 return NC_MSG_ERROR;
Radek Krejci206fcd62015-10-07 15:42:48 +0200813}
Radek Krejcife0b3472015-10-12 13:43:42 +0200814
Radek Krejci695d4fa2015-10-22 13:23:54 +0200815static NC_MSG_TYPE
816nc_send_hello_(struct nc_session *session)
817{
818 int r;
819 char **cpblts;
820
821 if (session->side == NC_CLIENT) {
822 /* client side hello - send only NETCONF base capabilities */
823 cpblts = malloc(3 * sizeof *cpblts);
824 cpblts[0] = "urn:ietf:params:netconf:base:1.0";
825 cpblts[1] = "urn:ietf:params:netconf:base:1.1";
826 cpblts[2] = NULL;
827
828 r = nc_write_msg(session, NC_MSG_HELLO, cpblts, NULL);
829 free(cpblts);
830 }
831
832
833 if (r) {
834 return NC_MSG_ERROR;
835 } else {
836 return NC_MSG_HELLO;
837 }
838}
839
840static NC_MSG_TYPE
841nc_send_rpc_(struct nc_session *session, struct lyd_node *op)
Radek Krejcife0b3472015-10-12 13:43:42 +0200842{
843 int r;
844
Radek Krejci695d4fa2015-10-22 13:23:54 +0200845 r = nc_write_msg(session, NC_MSG_RPC, op, NULL);
Radek Krejcife0b3472015-10-12 13:43:42 +0200846
847 if (r) {
848 return NC_MSG_ERROR;
849 } else {
850 return NC_MSG_RPC;
851 }
852}
853
Radek Krejci695d4fa2015-10-22 13:23:54 +0200854API NC_MSG_TYPE
855nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc)
856{
857 NC_MSG_TYPE r;
858 struct nc_rpc_lock *rpc_lock;
859 struct nc_rpc_getconfig *rpc_gc;
860 struct lyd_node *data, *node;
861 struct lys_module *ietfnc;
862
863 if (!session || !rpc || rpc->type == NC_RPC_SERVER) {
864 ERR("%s: Invalid parameter", __func__);
865 return NC_MSG_ERROR;
866 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
867 ERR("%s: invalid session to send RPCs.", __func__);
868 return NC_MSG_ERROR;
869 }
870
871 ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
872 if (!ietfnc) {
873 ERR("%s: Missing ietf-netconf schema in context (session %u)", session->id);
874 return NC_MSG_ERROR;
875 }
876
877 switch(rpc->type) {
878 case NC_RPC_GETCONFIG:
879 rpc_gc = (struct nc_rpc_getconfig *)rpc;
880
881 data = lyd_new(NULL, ietfnc, "get-config");
882 node = lyd_new(data, ietfnc, "source");
883 node = lyd_new_leaf_str(node, ietfnc, ncds2str[rpc_gc->source], LY_TYPE_EMPTY, NULL);
884 if (!node) {
885 lyd_free(data);
886 return NC_MSG_ERROR;
887 }
888 if (rpc_gc->filter) {
889 if (rpc_gc->filter->type == NC_FILTER_SUBTREE) {
890 node = lyd_new_anyxml(data, ietfnc, "filter", rpc_gc->filter->data);
891 lyd_insert_attr(node, "type", "subtree");
892 } else if (rpc_gc->filter->type == NC_FILTER_XPATH) {
893 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
894 /* TODO - handle namespaces from XPATH query */
895 lyd_insert_attr(node, "type", "xpath");
896 lyd_insert_attr(node, "select", rpc_gc->filter->data);
897 }
898 }
899 break;
900 case NC_RPC_LOCK:
901 rpc_lock = (struct nc_rpc_lock *)rpc;
902
903 data = lyd_new(NULL, ietfnc, "lock");
904 node = lyd_new(data, ietfnc, "target");
905 node = lyd_new_leaf_str(node, ietfnc, ncds2str[rpc_lock->target], LY_TYPE_EMPTY, NULL);
906 if (!node) {
907 lyd_free(data);
908 return NC_MSG_ERROR;
909 }
910 break;
911 case NC_RPC_UNLOCK:
912 rpc_lock = (struct nc_rpc_lock *)rpc;
913
914 data = lyd_new(NULL, ietfnc, "unlock");
915 node = lyd_new(data, ietfnc, "target");
916 node = lyd_new_leaf_str(node, ietfnc, ncds2str[rpc_lock->target], LY_TYPE_EMPTY, NULL);
917 if (!node) {
918 lyd_free(data);
919 return NC_MSG_ERROR;
920 }
921 break;
922 }
923
924 r = session_ti_lock(session, 0);
925 if (r != 0) {
926 /* error or blocking */
927 r = NC_MSG_WOULDBLOCK;
928 } else {
929 /* send RPC */
930 r = nc_send_rpc_(session, data);
931 }
932 session_ti_unlock(session);
933
934 lyd_free(data);
935 return r;
936}