blob: 2771490c71d22b632bfb87b6de2ac52f53a36e12 [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{
Michal Vaskoe90a41e2015-11-10 13:08:38 +010055 if (schema_searchpath) {
56 free(schema_searchpath);
57 }
Radek Krejci695d4fa2015-10-22 13:23:54 +020058 schema_searchpath = strdup(path);
59
60 return schema_searchpath ? 0 : 1;
61}
62
Radek Krejci5686ff72015-10-09 13:33:56 +020063/*
64 * @return 0 - success
65 * -1 - timeout
66 * >0 - error
67 */
68static int
69session_ti_lock(struct nc_session *session, int timeout)
Radek Krejci206fcd62015-10-07 15:42:48 +020070{
71 int r;
Radek Krejci206fcd62015-10-07 15:42:48 +020072
73 if (timeout >= 0) {
74 /* limited waiting for lock */
75 do {
Radek Krejci695d4fa2015-10-22 13:23:54 +020076 r = pthread_mutex_trylock(session->ti_lock);
Radek Krejci206fcd62015-10-07 15:42:48 +020077 if (r == EBUSY) {
78 /* try later until timeout passes */
79 usleep(TIMEOUT_STEP);
80 timeout = timeout - TIMEOUT_STEP;
81 continue;
82 } else if (r) {
83 /* error */
84 ERR("Acquiring session (%u) TI lock failed (%s).", session->id, strerror(r));
Radek Krejci5686ff72015-10-09 13:33:56 +020085 return r;
Radek Krejci206fcd62015-10-07 15:42:48 +020086 } else {
87 /* lock acquired */
Radek Krejci5686ff72015-10-09 13:33:56 +020088 return 0;
Radek Krejci206fcd62015-10-07 15:42:48 +020089 }
90 } while(timeout > 0);
91
Radek Krejci5686ff72015-10-09 13:33:56 +020092 /* timeout has passed */
93 return -1;
Radek Krejci206fcd62015-10-07 15:42:48 +020094 } else {
95 /* infinite waiting for lock */
Radek Krejci695d4fa2015-10-22 13:23:54 +020096 return pthread_mutex_lock(session->ti_lock);
Radek Krejci5686ff72015-10-09 13:33:56 +020097 }
98}
99
100static int
101session_ti_unlock(struct nc_session *session)
102{
Radek Krejci695d4fa2015-10-22 13:23:54 +0200103 return pthread_mutex_unlock(session->ti_lock);
104}
105
Radek Krejciac6d3472015-10-22 15:47:18 +0200106int
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100107nc_handshake(struct nc_session *session)
Radek Krejciac6d3472015-10-22 15:47:18 +0200108{
109 NC_MSG_TYPE type;
110
111 type = nc_send_hello_(session);
112 if (type != NC_MSG_HELLO) {
113 return 1;
114 }
115
116 type = nc_recv_hello(session);
117 if (type != NC_MSG_HELLO) {
118 return 1;
119 }
120
121 return 0;
122}
123
Radek Krejci695d4fa2015-10-22 13:23:54 +0200124static int
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100125ctx_load_model(struct nc_session *session, const char *cpblt, int get_schema_support)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200126{
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100127 struct lys_module *module;
128 char *ptr, *ptr2;
129 char *model_name, *revision = NULL, *features = NULL;
130
131 /*if (get_schema_support) {
132 * TODO *
133 } else*/ {
134 /* parse module */
135 ptr = strstr(cpblt, "module=");
136 if (!ptr) {
Michal Vasko97d69032015-11-10 16:08:15 +0100137 WRN("Unknown capability \"%s\" could not be parsed.", cpblt);
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100138 return 1;
139 }
140 ptr += 7;
141 ptr2 = strchr(ptr, '&');
142 if (!ptr2) {
143 ptr2 = ptr + strlen(ptr);
144 }
145 model_name = strndup(ptr, ptr2 - ptr);
146
147 /* parse revision */
148 ptr = strstr(cpblt, "revision=");
149 if (ptr) {
150 ptr += 9;
151 ptr2 = strchr(ptr, '&');
152 if (!ptr2) {
153 ptr2 = ptr + strlen(ptr);
154 }
155 revision = strndup(ptr, ptr2 - ptr);
156 }
157
158 /* load module */
159 module = ly_ctx_load_module(session->ctx, NULL, model_name, revision);
160
161 free(model_name);
162 free(revision);
163 if (!module) {
164 return 1;
165 }
166
167 /* parse features */
168 ptr = strstr(cpblt, "features=");
169 if (ptr) {
170 ptr += 9;
171 ptr2 = strchr(ptr, '&');
172 if (!ptr2) {
173 ptr2 = ptr + strlen(ptr);
174 }
175 features = strndup(ptr, ptr2 - ptr);
176 }
177
178 /* enable features */
179 if (features) {
180 /* basically manual strtok_r (to avoid macro) */
181 ptr2 = features;
182 for (ptr = features; *ptr; ++ptr) {
183 if (*ptr == ',') {
184 *ptr = '\0';
185 /* remember last feature */
186 ptr2 = ptr + 1;
187 }
188 }
189
190 ptr = features;
191 lys_features_enable(module, ptr);
192 while (ptr != ptr2) {
193 ptr += strlen(ptr) + 1;
194 lys_features_enable(module, ptr);
195 }
196
197 free(features);
198 }
199 }
200
201 return 0;
202}
203
204static int
205ctx_load_ietf_netconf(struct ly_ctx *ctx, const char **cpblts)
206{
207 int fd, i;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200208 struct lys_module *ietfnc;
209
210 fd = open(SCHEMAS_DIR"ietf-netconf.yin", O_RDONLY);
211 if (fd < 0) {
212 ERR("Loading base NETCONF schema (%s) failed (%s).", SCHEMAS_DIR"ietf-netconf", strerror(errno));
213 return 1;
214 }
215 if (!(ietfnc = lys_read(ctx, fd, LYS_IN_YIN))) {
216 ERR("Loading base NETCONF schema (%s) failed.", SCHEMAS_DIR"ietf-netconf");
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100217 close(fd);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200218 return 1;
219 }
220 close(fd);
221
222 /* set supported capabilities from ietf-netconf */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100223 for (i = 0; cpblts[i]; ++i) {
224 if (!strncmp(cpblts[i], "urn:ietf:params:netconf:capability:", 35)) {
225 if (!strncmp(cpblts[i] + 35, "writable-running", 16)) {
226 lys_features_enable(ietfnc, "writable-running");
227 } else if (!strncmp(cpblts[i] + 35, "candidate", 9)) {
228 lys_features_enable(ietfnc, "candidate");
229 } else if (!strcmp(cpblts[i] + 35, "confirmed-commit:1.1")) {
230 lys_features_enable(ietfnc, "confirmed-commit");
231 } else if (!strncmp(cpblts[i] + 35, "rollback-on-error", 17)) {
232 lys_features_enable(ietfnc, "rollback-on-error");
233 } else if (!strcmp(cpblts[i] + 35, "validate:1.1")) {
234 lys_features_enable(ietfnc, "validate");
235 } else if (!strncmp(cpblts[i] + 35, "startup", 7)) {
236 lys_features_enable(ietfnc, "startup");
237 } else if (!strncmp(cpblts[i] + 35, "url", 3)) {
238 lys_features_enable(ietfnc, "url");
239 } else if (!strncmp(cpblts[i] + 35, "xpath", 5)) {
240 lys_features_enable(ietfnc, "xpath");
241 }
242 }
243 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200244
245 return 0;
246}
247
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100248/* session with an empty context is assumed */
249int
250nc_ctx_fill(struct nc_session *session)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200251{
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100252 int i, get_schema_support;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200253
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100254 assert(session->cpblts && session->ctx);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200255
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100256 /* check if get-schema is supported */
257 get_schema_support = 0;
258 for (i = 0; session->cpblts[i]; ++i) {
259 if (!strncmp(session->cpblts[i], "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring", 51)) {
260 get_schema_support = 1;
261 break;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200262 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200263 }
264
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100265 /* load base model disregarding whether it's in capabilities (but NETCONF capabilities are used to enable features) */
266 if (ctx_load_ietf_netconf(session->ctx, session->cpblts)) {
267 goto fail;
268 }
269
270 /* load all other models */
271 for (i = 0; session->cpblts[i]; ++i) {
272 if (!strncmp(session->cpblts[i], "urn:ietf:params:netconf:capability", 34)
273 || !strncmp(session->cpblts[i], "urn:ietf:params:netconf:base", 28)) {
274 continue;
275 }
276
277 ctx_load_model(session, session->cpblts[i], get_schema_support);
278 }
279
280 return 0;
281
282fail:
283 free(session->ctx);
284 return 1;
285}
286
287int
288nc_ctx_check(struct nc_session *session)
289{
290 /* check presence of the required base schema */
291 if (!ly_ctx_get_module(session->ctx, "ietf-netconf", NULL)) {
292 if (ctx_load_ietf_netconf(session->ctx, session->cpblts)) {
293 return 1;
294 }
295 }
296
297 return 0;
Radek Krejciac6d3472015-10-22 15:47:18 +0200298}
299
300API struct nc_session *
301nc_connect_inout(int fdin, int fdout, struct ly_ctx *ctx)
302{
303 struct nc_session *session = NULL;
304
305 if (fdin < 0 || fdout < 0) {
306 ERR("%s: Invalid parameter", __func__);
307 return NULL;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200308 }
309
Radek Krejciac6d3472015-10-22 15:47:18 +0200310 /* prepare session structure */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100311 session = calloc(1, sizeof *session);
Radek Krejciac6d3472015-10-22 15:47:18 +0200312 if (!session) {
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100313 ERRMEM;
Radek Krejciac6d3472015-10-22 15:47:18 +0200314 return NULL;
315 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100316 session->status = NC_STATUS_STARTING;
317 session->side = NC_CLIENT;
Radek Krejciac6d3472015-10-22 15:47:18 +0200318
319 /* transport specific data */
320 session->ti_type = NC_TI_FD;
321 session->ti.fd.in = fdin;
322 session->ti.fd.out = fdout;
323
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100324 /* assign context (dicionary needed for handshake) */
325 if (!ctx) {
326 ctx = ly_ctx_new(SCHEMAS_DIR);
327 } else {
328 session->flags |= NC_SESSION_SHAREDCTX;
329 }
330 session->ctx = ctx;
331
Radek Krejciac6d3472015-10-22 15:47:18 +0200332 /* NETCONF handshake */
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100333 if (nc_handshake(session)) {
334 goto fail;
335 }
336
337 /* check/fill libyang context */
338 if (session->flags & NC_SESSION_SHAREDCTX) {
339 if (nc_ctx_check(session)) {
340 goto fail;
341 }
342 } else {
343 if (nc_ctx_fill(session)) {
344 goto fail;
345 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200346 }
347
348 session->status = NC_STATUS_RUNNING;
349 return session;
350
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100351fail:
Radek Krejci695d4fa2015-10-22 13:23:54 +0200352 nc_session_free(session);
353 return NULL;
354}
355
Radek Krejciac6d3472015-10-22 15:47:18 +0200356int
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100357nc_connect_getsocket(const char* host, unsigned short port)
Radek Krejci695d4fa2015-10-22 13:23:54 +0200358{
Radek Krejciac6d3472015-10-22 15:47:18 +0200359 int sock = -1;
360 int i;
361 struct addrinfo hints, *res_list, *res;
362 char port_s[6]; /* length of string representation of short int */
Radek Krejci695d4fa2015-10-22 13:23:54 +0200363
Radek Krejciac6d3472015-10-22 15:47:18 +0200364 snprintf(port_s, 6, "%u", port);
365
366 /* Connect to a server */
367 memset(&hints, 0, sizeof hints);
368 hints.ai_family = AF_UNSPEC;
369 hints.ai_socktype = SOCK_STREAM;
370 hints.ai_protocol = IPPROTO_TCP;
371 i = getaddrinfo(host, port_s, &hints, &res_list);
372 if (i != 0) {
373 ERR("Unable to translate the host address (%s).", gai_strerror(i));
374 return -1;
375 }
376
377 for (i = 0, res = res_list; res != NULL; res = res->ai_next) {
378 sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
379 if (sock == -1) {
380 /* socket was not created, try another resource */
381 i = errno;
382 goto errloop;
383 }
384
385 if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) {
386 /* network connection failed, try another resource */
387 i = errno;
388 close(sock);
389 sock = -1;
390 goto errloop;
391 }
392
393 /* we're done, network connection established */
394 break;
395errloop:
Michal Vaskoaebea602015-10-26 15:35:34 +0100396 VRB("Unable to connect to %s:%s over %s (%s).", host, port_s,
Radek Krejciac6d3472015-10-22 15:47:18 +0200397 (res->ai_family == AF_INET6) ? "IPv6" : "IPv4", strerror(i));
398 continue;
399 }
400
401 if (sock == -1) {
Michal Vaskoaebea602015-10-26 15:35:34 +0100402 ERR("Unable to connect to %s:%s.", host, port_s);
Radek Krejciac6d3472015-10-22 15:47:18 +0200403 } else {
Michal Vaskoaebea602015-10-26 15:35:34 +0100404 VRB("Successfully connected to %s:%s over %s", host, port_s, (res->ai_family == AF_INET6) ? "IPv6" : "IPv4");
Radek Krejciac6d3472015-10-22 15:47:18 +0200405 }
406 freeaddrinfo(res_list);
407
408 return sock;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200409}
410
Radek Krejci695d4fa2015-10-22 13:23:54 +0200411API void
412nc_session_free(struct nc_session *session)
413{
414 int r, i;
415 int multisession = 0; /* flag for more NETCONF session on a single SSH session */
416 struct nc_session *siter;
417 struct nc_notif_cont *ntfiter;
418 struct nc_reply_cont *rpliter;
Michal Vaskoadd4c792015-10-26 15:36:58 +0100419 struct lyxml_elem *rpl, *child;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200420 struct lyd_node *close_rpc;
421 struct lys_module *ietfnc;
422 void *p;
423
Michal Vaskoadd4c792015-10-26 15:36:58 +0100424 if (!session || session->status == NC_STATUS_CLOSING) {
Radek Krejci695d4fa2015-10-22 13:23:54 +0200425 return;
426 }
427
428 /* mark session for closing */
Michal Vaskoadd4c792015-10-26 15:36:58 +0100429 if (session->ti_lock) {
430 do {
431 r = session_ti_lock(session, 0);
432 } while (r < 0);
433 if (r) {
434 return;
435 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200436 }
437
438 /* stop notifications loop if any */
439 if (session->notif) {
440 pthread_cancel(*session->notif);
441 pthread_join(*session->notif, NULL);
442 }
443
444 if (session->side == NC_CLIENT && session->status == NC_STATUS_RUNNING) {
445 /* cleanup message queues */
446 /* notifications */
447 for (ntfiter = session->notifs; ntfiter; ) {
448 nc_notif_free(ntfiter->msg);
449
450 p = ntfiter;
451 ntfiter = ntfiter->next;
452 free(p);
453 }
454
455 /* rpc replies */
456 for (rpliter = session->replies; rpliter; ) {
457 nc_reply_free(rpliter->msg);
458
459 p = rpliter;
460 rpliter = rpliter->next;
461 free(p);
462 }
463
464 /* send closing info to the other side */
465 ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
466 if (!ietfnc) {
467 WRN("%s: Missing ietf-netconf schema in context (session %u), unable to send <close-session\\>", session->id);
468 } else {
469 close_rpc = lyd_new(NULL, ietfnc, "close-session");
470 nc_send_rpc_(session, close_rpc);
471 lyd_free(close_rpc);
Michal Vaskofad6e912015-10-26 15:37:22 +0100472 switch (nc_read_msg(session, 200, &rpl)) {
473 case NC_MSG_REPLY:
474 LY_TREE_FOR(rpl->child, child) {
475 if (!strcmp(child->name, "ok") && child->ns && !strcmp(child->ns->value, NC_NS_BASE)) {
476 break;
477 }
478 }
479 if (!child) {
480 WRN("The reply to <close-session\\> was not <ok\\> as expected.");
481 }
482 lyxml_free_elem(session->ctx, rpl);
483 break;
484 case NC_MSG_WOULDBLOCK:
485 WRN("Timeout for receiving a reply to <close-session\\> elapsed.");
486 break;
487 case NC_MSG_ERROR:
488 ERR("Failed to receive a reply to <close-session\\>.");
489 break;
490 default:
491 /* cannot happen */
492 break;
493 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200494 }
495
496 /* list of server's capabilities */
497 if (session->cpblts) {
498 for (i = 0; session->cpblts[i]; i++) {
499 lydict_remove(session->ctx, session->cpblts[i]);
500 }
501 free(session->cpblts);
502 }
503 }
504
505 session->status = NC_STATUS_CLOSING;
506
507 /* transport implementation cleanup */
508 switch (session->ti_type) {
509 case NC_TI_FD:
510 /* nothing needed - file descriptors were provided by caller,
511 * so it is up to the caller to close them correctly
512 * TODO use callbacks
513 */
514 break;
515
Michal Vaskofb2fb762015-10-27 11:44:32 +0100516#ifdef ENABLE_SSH
Radek Krejci695d4fa2015-10-22 13:23:54 +0200517 case NC_TI_LIBSSH:
518 ssh_channel_free(session->ti.libssh.channel);
519 /* There can be multiple NETCONF sessions on the same SSH session (NETCONF session maps to
520 * SSH channel). So destroy the SSH session only if there is no other NETCONF session using
521 * it.
522 */
523 if (!session->ti.libssh.next) {
524 ssh_disconnect(session->ti.libssh.session);
525 ssh_free(session->ti.libssh.session);
526 } else {
527 /* multiple NETCONF sessions on a single SSH session */
528 multisession = 1;
529 /* remove the session from the list */
530 for (siter = session->ti.libssh.next; siter->ti.libssh.next != session; siter = siter->ti.libssh.next);
Michal Vaskoaec4f212015-10-26 15:37:45 +0100531 if (session->ti.libssh.next == siter) {
Radek Krejci695d4fa2015-10-22 13:23:54 +0200532 /* there will be only one session */
533 siter->ti.libssh.next = NULL;
534 } else {
535 /* there are still multiple sessions, keep the ring list */
536 siter->ti.libssh.next = session->ti.libssh.next;
537 }
538 }
539 break;
540#endif
541
542#ifdef ENABLE_TLS
543 case NC_TI_OPENSSL:
544 SSL_shutdown(session->ti.tls);
545 SSL_free(session->ti.tls);
546 break;
547#endif
548 }
Radek Krejciac6d3472015-10-22 15:47:18 +0200549 lydict_remove(session->ctx, session->username);
550 lydict_remove(session->ctx, session->host);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200551
552 /* final cleanup */
Michal Vaskoadd4c792015-10-26 15:36:58 +0100553 if (session->ti_lock) {
554 if (multisession) {
555 session_ti_unlock(session);
556 } else {
557 pthread_mutex_destroy(session->ti_lock);
558 free(session->ti_lock);
559 }
Radek Krejci695d4fa2015-10-22 13:23:54 +0200560 }
561
562 if (!(session->flags & NC_SESSION_SHAREDCTX)) {
563 ly_ctx_destroy(session->ctx);
564 }
565
566 free(session);
567}
568
569static int
570parse_cpblts(struct lyxml_elem *xml, const char ***list)
571{
572 struct lyxml_elem *cpblt;
573 int ver = -1;
574 int i = 0;
575
576 if (list) {
577 /* get the storage for server's capabilities */
578 LY_TREE_FOR(xml->child, cpblt) {
579 i++;
580 }
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100581 /* last item remains NULL */
582 *list = calloc(i + 1, sizeof **list);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200583 if (!*list) {
584 ERRMEM;
585 return -1;
586 }
587 i = 0;
588 }
589
590 LY_TREE_FOR(xml->child, cpblt) {
591 if (strcmp(cpblt->name, "capability") && cpblt->ns && cpblt->ns->value &&
592 !strcmp(cpblt->ns->value, NC_NS_BASE)) {
593 ERR("Unexpected <%s> element in client's <hello>.", cpblt->name);
594 return -1;
595 } else if (!cpblt->ns || !cpblt->ns->value || strcmp(cpblt->ns->value, NC_NS_BASE)) {
596 continue;
597 }
598
599 /* detect NETCONF version */
600 if (ver < 0 && !strcmp(cpblt->content, "urn:ietf:params:netconf:base:1.0")) {
601 ver = 0;
602 } else if (ver < 1 && !strcmp(cpblt->content, "urn:ietf:params:netconf:base:1.1")) {
603 ver = 1;
604 }
605
606 /* store capabilities */
607 if (list) {
608 (*list)[i] = cpblt->content;
609 cpblt->content = NULL;
610 i++;
611 }
612 }
613
614 if (ver == -1) {
615 ERR("Peer does not support compatible NETCONF version.");
616 }
617
618 return ver;
619}
620
621static NC_MSG_TYPE
622nc_recv_hello(struct nc_session *session)
623{
624 struct lyxml_elem *xml = NULL, *node;
625 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
626 int ver = -1;
627 char *str;
628 long long int id;
629 int flag = 0;
630
631 msgtype = nc_read_msg(session, cfg.hello_timeout * 1000, &xml);
632
633 switch(msgtype) {
634 case NC_MSG_HELLO:
635 /* parse <hello> data */
636 if (session->side == NC_SERVER) {
637 /* get know NETCONF version */
638 LY_TREE_FOR(xml->child, node) {
639 if (!node->ns || !node->ns->value || strcmp(node->ns->value, NC_NS_BASE)) {
640 continue;
641 } else if (strcmp(node->name, "capabilities")) {
642 ERR("Unexpected <%s> element in client's <hello>.", node->name);
643 goto error;
644 }
645
646 if (flag) {
647 /* multiple capabilities elements */
648 ERR("Invalid <hello> message (multiple <capabilities> elements)");
649 goto error;
650 }
651 flag = 1;
652
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100653 if ((ver = parse_cpblts(node, &session->cpblts)) < 0) {
Radek Krejci695d4fa2015-10-22 13:23:54 +0200654 goto error;
655 }
656 session->version = ver;
657 }
658 } else { /* NC_CLIENT */
659 LY_TREE_FOR(xml->child, node) {
660 if (!node->ns || !node->ns->value || strcmp(node->ns->value, NC_NS_BASE)) {
661 continue;
662 } else if (!strcmp(node->name, "session-id")) {
663 if (!node->content || !strlen(node->content)) {
664 ERR("No value of <session-id> element in server's <hello>");
665 goto error;
666 }
667 str = NULL;
668 id = strtoll(node->content, &str, 10);
669 if (*str || id < 1 || id > UINT32_MAX) {
670 ERR("Invalid value of <session-id> element in server's <hello>");
671 goto error;
672 }
673 session->id = (uint32_t)id;
674 continue;
675 } else if (strcmp(node->name, "capabilities")) {
676 ERR("Unexpected <%s> element in client's <hello>.", node->name);
677 goto error;
678 }
679
680 if (flag) {
681 /* multiple capabilities elements */
682 ERR("Invalid <hello> message (multiple <capabilities> elements)");
683 goto error;
684 }
685 flag = 1;
686
Michal Vasko9e2d3a32015-11-10 13:09:18 +0100687 if ((ver = parse_cpblts(node, &session->cpblts)) < 0) {
Radek Krejci695d4fa2015-10-22 13:23:54 +0200688 goto error;
689 }
690 session->version = ver;
691 }
692
693 if (!session->id) {
694 ERR("Missing <session-id> in server's <hello>");
695 goto error;
696 }
697 }
698 break;
699 case NC_MSG_ERROR:
700 /* nothing special, just pass it out */
701 break;
702 default:
703 ERR("Unexpected message received instead of <hello>.");
704 msgtype = NC_MSG_ERROR;
705 }
706
707 /* cleanup */
708 lyxml_free_elem(session->ctx, xml);
709
710 return msgtype;
711
712error:
713 /* cleanup */
714 lyxml_free_elem(session->ctx, xml);
715
716 return NC_MSG_ERROR;
Radek Krejci5686ff72015-10-09 13:33:56 +0200717}
718
719API NC_MSG_TYPE
Radek Krejci695d4fa2015-10-22 13:23:54 +0200720nc_recv_rpc(struct nc_session *session, int timeout, struct nc_rpc_server **rpc)
Radek Krejci5686ff72015-10-09 13:33:56 +0200721{
722 int r;
723 struct lyxml_elem *xml = NULL;
724 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
725
726 if (!session || !rpc) {
727 ERR("%s: Invalid parameter", __func__);
728 return NC_MSG_ERROR;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200729 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_SERVER) {
730 ERR("%s: invalid session to receive RPCs.", __func__);
Radek Krejci5686ff72015-10-09 13:33:56 +0200731 return NC_MSG_ERROR;
732 }
733
734 r = session_ti_lock(session, timeout);
735 if (r > 0) {
736 /* error */
737 return NC_MSG_ERROR;
738 } else if (r < 0) {
739 /* timeout */
740 return NC_MSG_WOULDBLOCK;
Radek Krejci206fcd62015-10-07 15:42:48 +0200741 }
742
743 msgtype = nc_read_msg(session, timeout, &xml);
Radek Krejci5686ff72015-10-09 13:33:56 +0200744 session_ti_unlock(session);
Radek Krejci206fcd62015-10-07 15:42:48 +0200745
Radek Krejci5686ff72015-10-09 13:33:56 +0200746 switch(msgtype) {
747 case NC_MSG_RPC:
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200748 *rpc = malloc(sizeof **rpc);
Radek Krejci695d4fa2015-10-22 13:23:54 +0200749 (*rpc)->type = NC_RPC_SERVER;
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200750 (*rpc)->ctx = session->ctx;
Radek Krejci61a20302015-10-31 22:57:55 +0100751 (*rpc)->tree = lyd_parse_xml(session->ctx, xml, LYD_OPT_DESTRUCT);
Radek Krejci5686ff72015-10-09 13:33:56 +0200752 (*rpc)->root = xml;
753 break;
754 case NC_MSG_HELLO:
755 ERR("SESSION %u: Received another <hello> message.", session->id);
756 goto error;
757 case NC_MSG_REPLY:
758 ERR("SESSION %u: Received <rpc-reply> from NETCONF client.", session->id);
759 goto error;
760 case NC_MSG_NOTIF:
761 ERR("SESSION %u: Received <notification> from NETCONF client.", session->id);
762 goto error;
763 default:
764 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
765 * NC_MSG_NONE is not returned by nc_read_msg()
766 */
767 break;
768 }
Radek Krejci0dc69f72015-10-08 15:32:09 +0200769
Radek Krejci206fcd62015-10-07 15:42:48 +0200770 return msgtype;
Radek Krejci5686ff72015-10-09 13:33:56 +0200771
772error:
773
774 /* cleanup */
775 lyxml_free_elem(session->ctx, xml);
776
777 return NC_MSG_ERROR;
778}
779
Radek Krejci8800a492015-10-31 17:51:27 +0100780static struct nc_reply *
781parse_reply(struct ly_ctx *ctx, struct lyxml_elem *xml)
782{
783 struct lyxml_elem *iter;
784 struct nc_reply *reply = NULL;
785
786 LY_TREE_FOR(xml->child, iter) {
787 if (!iter->ns || strcmp(iter->ns->value, NC_NS_BASE)) {
788 continue;
789 }
790
791 if (!strcmp(iter->name, "ok")) {
792 if (reply) {
793 ERR("Unexpected content of the <rpc-reply>.");
794 goto error;
795 }
796 reply = malloc(sizeof(struct nc_reply));
797 reply->type = NC_REPLY_OK;
798 reply->ctx = ctx;
799 reply->root = xml;
800 } else if (!strcmp(iter->name, "data")) {
801 if (reply) {
802 ERR("Unexpected content of the <rpc-reply>.");
803 goto error;
804 }
805 reply = malloc(sizeof(struct nc_reply_data));
806 reply->type = NC_REPLY_DATA;
807 reply->ctx = ctx;
808 reply->root = xml;
Radek Krejci61a20302015-10-31 22:57:55 +0100809 ((struct nc_reply_data *)reply)->data = lyd_parse_xml(ctx, iter, LYD_OPT_DESTRUCT);
Radek Krejci8800a492015-10-31 17:51:27 +0100810 } else if (!strcmp(iter->name, "rpc-error")) {
811 if (reply && reply->type != NC_REPLY_ERROR) {
812 ERR("<rpc-reply> content mismatch.");
813 goto error;
814 }
815 reply = malloc(sizeof(struct nc_reply_error));
816 reply->type = NC_REPLY_ERROR;
817 reply->ctx = ctx;
818 reply->root = xml;
819 }
820 }
821
822 if (!reply) {
823 ERR("Invalid content of the <rpc-reply>.");
824 }
825 return reply;
826
827error:
828 if (reply) {
829 reply->root = NULL;
830 nc_reply_free(reply);
831 }
832 return NULL;
833}
834
Radek Krejci5686ff72015-10-09 13:33:56 +0200835API NC_MSG_TYPE
Radek Krejci695d4fa2015-10-22 13:23:54 +0200836nc_recv_reply(struct nc_session *session, int timeout, struct nc_reply **reply)
Radek Krejci5686ff72015-10-09 13:33:56 +0200837{
838 int r;
839 struct lyxml_elem *xml;
840 struct nc_reply_cont *cont_r;
841 struct nc_notif_cont **cont_n;
842 struct nc_notif *notif;
843 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
844
845 if (!session || !reply) {
846 ERR("%s: Invalid parameter", __func__);
847 return NC_MSG_ERROR;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200848 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
849 ERR("%s: invalid session to receive RPC replies.", __func__);
Radek Krejci5686ff72015-10-09 13:33:56 +0200850 return NC_MSG_ERROR;
851 }
Radek Krejci8800a492015-10-31 17:51:27 +0100852 *reply = NULL;
Radek Krejci5686ff72015-10-09 13:33:56 +0200853
854 do {
855 if (msgtype && session->notif) {
856 /* second run, wait and give a chance to nc_recv_notif() */
857 usleep(TIMEOUT_STEP);
858 timeout = timeout - (TIMEOUT_STEP);
859 }
860 r = session_ti_lock(session, timeout);
861 if (r > 0) {
862 /* error */
863 return NC_MSG_ERROR;
864 } else if (r < 0) {
865 /* timeout */
866 return NC_MSG_WOULDBLOCK;
867 }
868
869 /* try to get message from the session's queue */
870 if (session->notifs) {
871 cont_r = session->replies;
872 session->replies = cont_r->next;
873
874 session_ti_unlock(session);
875
876 *reply = cont_r->msg;
877 free(cont_r);
878
879 return NC_MSG_REPLY;
880 }
881
882 /* read message from wire */
883 msgtype = nc_read_msg(session, timeout, &xml);
884 if (msgtype == NC_MSG_NOTIF) {
885 if (!session->notif) {
886 session_ti_unlock(session);
887 ERR("SESSION %u: Received Notification but session is not subscribed.", session->id);
888 goto error;
889 }
890
891 /* create notification object */
Radek Krejcia53b3fe2015-10-19 17:25:04 +0200892 notif = malloc(sizeof *notif);
893 notif->ctx = session->ctx;
Radek Krejci61a20302015-10-31 22:57:55 +0100894 notif->tree = lyd_parse_xml(session->ctx, xml, LYD_OPT_DESTRUCT);
Radek Krejci5686ff72015-10-09 13:33:56 +0200895 notif->root = xml;
896
897 /* store the message for nc_recv_notif() */
898 cont_n = &session->notifs;
899 while(*cont_n) {
900 cont_n = &((*cont_n)->next);
901 }
902 *cont_n = malloc(sizeof **cont_n);
903 (*cont_n)->msg = notif;
904 (*cont_n)->next = NULL;
905 }
906
907 session_ti_unlock(session);
908
909 switch(msgtype) {
910 case NC_MSG_REPLY:
Radek Krejci8800a492015-10-31 17:51:27 +0100911 /* distinguish between data / ok / error reply */
912 *reply = parse_reply(session->ctx, xml);
913 if (!(*reply)) {
914 goto error;
915 }
Radek Krejci5686ff72015-10-09 13:33:56 +0200916 break;
917 case NC_MSG_HELLO:
918 ERR("SESSION %u: Received another <hello> message.", session->id);
919 goto error;
920 case NC_MSG_RPC:
921 ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
922 goto error;
923 default:
924 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
925 * NC_MSG_NOTIF already handled before the switch;
926 * NC_MSG_NONE is not returned by nc_read_msg()
927 */
928 break;
929 }
930
931 } while(msgtype == NC_MSG_NOTIF);
932
933 return msgtype;
934
935error:
936
937 /* cleanup */
938 lyxml_free_elem(session->ctx, xml);
939
940 return NC_MSG_ERROR;
941}
942
943API NC_MSG_TYPE
Radek Krejci695d4fa2015-10-22 13:23:54 +0200944nc_recv_notif(struct nc_session *session, int timeout, struct nc_notif **notif)
Radek Krejci5686ff72015-10-09 13:33:56 +0200945{
946 int r;
947 struct lyxml_elem *xml;
948 struct nc_notif_cont *cont_n;
949 struct nc_reply_cont **cont_r;
950 struct nc_reply *reply;
951 NC_MSG_TYPE msgtype = 0; /* NC_MSG_ERROR */
952
953 if (!session || !notif) {
954 ERR("%s: Invalid parameter", __func__);
955 return NC_MSG_ERROR;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200956 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
957 ERR("%s: invalid session to receive Notifications.", __func__);
Radek Krejci5686ff72015-10-09 13:33:56 +0200958 return NC_MSG_ERROR;
959 }
960
961 do {
962 if (msgtype) {
963 /* second run, wait and give a chance to nc_recv_reply() */
964 usleep(TIMEOUT_STEP);
965 timeout = timeout - (TIMEOUT_STEP);
966 }
967 r = session_ti_lock(session, timeout);
968 if (r > 0) {
969 /* error */
970 return NC_MSG_ERROR;
971 } else if (r < 0) {
972 /* timeout */
973 return NC_MSG_WOULDBLOCK;
974 }
975
976 /* try to get message from the session's queue */
977 if (session->notifs) {
978 cont_n = session->notifs;
979 session->notifs = cont_n->next;
980
981 session_ti_unlock(session);
982
983 *notif = cont_n->msg;
984 free(cont_n);
985
986 return NC_MSG_NOTIF;
987 }
988
989 /* read message from wire */
990 msgtype = nc_read_msg(session, timeout, &xml);
991 if (msgtype == NC_MSG_REPLY) {
Radek Krejci8800a492015-10-31 17:51:27 +0100992 /* distinguish between data / ok / error reply */
993 reply = parse_reply(session->ctx, xml);
994 if (!reply) {
995 goto error;
996 }
Radek Krejci5686ff72015-10-09 13:33:56 +0200997
998 /* store the message for nc_recv_reply() */
999 cont_r = &session->replies;
1000 while(*cont_r) {
1001 cont_r = &((*cont_r)->next);
1002 }
1003 *cont_r = malloc(sizeof **cont_r);
1004 (*cont_r)->msg = reply;
1005 (*cont_r)->next = NULL;
1006 }
1007
1008 session_ti_unlock(session);
1009
1010 switch(msgtype) {
1011 case NC_MSG_NOTIF:
Radek Krejcia53b3fe2015-10-19 17:25:04 +02001012 *notif = malloc(sizeof **notif);
1013 (*notif)->ctx = session->ctx;
Radek Krejci61a20302015-10-31 22:57:55 +01001014 (*notif)->tree = lyd_parse_xml(session->ctx, xml, LYD_OPT_DESTRUCT);
Radek Krejci5686ff72015-10-09 13:33:56 +02001015 (*notif)->root = xml;
1016 break;
1017 case NC_MSG_HELLO:
1018 ERR("SESSION %u: Received another <hello> message.", session->id);
1019 goto error;
1020 case NC_MSG_RPC:
1021 ERR("SESSION %u: Received <rpc> from NETCONF server.", session->id);
1022 goto error;
1023 default:
1024 /* NC_MSG_WOULDBLOCK and NC_MSG_ERROR - pass it out;
1025 * NC_MSG_REPLY already handled before the switch;
1026 * NC_MSG_NONE is not returned by nc_read_msg()
1027 */
1028 break;
1029 }
1030
Radek Krejci8800a492015-10-31 17:51:27 +01001031 } while(msgtype == NC_MSG_NOTIF);
Radek Krejci5686ff72015-10-09 13:33:56 +02001032
1033 return msgtype;
1034
1035error:
1036
1037 /* cleanup */
1038 lyxml_free_elem(session->ctx, xml);
1039
1040 return NC_MSG_ERROR;
Radek Krejci206fcd62015-10-07 15:42:48 +02001041}
Radek Krejcife0b3472015-10-12 13:43:42 +02001042
Radek Krejci695d4fa2015-10-22 13:23:54 +02001043static NC_MSG_TYPE
1044nc_send_hello_(struct nc_session *session)
1045{
1046 int r;
1047 char **cpblts;
1048
1049 if (session->side == NC_CLIENT) {
1050 /* client side hello - send only NETCONF base capabilities */
1051 cpblts = malloc(3 * sizeof *cpblts);
1052 cpblts[0] = "urn:ietf:params:netconf:base:1.0";
1053 cpblts[1] = "urn:ietf:params:netconf:base:1.1";
1054 cpblts[2] = NULL;
1055
1056 r = nc_write_msg(session, NC_MSG_HELLO, cpblts, NULL);
1057 free(cpblts);
1058 }
1059
1060
1061 if (r) {
1062 return NC_MSG_ERROR;
1063 } else {
1064 return NC_MSG_HELLO;
1065 }
1066}
1067
1068static NC_MSG_TYPE
1069nc_send_rpc_(struct nc_session *session, struct lyd_node *op)
Radek Krejcife0b3472015-10-12 13:43:42 +02001070{
1071 int r;
1072
Radek Krejci695d4fa2015-10-22 13:23:54 +02001073 r = nc_write_msg(session, NC_MSG_RPC, op, NULL);
Radek Krejcife0b3472015-10-12 13:43:42 +02001074
1075 if (r) {
1076 return NC_MSG_ERROR;
1077 } else {
1078 return NC_MSG_RPC;
1079 }
1080}
1081
Radek Krejci695d4fa2015-10-22 13:23:54 +02001082API NC_MSG_TYPE
1083nc_send_rpc(struct nc_session *session, struct nc_rpc *rpc)
1084{
1085 NC_MSG_TYPE r;
1086 struct nc_rpc_lock *rpc_lock;
1087 struct nc_rpc_getconfig *rpc_gc;
Radek Krejci926a5742015-10-31 17:50:49 +01001088 struct nc_rpc_get *rpc_g;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001089 struct lyd_node *data, *node;
1090 struct lys_module *ietfnc;
1091
1092 if (!session || !rpc || rpc->type == NC_RPC_SERVER) {
1093 ERR("%s: Invalid parameter", __func__);
1094 return NC_MSG_ERROR;
1095 } else if (session->status != NC_STATUS_RUNNING || session->side != NC_CLIENT) {
1096 ERR("%s: invalid session to send RPCs.", __func__);
1097 return NC_MSG_ERROR;
1098 }
1099
1100 ietfnc = ly_ctx_get_module(session->ctx, "ietf-netconf", NULL);
1101 if (!ietfnc) {
1102 ERR("%s: Missing ietf-netconf schema in context (session %u)", session->id);
1103 return NC_MSG_ERROR;
1104 }
1105
1106 switch(rpc->type) {
Radek Krejci926a5742015-10-31 17:50:49 +01001107 case NC_RPC_GET:
1108 rpc_g = (struct nc_rpc_get *)rpc;
1109
1110 data = lyd_new(NULL, ietfnc, "get");
1111 if (rpc_g->filter) {
1112 if (rpc_g->filter->type == NC_FILTER_SUBTREE) {
1113 node = lyd_new_anyxml(data, ietfnc, "filter", rpc_g->filter->data);
1114 lyd_insert_attr(node, "type", "subtree");
1115 } else if (rpc_g->filter->type == NC_FILTER_XPATH) {
1116 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
1117 /* TODO - handle namespaces from XPATH query */
1118 lyd_insert_attr(node, "type", "xpath");
1119 lyd_insert_attr(node, "select", rpc_g->filter->data);
1120 }
1121 }
1122 break;
Radek Krejci695d4fa2015-10-22 13:23:54 +02001123 case NC_RPC_GETCONFIG:
1124 rpc_gc = (struct nc_rpc_getconfig *)rpc;
1125
1126 data = lyd_new(NULL, ietfnc, "get-config");
1127 node = lyd_new(data, ietfnc, "source");
Radek Krejcib68f7c22015-10-31 11:04:40 +01001128 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_gc->source], NULL);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001129 if (!node) {
1130 lyd_free(data);
1131 return NC_MSG_ERROR;
1132 }
1133 if (rpc_gc->filter) {
1134 if (rpc_gc->filter->type == NC_FILTER_SUBTREE) {
1135 node = lyd_new_anyxml(data, ietfnc, "filter", rpc_gc->filter->data);
1136 lyd_insert_attr(node, "type", "subtree");
1137 } else if (rpc_gc->filter->type == NC_FILTER_XPATH) {
1138 node = lyd_new_anyxml(data, ietfnc, "filter", NULL);
1139 /* TODO - handle namespaces from XPATH query */
1140 lyd_insert_attr(node, "type", "xpath");
1141 lyd_insert_attr(node, "select", rpc_gc->filter->data);
1142 }
1143 }
1144 break;
1145 case NC_RPC_LOCK:
1146 rpc_lock = (struct nc_rpc_lock *)rpc;
1147
1148 data = lyd_new(NULL, ietfnc, "lock");
1149 node = lyd_new(data, ietfnc, "target");
Radek Krejcib68f7c22015-10-31 11:04:40 +01001150 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_lock->target], NULL);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001151 if (!node) {
1152 lyd_free(data);
1153 return NC_MSG_ERROR;
1154 }
1155 break;
1156 case NC_RPC_UNLOCK:
1157 rpc_lock = (struct nc_rpc_lock *)rpc;
1158
1159 data = lyd_new(NULL, ietfnc, "unlock");
1160 node = lyd_new(data, ietfnc, "target");
Radek Krejcib68f7c22015-10-31 11:04:40 +01001161 node = lyd_new_leaf(node, ietfnc, ncds2str[rpc_lock->target], NULL);
Radek Krejci695d4fa2015-10-22 13:23:54 +02001162 if (!node) {
1163 lyd_free(data);
1164 return NC_MSG_ERROR;
1165 }
1166 break;
1167 }
1168
1169 r = session_ti_lock(session, 0);
1170 if (r != 0) {
1171 /* error or blocking */
1172 r = NC_MSG_WOULDBLOCK;
1173 } else {
1174 /* send RPC */
1175 r = nc_send_rpc_(session, data);
1176 }
1177 session_ti_unlock(session);
1178
1179 lyd_free(data);
1180 return r;
1181}