blob: 65e9a4e8f9a82f4713db276b798844cc6427910e [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 Krejciac6d3472015-10-22 15:47:18 +020026#include <netdb.h>
27#include <pthread.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020028#include <stdlib.h>
29#include <string.h>
Radek Krejciac6d3472015-10-22 15:47:18 +020030#include <sys/socket.h>
Radek Krejci695d4fa2015-10-22 13:23:54 +020031#include <sys/stat.h>
32#include <sys/types.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020033#include <unistd.h>
34
35#include <libyang/libyang.h>
36
Radek Krejci206fcd62015-10-07 15:42:48 +020037#include "libnetconf.h"
Radek Krejci206fcd62015-10-07 15:42:48 +020038
39#define TIMEOUT_STEP 50
40
Radek Krejci695d4fa2015-10-22 13:23:54 +020041static NC_MSG_TYPE nc_send_hello_(struct nc_session *session);
42static NC_MSG_TYPE nc_recv_hello(struct nc_session *session);
43static NC_MSG_TYPE nc_send_rpc_(struct nc_session *session, struct lyd_node *op);
44
45static char *schema_searchpath = NULL;
46
47/* session configuration */
48static struct {
49 uint16_t hello_timeout; /**< hello-timeout in seconds, default is 600 */
50} cfg = {600};
51
52API int
53nc_schema_searchpath(const char *path)
54{
55 schema_searchpath = strdup(path);
56
57 return schema_searchpath ? 0 : 1;
58}
59
Radek Krejci5686ff72015-10-09 13:33:56 +020060/*
61 * @return 0 - success
62 * -1 - timeout
63 * >0 - error
64 */
65static int
66session_ti_lock(struct nc_session *session, int timeout)
Radek Krejci206fcd62015-10-07 15:42:48 +020067{
68 int r;
Radek Krejci206fcd62015-10-07 15:42:48 +020069
70 if (timeout >= 0) {
71 /* limited waiting for lock */
72 do {
Radek Krejci695d4fa2015-10-22 13:23:54 +020073 r = pthread_mutex_trylock(session->ti_lock);
Radek Krejci206fcd62015-10-07 15:42:48 +020074 if (r == EBUSY) {
75 /* try later until timeout passes */
76 usleep(TIMEOUT_STEP);
77 timeout = timeout - TIMEOUT_STEP;
78 continue;
79 } else if (r) {
80 /* error */
81 ERR("Acquiring session (%u) TI lock failed (%s).", session->id, strerror(r));
Radek Krejci5686ff72015-10-09 13:33:56 +020082 return r;
Radek Krejci206fcd62015-10-07 15:42:48 +020083 } else {
84 /* lock acquired */
Radek Krejci5686ff72015-10-09 13:33:56 +020085 return 0;
Radek Krejci206fcd62015-10-07 15:42:48 +020086 }
87 } while(timeout > 0);
88
Radek Krejci5686ff72015-10-09 13:33:56 +020089 /* timeout has passed */
90 return -1;
Radek Krejci206fcd62015-10-07 15:42:48 +020091 } else {
92 /* infinite waiting for lock */
Radek Krejci695d4fa2015-10-22 13:23:54 +020093 return pthread_mutex_lock(session->ti_lock);
Radek Krejci5686ff72015-10-09 13:33:56 +020094 }
95}
96
97static int
98session_ti_unlock(struct nc_session *session)
99{
Radek Krejci695d4fa2015-10-22 13:23:54 +0200100 return pthread_mutex_unlock(session->ti_lock);
101}
102
Radek Krejciac6d3472015-10-22 15:47:18 +0200103int
104handshake(struct nc_session *session)
105{
106 NC_MSG_TYPE type;
107
108 type = nc_send_hello_(session);
109 if (type != NC_MSG_HELLO) {
110 return 1;
111 }
112
113 type = nc_recv_hello(session);
114 if (type != NC_MSG_HELLO) {
115 return 1;
116 }
117
118 return 0;
119}
120
Radek Krejci695d4fa2015-10-22 13:23:54 +0200121static int
122connect_load_schemas(struct ly_ctx *ctx)
123{
124 int fd;
125 struct lys_module *ietfnc;
126
127 fd = open(SCHEMAS_DIR"ietf-netconf.yin", O_RDONLY);
128 if (fd < 0) {
129 ERR("Loading base NETCONF schema (%s) failed (%s).", SCHEMAS_DIR"ietf-netconf", strerror(errno));
130 return 1;
131 }
132 if (!(ietfnc = lys_read(ctx, fd, LYS_IN_YIN))) {
133 ERR("Loading base NETCONF schema (%s) failed.", SCHEMAS_DIR"ietf-netconf");
134 return 1;
135 }
136 close(fd);
137
138 /* set supported capabilities from ietf-netconf */
139 lys_features_enable(ietfnc, "writable-running");
140 lys_features_enable(ietfnc, "candidate");
141 //lys_features_enable(ietfnc, "confirmed-commit");
142 lys_features_enable(ietfnc, "rollback-on-error");
143 lys_features_enable(ietfnc, "validate");
144 lys_features_enable(ietfnc, "startup");
145 lys_features_enable(ietfnc, "url");
146 lys_features_enable(ietfnc, "xpath");
147
148 return 0;
149}
150
Radek Krejciac6d3472015-10-22 15:47:18 +0200151struct nc_session *
152connect_init(struct ly_ctx *ctx)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200153{
Radek Krejci695d4fa2015-10-22 13:23:54 +0200154 struct nc_session *session = NULL;
Radek Krejciac6d3472015-10-22 15:47:18 +0200155 const char *str;
156 int r;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200157
158 /* prepare session structure */
159 session = calloc(1, sizeof *session);
160 if (!session) {
161 ERRMEM;
162 return NULL;
163 }
164 session->status = NC_STATUS_STARTING;
165 session->side = NC_CLIENT;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200166
167 /* transport lock */
168 session->ti_lock = malloc(sizeof *session->ti_lock);
169 if (!session->ti_lock) {
170 ERRMEM;
171 return NULL;
172 }
173 pthread_mutex_init(session->ti_lock, NULL);
174
175 /* YANG context for the session */
176 if (ctx) {
177 session->flags |= NC_SESSION_SHAREDCTX;
178 session->ctx = ctx;
179
180 /* check presence of the required schemas */
181 if (!ly_ctx_get_module(session->ctx, "ietf-netconf", NULL)) {
182 str = ly_ctx_get_searchdir(session->ctx);
183 ly_ctx_set_searchdir(session->ctx, SCHEMAS_DIR);
184 r = connect_load_schemas(session->ctx);
185 ly_ctx_set_searchdir(session->ctx, str);
186
187 if (r) {
Radek Krejciac6d3472015-10-22 15:47:18 +0200188 nc_session_free(session);
189 return NULL;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200190 }
191 }
192 } else {
193 session->ctx = ly_ctx_new(SCHEMAS_DIR);
194
195 /* load basic NETCONF schemas required for libnetconf work */
196 if (connect_load_schemas(session->ctx)) {
Radek Krejciac6d3472015-10-22 15:47:18 +0200197 nc_session_free(session);
198 return NULL;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200199 }
200
201 ly_ctx_set_searchdir(session->ctx, schema_searchpath);
202 }
203
Radek Krejciac6d3472015-10-22 15:47:18 +0200204 return session;
205}
206
207API struct nc_session *
208nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx)
209{
210 struct nc_session *session = NULL;
211
212 if (fdin < 0 || fdout < 0) {
213 ERR("%s: Invalid parameter", __func__);
214 return NULL;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200215 }
216
Radek Krejciac6d3472015-10-22 15:47:18 +0200217 /* prepare session structure */
218 session = connect_init(ctx);
219 if (!session) {
220 return NULL;
221 }
222
223 /* transport specific data */
224 session->ti_type = NC_TI_FD;
225 session->ti.fd.in = fdin;
226 session->ti.fd.out = fdout;
227
228 /* NETCONF handshake */
229 if (handshake(session)) {
Radek Krejci695d4fa2015-10-22 13:23:54 +0200230 goto error;
231 }
232
233 session->status = NC_STATUS_RUNNING;
234 return session;
235
236error:
237 nc_session_free(session);
238 return NULL;
239}
240
Radek Krejciac6d3472015-10-22 15:47:18 +0200241int
242connect_getsocket(const char* host, unsigned short port)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200243{
Radek Krejciac6d3472015-10-22 15:47:18 +0200244 int sock = -1;
245 int i;
246 struct addrinfo hints, *res_list, *res;
247 char port_s[6]; /* length of string representation of short int */
Radek Krejci695d4fa2015-10-22 13:23:54 +0200248
Radek Krejciac6d3472015-10-22 15:47:18 +0200249 snprintf(port_s, 6, "%u", port);
250
251 /* Connect to a server */
252 memset(&hints, 0, sizeof hints);
253 hints.ai_family = AF_UNSPEC;
254 hints.ai_socktype = SOCK_STREAM;
255 hints.ai_protocol = IPPROTO_TCP;
256 i = getaddrinfo(host, port_s, &hints, &res_list);
257 if (i != 0) {
258 ERR("Unable to translate the host address (%s).", gai_strerror(i));
259 return -1;
260 }
261
262 for (i = 0, res = res_list; res != NULL; res = res->ai_next) {
263 sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
264 if (sock == -1) {
265 /* socket was not created, try another resource */
266 i = errno;
267 goto errloop;
268 }
269
270 if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
271 /* network connection failed, try another resource */
272 i = errno;
273 close(sock);
274 sock = -1;
275 goto errloop;
276 }
277
278 /* we're done, network connection established */
279 break;
280errloop:
281 VRB("Unable to connect to %s:%s over %s (%s).", host, port,
282 (res->ai_family == AF_INET6) ? "IPv6" : "IPv4", strerror(i));
283 continue;
284 }
285
286 if (sock == -1) {
287 ERR("Unable to connect to %s:%s.", host, port);
288 } else {
289 VRB("Successfully connected to %s:%s over %s", host, port, (res->ai_family == AF_INET6) ? "IPv6" : "IPv4");
290 }
291 freeaddrinfo(res_list);
292
293 return sock;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200294}
295
Radek Krejci695d4fa2015-10-22 13:23:54 +0200296#ifdef ENABLE_TLS
297
298API struct nc_session *
Radek Krejci1d06db42015-10-22 13:39:49 +0200299nc_connect_tls(const char *host, unsigned short port, const char *username, struct ly_ctx *ctx)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200300{
301 (void) host;
302 (void) port;
303 (void) username;
304 (void) ctx;
305
306 return NULL;
307}
308
309API struct nc_session *
Radek Krejci1d06db42015-10-22 13:39:49 +0200310nc_connect_libssl(SSL *tls, struct ly_ctx *ctx)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200311{
312 (void) tls;
313 (void) ctx;
314
315 return NULL;
316}
317
318#endif /* ENABLE_TLS */
319
320API void
321nc_session_free(struct nc_session *session)
322{
323 int r, i;
324 int multisession = 0; /* flag for more NETCONF session on a single SSH session */
325 struct nc_session *siter;
326 struct nc_notif_cont *ntfiter;
327 struct nc_reply_cont *rpliter;
328 struct lyd_node *close_rpc;
329 struct lys_module *ietfnc;
330 void *p;
331
332 if (!session || session->status < NC_STATUS_INVALID) {
333 return;
334 }
335
336 /* mark session for closing */
337 do {
338 r = session_ti_lock(session, 0);
339 } while (r < 0);
340 if (r) {
341 return;
342 }
343
344 /* stop notifications loop if any */
345 if (session->notif) {
346 pthread_cancel(*session->notif);
347 pthread_join(*session->notif, NULL);
348 }
349
350 if (session->side == NC_CLIENT && session->status == NC_STATUS_RUNNING) {
351 /* cleanup message queues */
352 /* notifications */
353 for (ntfiter = session->notifs; ntfiter; ) {
354 nc_notif_free(ntfiter->msg);
355
356 p = ntfiter;
357 ntfiter = ntfiter->next;
358 free(p);
359 }
360
361 /* rpc replies */
362 for (rpliter = session->replies; rpliter; ) {
363 nc_reply_free(rpliter->msg);
364
365 p = rpliter;
366 rpliter = rpliter->next;
367 free(p);
368 }
369
370 /* send closing info to the other side */
371 ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
372 if (!ietfnc) {
373 WRN("%s: Missing ietf-netconf schema in context (session %u), unable to send <close-session\\>", session->id);
374 } else {
375 close_rpc = lyd_new(NULL, ietfnc, "close-session");
376 nc_send_rpc_(session, close_rpc);
377 lyd_free(close_rpc);
378 }
379
380 /* list of server's capabilities */
381 if (session->cpblts) {
382 for (i = 0; session->cpblts[i]; i++) {
383 lydict_remove(session->ctx, session->cpblts[i]);
384 }
385 free(session->cpblts);
386 }
387 }
388
389 session->status = NC_STATUS_CLOSING;
390
391 /* transport implementation cleanup */
392 switch (session->ti_type) {
393 case NC_TI_FD:
394 /* nothing needed - file descriptors were provided by caller,
395 * so it is up to the caller to close them correctly
396 * TODO use callbacks
397 */
398 break;
399
400#ifdef ENABLE_LIBSSH
401 case NC_TI_LIBSSH:
402 ssh_channel_free(session->ti.libssh.channel);
403 /* There can be multiple NETCONF sessions on the same SSH session (NETCONF session maps to
404 * SSH channel). So destroy the SSH session only if there is no other NETCONF session using
405 * it.
406 */
407 if (!session->ti.libssh.next) {
408 ssh_disconnect(session->ti.libssh.session);
409 ssh_free(session->ti.libssh.session);
410 } else {
411 /* multiple NETCONF sessions on a single SSH session */
412 multisession = 1;
413 /* remove the session from the list */
414 for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next);
415 if (siter->ti.libssh.next == session->ti.libssh.next) {
416 /* there will be only one session */
417 siter->ti.libssh.next = NULL;
418 } else {
419 /* there are still multiple sessions, keep the ring list */
420 siter->ti.libssh.next = session->ti.libssh.next;
421 }
422 }
423 break;
424#endif
425
426#ifdef ENABLE_TLS
427 case NC_TI_OPENSSL:
428 SSL_shutdown(session->ti.tls);
429 SSL_free(session->ti.tls);
430 break;
431#endif
432 }
Radek Krejciac6d3472015-10-22 15:47:18 +0200433 lydict_remove(session->ctx, session->username);
434 lydict_remove(session->ctx, session->host);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200435
436 /* final cleanup */
437 if (multisession) {
438 session_ti_unlock(session);
439 } else {
440 pthread_mutex_destroy(session->ti_lock);
441 free(session->ti_lock);
442 }
443
444 if (!(session->flags & NC_SESSION_SHAREDCTX)) {
445 ly_ctx_destroy(session->ctx);
446 }
447
448 free(session);
449}
450
451static int
452parse_cpblts(struct lyxml_elem *xml, const char ***list)
453{
454 struct lyxml_elem *cpblt;
455 int ver = -1;
456 int i = 0;
457
458 if (list) {
459 /* get the storage for server's capabilities */
460 LY_TREE_FOR(xml->child, cpblt) {
461 i++;
462 }
463 *list = calloc(i, sizeof **list);
464 if (!*list) {
465 ERRMEM;
466 return -1;
467 }
468 i = 0;
469 }
470
471 LY_TREE_FOR(xml->child, cpblt) {
472 if (strcmp(cpblt->name, "capability") && cpblt->ns && cpblt->ns->value &&
473 !strcmp(cpblt->ns->value, NC_NS_BASE)) {
474 ERR("Unexpected <%s> element in client's <hello>.", cpblt->name);
475 return -1;
476 } else if (!cpblt->ns || !cpblt->ns->value || strcmp(cpblt->ns->value, NC_NS_BASE)) {
477 continue;
478 }
479
480 /* detect NETCONF version */
481 if (ver < 0 && !strcmp(cpblt->content, "urn:ietf:params:netconf:base:1.0")) {
482 ver = 0;
483 } else if (ver < 1 && !strcmp(cpblt->content, "urn:ietf:params:netconf:base:1.1")) {
484 ver = 1;
485 }
486
487 /* store capabilities */
488 if (list) {
489 (*list)[i] = cpblt->content;
490 cpblt->content = NULL;
491 i++;
492 }
493 }
494
495 if (ver == -1) {
496 ERR("Peer does not support compatible NETCONF version.");
497 }
498
499 return ver;
500}
501
502static NC_MSG_TYPE
503nc_recv_hello(struct nc_session *session)
504{
505 struct lyxml_elem *xml = NULL, *node;
506 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
507 int ver = -1;
508 char *str;
509 long long int id;
510 int flag = 0;
511
512 msgtype = nc_read_msg(session, cfg.hello_timeout * 1000, &xml);
513
514 switch(msgtype) {
515 case NC_MSG_HELLO:
516 /* parse <hello> data */
517 if (session->side == NC_SERVER) {
518 /* get know NETCONF version */
519 LY_TREE_FOR(xml->child, node) {
520 if (!node->ns || !node->ns->value || strcmp(node->ns->value, NC_NS_BASE)) {
521 continue;
522 } else if (strcmp(node->name, "capabilities")) {
523 ERR("Unexpected <%s> element in client's <hello>.", node->name);
524 goto error;
525 }
526
527 if (flag) {
528 /* multiple capabilities elements */
529 ERR("Invalid <hello> message (multiple <capabilities> elements)");
530 goto error;
531 }
532 flag = 1;
533
534 if ((ver = parse_cpblts(node, NULL)) < 0) {
535 goto error;
536 }
537 session->version = ver;
538 }
539 } else { /* NC_CLIENT */
540 LY_TREE_FOR(xml->child, node) {
541 if (!node->ns || !node->ns->value || strcmp(node->ns->value, NC_NS_BASE)) {
542 continue;
543 } else if (!strcmp(node->name, "session-id")) {
544 if (!node->content || !strlen(node->content)) {
545 ERR("No value of <session-id> element in server's <hello>");
546 goto error;
547 }
548 str = NULL;
549 id = strtoll(node->content, &str, 10);
550 if (*str || id < 1 || id > UINT32_MAX) {
551 ERR("Invalid value of <session-id> element in server's <hello>");
552 goto error;
553 }
554 session->id = (uint32_t)id;
555 continue;
556 } else if (strcmp(node->name, "capabilities")) {
557 ERR("Unexpected <%s> element in client's <hello>.", node->name);
558 goto error;
559 }
560
561 if (flag) {
562 /* multiple capabilities elements */
563 ERR("Invalid <hello> message (multiple <capabilities> elements)");
564 goto error;
565 }
566 flag = 1;
567
568 if ((ver = parse_cpblts(node, NULL)) < 0) {
569 goto error;
570 }
571 session->version = ver;
572 }
573
574 if (!session->id) {
575 ERR("Missing <session-id> in server's <hello>");
576 goto error;
577 }
578 }
579 break;
580 case NC_MSG_ERROR:
581 /* nothing special, just pass it out */
582 break;
583 default:
584 ERR("Unexpected message received instead of <hello>.");
585 msgtype = NC_MSG_ERROR;
586 }
587
588 /* cleanup */
589 lyxml_free_elem(session->ctx, xml);
590
591 return msgtype;
592
593error:
594 /* cleanup */
595 lyxml_free_elem(session->ctx, xml);
596
597 return NC_MSG_ERROR;
Radek Krejci5686ff72015-10-09 13:33:56 +0200598}
599
600API NC_MSG_TYPE
Radek Krejci695d4fa2015-10-22 13:23:54 +0200601nc_recv_rpc(struct nc_session *session, int timeout, struct nc_rpc_server **rpc)
Radek Krejci5686ff72015-10-09 13:33:56 +0200602{
603 int r;
604 struct lyxml_elem *xml = NULL;
605 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
606
607 if (!session || !rpc) {
608 ERR("%s: Invalid parameter", __func__);
609 return NC_MSG_ERROR;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200610 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_SERVER) {
611 ERR("%s: invalid session to receive RPCs.", __func__);
Radek Krejci5686ff72015-10-09 13:33:56 +0200612 return NC_MSG_ERROR;
613 }
614
615 r = session_ti_lock(session, timeout);
616 if (r > 0) {
617 /* error */
618 return NC_MSG_ERROR;
619 } else if (r < 0) {
620 /* timeout */
621 return NC_MSG_WOULDBLOCK;
Radek Krejci206fcd62015-10-07 15:42:48 +0200622 }
623
624 msgtype = nc_read_msg(session, timeout, &xml);
Radek Krejci5686ff72015-10-09 13:33:56 +0200625 session_ti_unlock(session);
Radek Krejci206fcd62015-10-07 15:42:48 +0200626
Radek Krejci5686ff72015-10-09 13:33:56 +0200627 switch(msgtype) {
628 case NC_MSG_RPC:
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200629 *rpc = malloc(sizeof **rpc);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200630 (*rpc)->type = NC_RPC_SERVER;
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200631 (*rpc)->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200632 (*rpc)->tree = lyd_parse_xml(session->ctx, xml, 0);
633 (*rpc)->root = xml;
634 break;
635 case NC_MSG_HELLO:
636 ERR("SESSION %u: Received another <hello> message.", session->id);
637 goto error;
638 case NC_MSG_REPLY:
639 ERR("SESSION %u: Received <rpc-reply> from NETCONF client.", session->id);
640 goto error;
641 case NC_MSG_NOTIF:
642 ERR("SESSION %u: Received <notification> from NETCONF client.", session->id);
643 goto error;
644 default:
645 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
646 * NC_MSG_NONE is not returned by nc_read_msg()
647 */
648 break;
649 }
Radek Krejci0dc69f72015-10-08 15:32:09 +0200650
Radek Krejci206fcd62015-10-07 15:42:48 +0200651 return msgtype;
Radek Krejci5686ff72015-10-09 13:33:56 +0200652
653error:
654
655 /* cleanup */
656 lyxml_free_elem(session->ctx, xml);
657
658 return NC_MSG_ERROR;
659}
660
661API NC_MSG_TYPE
Radek Krejci695d4fa2015-10-22 13:23:54 +0200662nc_recv_reply(struct nc_session *session, int timeout, struct nc_reply **reply)
Radek Krejci5686ff72015-10-09 13:33:56 +0200663{
664 int r;
665 struct lyxml_elem *xml;
666 struct nc_reply_cont *cont_r;
667 struct nc_notif_cont **cont_n;
668 struct nc_notif *notif;
669 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
670
671 if (!session || !reply) {
672 ERR("%s: Invalid parameter", __func__);
673 return NC_MSG_ERROR;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200674 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
675 ERR("%s: invalid session to receive RPC replies.", __func__);
Radek Krejci5686ff72015-10-09 13:33:56 +0200676 return NC_MSG_ERROR;
677 }
678
679 do {
680 if (msgtype && session->notif) {
681 /* second run, wait and give a chance to nc_recv_notif() */
682 usleep(TIMEOUT_STEP);
683 timeout = timeout - (TIMEOUT_STEP);
684 }
685 r = session_ti_lock(session, timeout);
686 if (r > 0) {
687 /* error */
688 return NC_MSG_ERROR;
689 } else if (r < 0) {
690 /* timeout */
691 return NC_MSG_WOULDBLOCK;
692 }
693
694 /* try to get message from the session's queue */
695 if (session->notifs) {
696 cont_r = session->replies;
697 session->replies = cont_r->next;
698
699 session_ti_unlock(session);
700
701 *reply = cont_r->msg;
702 free(cont_r);
703
704 return NC_MSG_REPLY;
705 }
706
707 /* read message from wire */
708 msgtype = nc_read_msg(session, timeout, &xml);
709 if (msgtype == NC_MSG_NOTIF) {
710 if (!session->notif) {
711 session_ti_unlock(session);
712 ERR("SESSION %u: Received Notification but session is not subscribed.", session->id);
713 goto error;
714 }
715
716 /* create notification object */
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200717 notif = malloc(sizeof *notif);
718 notif->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200719 notif->tree = lyd_parse_xml(session->ctx, xml, 0);
720 notif->root = xml;
721
722 /* store the message for nc_recv_notif() */
723 cont_n = &session->notifs;
724 while(*cont_n) {
725 cont_n = &((*cont_n)->next);
726 }
727 *cont_n = malloc(sizeof **cont_n);
728 (*cont_n)->msg = notif;
729 (*cont_n)->next = NULL;
730 }
731
732 session_ti_unlock(session);
733
734 switch(msgtype) {
735 case NC_MSG_REPLY:
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200736 *reply = malloc(sizeof **reply);
737 (*reply)->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200738 (*reply)->tree = lyd_parse_xml(session->ctx, xml, 0);
739 (*reply)->root = xml;
740 break;
741 case NC_MSG_HELLO:
742 ERR("SESSION %u: Received another <hello> message.", session->id);
743 goto error;
744 case NC_MSG_RPC:
745 ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
746 goto error;
747 default:
748 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
749 * NC_MSG_NOTIF already handled before the switch;
750 * NC_MSG_NONE is not returned by nc_read_msg()
751 */
752 break;
753 }
754
755 } while(msgtype == NC_MSG_NOTIF);
756
757 return msgtype;
758
759error:
760
761 /* cleanup */
762 lyxml_free_elem(session->ctx, xml);
763
764 return NC_MSG_ERROR;
765}
766
767API NC_MSG_TYPE
Radek Krejci695d4fa2015-10-22 13:23:54 +0200768nc_recv_notif(struct nc_session *session, int timeout, struct nc_notif **notif)
Radek Krejci5686ff72015-10-09 13:33:56 +0200769{
770 int r;
771 struct lyxml_elem *xml;
772 struct nc_notif_cont *cont_n;
773 struct nc_reply_cont **cont_r;
774 struct nc_reply *reply;
775 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
776
777 if (!session || !notif) {
778 ERR("%s: Invalid parameter", __func__);
779 return NC_MSG_ERROR;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200780 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
781 ERR("%s: invalid session to receive Notifications.", __func__);
Radek Krejci5686ff72015-10-09 13:33:56 +0200782 return NC_MSG_ERROR;
783 }
784
785 do {
786 if (msgtype) {
787 /* second run, wait and give a chance to nc_recv_reply() */
788 usleep(TIMEOUT_STEP);
789 timeout = timeout - (TIMEOUT_STEP);
790 }
791 r = session_ti_lock(session, timeout);
792 if (r > 0) {
793 /* error */
794 return NC_MSG_ERROR;
795 } else if (r < 0) {
796 /* timeout */
797 return NC_MSG_WOULDBLOCK;
798 }
799
800 /* try to get message from the session's queue */
801 if (session->notifs) {
802 cont_n = session->notifs;
803 session->notifs = cont_n->next;
804
805 session_ti_unlock(session);
806
807 *notif = cont_n->msg;
808 free(cont_n);
809
810 return NC_MSG_NOTIF;
811 }
812
813 /* read message from wire */
814 msgtype = nc_read_msg(session, timeout, &xml);
815 if (msgtype == NC_MSG_REPLY) {
816 /* create reply object */
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200817 reply = malloc(sizeof *reply);
818 reply->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200819 reply->tree = lyd_parse_xml(session->ctx, xml, 0);
820 reply->root = xml;
821
822 /* store the message for nc_recv_reply() */
823 cont_r = &session->replies;
824 while(*cont_r) {
825 cont_r = &((*cont_r)->next);
826 }
827 *cont_r = malloc(sizeof **cont_r);
828 (*cont_r)->msg = reply;
829 (*cont_r)->next = NULL;
830 }
831
832 session_ti_unlock(session);
833
834 switch(msgtype) {
835 case NC_MSG_NOTIF:
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200836 *notif = malloc(sizeof **notif);
837 (*notif)->ctx = session->ctx;
Radek Krejci5686ff72015-10-09 13:33:56 +0200838 (*notif)->tree = lyd_parse_xml(session->ctx, xml, 0);
839 (*notif)->root = xml;
840 break;
841 case NC_MSG_HELLO:
842 ERR("SESSION %u: Received another <hello> message.", session->id);
843 goto error;
844 case NC_MSG_RPC:
845 ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
846 goto error;
847 default:
848 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
849 * NC_MSG_REPLY already handled before the switch;
850 * NC_MSG_NONE is not returned by nc_read_msg()
851 */
852 break;
853 }
854
855 } while(msgtype == NC_MSG_REPLY);
856
857 return msgtype;
858
859error:
860
861 /* cleanup */
862 lyxml_free_elem(session->ctx, xml);
863
864 return NC_MSG_ERROR;
Radek Krejci206fcd62015-10-07 15:42:48 +0200865}
Radek Krejcife0b3472015-10-12 13:43:42 +0200866
Radek Krejci695d4fa2015-10-22 13:23:54 +0200867static NC_MSG_TYPE
868nc_send_hello_(struct nc_session *session)
869{
870 int r;
871 char **cpblts;
872
873 if (session->side == NC_CLIENT) {
874 /* client side hello - send only NETCONF base capabilities */
875 cpblts = malloc(3 * sizeof *cpblts);
876 cpblts[0] = "urn:ietf:params:netconf:base:1.0";
877 cpblts[1] = "urn:ietf:params:netconf:base:1.1";
878 cpblts[2] = NULL;
879
880 r = nc_write_msg(session, NC_MSG_HELLO, cpblts, NULL);
881 free(cpblts);
882 }
883
884
885 if (r) {
886 return NC_MSG_ERROR;
887 } else {
888 return NC_MSG_HELLO;
889 }
890}
891
892static NC_MSG_TYPE
893nc_send_rpc_(struct nc_session *session, struct lyd_node *op)
Radek Krejcife0b3472015-10-12 13:43:42 +0200894{
895 int r;
896
Radek Krejci695d4fa2015-10-22 13:23:54 +0200897 r = nc_write_msg(session, NC_MSG_RPC, op, NULL);
Radek Krejcife0b3472015-10-12 13:43:42 +0200898
899 if (r) {
900 return NC_MSG_ERROR;
901 } else {
902 return NC_MSG_RPC;
903 }
904}
905
Radek Krejci695d4fa2015-10-22 13:23:54 +0200906API NC_MSG_TYPE
907nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc)
908{
909 NC_MSG_TYPE r;
910 struct nc_rpc_lock *rpc_lock;
911 struct nc_rpc_getconfig *rpc_gc;
912 struct lyd_node *data, *node;
913 struct lys_module *ietfnc;
914
915 if (!session || !rpc || rpc->type == NC_RPC_SERVER) {
916 ERR("%s: Invalid parameter", __func__);
917 return NC_MSG_ERROR;
918 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
919 ERR("%s: invalid session to send RPCs.", __func__);
920 return NC_MSG_ERROR;
921 }
922
923 ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
924 if (!ietfnc) {
925 ERR("%s: Missing ietf-netconf schema in context (session %u)", session->id);
926 return NC_MSG_ERROR;
927 }
928
929 switch(rpc->type) {
930 case NC_RPC_GETCONFIG:
931 rpc_gc = (struct nc_rpc_getconfig *)rpc;
932
933 data = lyd_new(NULL, ietfnc, "get-config");
934 node = lyd_new(data, ietfnc, "source");
935 node = lyd_new_leaf_str(node, ietfnc, ncds2str[rpc_gc->source], LY_TYPE_EMPTY, NULL);
936 if (!node) {
937 lyd_free(data);
938 return NC_MSG_ERROR;
939 }
940 if (rpc_gc->filter) {
941 if (rpc_gc->filter->type == NC_FILTER_SUBTREE) {
942 node = lyd_new_anyxml(data, ietfnc, "filter", rpc_gc->filter->data);
943 lyd_insert_attr(node, "type", "subtree");
944 } else if (rpc_gc->filter->type == NC_FILTER_XPATH) {
945 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
946 /* TODO - handle namespaces from XPATH query */
947 lyd_insert_attr(node, "type", "xpath");
948 lyd_insert_attr(node, "select", rpc_gc->filter->data);
949 }
950 }
951 break;
952 case NC_RPC_LOCK:
953 rpc_lock = (struct nc_rpc_lock *)rpc;
954
955 data = lyd_new(NULL, ietfnc, "lock");
956 node = lyd_new(data, ietfnc, "target");
957 node = lyd_new_leaf_str(node, ietfnc, ncds2str[rpc_lock->target], LY_TYPE_EMPTY, NULL);
958 if (!node) {
959 lyd_free(data);
960 return NC_MSG_ERROR;
961 }
962 break;
963 case NC_RPC_UNLOCK:
964 rpc_lock = (struct nc_rpc_lock *)rpc;
965
966 data = lyd_new(NULL, ietfnc, "unlock");
967 node = lyd_new(data, ietfnc, "target");
968 node = lyd_new_leaf_str(node, ietfnc, ncds2str[rpc_lock->target], LY_TYPE_EMPTY, NULL);
969 if (!node) {
970 lyd_free(data);
971 return NC_MSG_ERROR;
972 }
973 break;
974 }
975
976 r = session_ti_lock(session, 0);
977 if (r != 0) {
978 /* error or blocking */
979 r = NC_MSG_WOULDBLOCK;
980 } else {
981 /* send RPC */
982 r = nc_send_rpc_(session, data);
983 }
984 session_ti_unlock(session);
985
986 lyd_free(data);
987 return r;
988}