RESTCONF notification for native mode

This commit is contained in:
Olof hagsand 2024-05-19 17:44:00 +02:00
parent 7a842846db
commit 1336a4ccfb
14 changed files with 282 additions and 533 deletions

View file

@ -15,6 +15,9 @@ Expected: June 2024
### Features
* RESTCONF notification for native mode
* Previously only for FCGI
* Limitations, ie only HTTP/1, regular subscription + stop-time
* Optimization of yang schema mount: share yang-specs if equal
* Changed datastore modstate to be last in file, as prior to 7.0
* New: Event priority. Backend socket has higher prio

View file

@ -88,7 +88,6 @@ LEX = @LEX@
CPPFLAGS = @CPPFLAGS@
ifeq ($(LINKAGE),dynamic)
CPPFLAGS += -fPIC
endif
@ -100,7 +99,6 @@ APPL = clixon_restconf
# Common source - not accessible from plugin - independent of restconf package (fcgi|native)
APPSRC =
APPSRC += restconf_api.c # maybe empty
APPSRC += restconf_err.c
APPSRC += restconf_methods.c
APPSRC += restconf_methods_post.c

View file

@ -10,11 +10,19 @@ There are two installation instructions: for native and nginx.
## Native
Native with http1 and http2 is the main variant, with most regression testing.
Configure clixon with native restconf:
```
./configure --with-restconf=native
```
You can disable http1 and http2:
```
--disable-http1 Disable native http/1.1 (ie http/2 only)
--disable-nghttp2 Disable native http/2 using libnghttp2 (ie http/1 only)
```
Ensure www-data is member of the CLICON_SOCK_GROUP (default clicon). If not, add it:
```
sudo usermod -a -G clicon www-data
@ -164,6 +172,8 @@ See (stream tests)[../test/test_streams.sh] for more examples.
## Nchan
This is not supported
As an alternative streams implementation, Nginx/Nchan can be used.
Nginx uses pub/sub channels and can be configured in a variety of
ways. The following uses a simple variant with one generic subscription
@ -247,3 +257,16 @@ You can set debug level of the backend via restconf:
```
curl -is -X POST -H "Content-Type: application/yang-data+json" -d '{"clixon-lib:input":{"level":1}}' http://localhost/restconf/operations/clixon-lib:debug
```
## Code structure
Due to the native and fcgi variants, and also native http1/http2, the
source file structure is complex.
There are the following blocks of files:
* COMMON: Common code, such as HTTP methods processing, error and common lib functions
* FCGI: Top-level main, stream and low-level lib (as defined by restconf_api.h)
* NATIVE-COMMON: Top-level main, stream and low-level lib
* NATIVE-HTTP1: Native for HTTP/1 only
* NATIVE-HTTP2: Native for Libnghttp2 only

View file

@ -1,67 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* Generic restconf API functions
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <libgen.h>
#include <sys/stat.h> /* chmod */
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include <clixon/clixon.h>
/* restconf */
#include "restconf_lib.h"
#include "restconf_api.h"
/* empty */

View file

@ -67,9 +67,7 @@
#include "clixon_http1_parse.h"
#include "restconf_http1.h"
#include "clixon_http_data.h"
#ifdef RESTCONF_NATIVE_STREAM
#include "restconf_stream.h"
#endif
/* Size of xml read buffer */
#define BUFLEN 1024
@ -453,13 +451,11 @@ restconf_http1_path_root(clixon_handle h,
if (api_http_data(h, sd, sd->sd_qvec) < 0)
goto done;
}
#ifdef RESTCONF_NATIVE_STREAM
else if (api_path_is_stream(h)){
restconf_socket *rs = rc->rc_socket;
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
goto done;
}
#endif
else
sd->sd_code = 404; /* catch all without body/media */
fail:

View file

@ -76,9 +76,7 @@
#ifdef HAVE_HTTP1
#include "restconf_http1.h"
#endif
#ifdef RESTCONF_NATIVE_STREAM
#include "restconf_stream.h"
#endif
/* Forward */
static int restconf_idle_cb(int fd, void *arg);
@ -384,7 +382,8 @@ restconf_connection_sanity(clixon_handle h,
}
/* Write buf to socket
* see also this function in restcont_api_openssl.c
*
* see also this function in restconf_api_openssl.c
* @param[in] h Clixon handle
* @param[in] buf Buffer to write
* @param[in] buflen Length of buffer
@ -394,7 +393,7 @@ restconf_connection_sanity(clixon_handle h,
* @retval 0 OK, but socket write returned error, caller should close rc
* @retval -1 Error
*/
static int
int
native_buf_write(clixon_handle h,
char *buf,
size_t buflen,
@ -1081,11 +1080,9 @@ restconf_connection_close1(restconf_conn *rc)
if (restconf_callhome_timer(rsock, 1) < 0)
goto done;
}
#ifdef RESTCONF_NATIVE_STREAM
if (rc->rc_event_stream){
stream_close(rc->rc_h, rc);
}
#endif
retval = 0;
done:
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);

View file

@ -121,9 +121,7 @@ typedef struct restconf_conn {
restconf_socket *rc_socket; /* Backpointer to restconf_socket needed for callhome */
struct timeval rc_t; /* Timestamp of last read/write activity, used by callhome
idle-timeout algorithm */
#ifdef RESTCONF_NATIVE_STREAM
int rc_event_stream; /* Event notification stream socket (maybe in sd?) */
#endif
} restconf_conn;
/* Restconf per socket handle
@ -182,6 +180,7 @@ int ssl_x509_name_oneline(SSL *ssl, char **oneline);
int restconf_close_ssl_socket(restconf_conn *rc, const char *callfn, int sslerr0);
int restconf_connection_sanity(clixon_handle h, restconf_conn *rc, restconf_stream_data *sd);
int native_buf_write(clixon_handle h, char *buf, size_t buflen, restconf_conn *rc, const char *callfn);
restconf_native_handle *restconf_native_handle_get(clixon_handle h);
int restconf_connection(int s, void *arg);
int restconf_ssl_accept_client(clixon_handle h, int s, restconf_socket *rsock, restconf_conn **rcp);
@ -190,6 +189,8 @@ int restconf_callhome_timer(restconf_socket *rsock, int status);
int restconf_socket_extract(clixon_handle h, cxobj *xs, cvec *nsc, restconf_socket *rsock,
char **namespace, char **address, char **addrtype, uint16_t *port);
#endif /* _RESTCONF_NATIVE_H_ */
#ifdef __cplusplus

View file

@ -85,9 +85,7 @@
#include "restconf_err.h"
#include "restconf_root.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#ifdef RESTCONF_NATIVE_STREAM
#include "restconf_stream.h"
#endif
#ifdef HAVE_LIBNGHTTP2 /* Ends at end-of-file */
#include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/
#include "clixon_http_data.h"
@ -341,13 +339,11 @@ restconf_nghttp2_path(restconf_stream_data *sd)
if (api_http_data(h, sd, sd->sd_qvec) < 0)
goto done;
}
#ifdef RESTCONF_NATIVE_STREAM
else if (api_path_is_stream(h)){
restconf_socket *rs = rc->rc_socket;
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
goto done;
}
#endif
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */
goto done;
}
@ -497,9 +493,7 @@ http2_exec(restconf_conn *rc,
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|| api_path_is_restconf(rc->rc_h)
|| api_path_is_data(rc->rc_h)
#ifdef RESTCONF_NATIVE_STREAM
|| api_path_is_stream(rc->rc_h)
#endif
) {
clixon_debug(CLIXON_DBG_RESTCONF, "path found");
if (restconf_nghttp2_path(sd) < 0)

View file

@ -179,6 +179,8 @@ restconf_subscription(clixon_handle h,
goto ok;
}
/* Setting up stream */
if (restconf_reply_header(req, "Server", "clixon") < 0)
goto done;
if (restconf_reply_header(req, "Server", "clixon") < 0)
goto done;
if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0)
@ -187,10 +189,9 @@ restconf_subscription(clixon_handle h,
goto done;
if (restconf_reply_header(req, "Connection", "keep-alive") < 0)
goto done;
#ifndef RESTCONF_NATIVE_STREAM
/* Must be there for FCGI caching */
if (restconf_reply_header(req, "X-Accel-Buffering", "no") < 0)
goto done;
#endif
if (restconf_reply_send(req, 201, NULL, 0) < 0)
goto done;
*sp = s;
@ -204,3 +205,120 @@ restconf_subscription(clixon_handle h,
cbuf_free(cb);
return retval;
}
/*! Process a stream request
*
* @param[in] h Clixon handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @param[in] timeout Stream timeout
* @param[out] finish Set to zero, if request should not be finnished by upper layer
* @retval 0 OK
* @retval -1 Error
*/
int
api_stream(clixon_handle h,
void *req,
cvec *qvec,
int timeout,
int *finish)
{
int retval = -1;
char *path = NULL;
char **pvec = NULL;
int pn;
cvec *pcvec = NULL; /* for rest api */
cxobj *xerr = NULL;
char *streampath;
int pretty;
int besock = -1;
restconf_media media_reply = YANG_DATA_XML;
char *media_str = NULL;
char *stream_name;
int ret;
clixon_debug(CLIXON_DBG_STREAM, "");
if (req == NULL){
clixon_err(OE_RESTCONF, EINVAL, "req is NULL");
goto done;
}
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
if ((path = restconf_uripath(h)) == NULL)
goto done;
pretty = restconf_pretty_get(h);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /stream/<name> */
if (pn != 3){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
/* Get media for output (proactive negotiation) RFC7231 by using
*/
media_str = restconf_param_get(h, "HTTP_ACCEPT");
if (media_str == NULL){
if (restconf_not_acceptable(h, req, pretty, media_reply) < 0)
goto done;
goto ok;
}
/* Accept only text_event-stream or */
if (strcmp(media_str, "*/*") != 0 &&
strcmp(media_str, "text/event-stream") != 0){
if (restconf_not_acceptable(h, req, pretty, media_reply) < 0)
goto done;
goto ok;
}
if (strlen(pvec[0]) != 0){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
if (strcmp(pvec[1], streampath)){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
if ((stream_name = pvec[2]) == NULL){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
clixon_debug(CLIXON_DBG_STREAM, "stream-name: %s", stream_name);
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((ret = restconf_authentication_cb(h, req, pretty, media_reply)) < 0)
goto done;
if (ret == 0)
goto ok;
if (restconf_subscription(h, req, stream_name, qvec, pretty, media_reply, &besock) < 0)
goto done;
if (besock != -1){
if (stream_sockets_setup(h, req, timeout, besock, finish) < 0)
goto done;
}
ok:
retval = 0;
done:
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
if (xerr)
xml_free(xerr);
if (pvec)
free(pvec);
if (pcvec)
cvec_free(pcvec);
if (path)
free(path);
return retval;
}

View file

@ -42,9 +42,10 @@
*/
int api_path_is_stream(clixon_handle h);
int restconf_subscription(clixon_handle h, void *req, char *name, cvec *qvec, int pretty, restconf_media media_out, int *sp);
int stream_child_free(clixon_handle h, int pid);
int stream_child_freeall(clixon_handle h);
int stream_close(clixon_handle h, void *req);
int api_stream(clixon_handle h, void *req, cvec *qvec, int timeout, int *finish);
int stream_sockets_setup(clixon_handle h, void *req, int timeout, int besock, int *finish);
int stream_close(clixon_handle h, void *req); // only native
int stream_child_free(clixon_handle h, int pid); // only fcgi
int stream_child_freeall(clixon_handle h); // only fcgi
#endif /* _RESTCONF_STREAM_H_ */

View file

@ -104,8 +104,9 @@
*/
/* Enable for forking stream subscription loop.
* Disable to get single threading but blocking on streams
* XXX: Integrate with top-level events
*/
#define STREAM_FORK
#undef STREAM_FORK
/* Keep track of children - when they exit - their FCGX handle needs to be
* freed with FCGX_Free(&rbk, 0);
@ -120,6 +121,8 @@ struct stream_child{
*/
static struct stream_child *STREAM_CHILD = NULL;
static int backend_eof = 0;
/*! Find restconf child using PID and cleanup FCGI Request data
*
* For forked, called on SIGCHILD
@ -174,11 +177,11 @@ stream_child_freeall(clixon_handle h)
* @see netconf_notification_cb
*/
static int
restconf_stream_cb(int s,
stream_fcgi_backend_cb(int s,
void *arg)
{
int retval = -1;
FCGX_Request *r = (FCGX_Request *)arg;
FCGX_Request *req = (FCGX_Request *)arg;
int eof;
cxobj *xtop = NULL; /* top xml */
cxobj *xn; /* notification xml */
@ -193,14 +196,13 @@ restconf_stream_cb(int s,
clixon_debug(CLIXON_DBG_STREAM, "%s", cbuf_get(cbmsg)); // Also MSG
/* handle close from remote end: this will exit the client */
if (eof){
clixon_debug(CLIXON_DBG_STREAM, "eof");
clixon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
errno = ESHUTDOWN;
FCGX_FPrintF(r->out, "SHUTDOWN\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FFlush(r->out);
clixon_exit_set(1);
goto done;
clixon_debug(CLIXON_DBG_STREAM, "eof, terminate stream");
backend_eof = 1;
clixon_exit_set(1); // local timeout
FCGX_FPrintF(req->out, "SHUTDOWN\r\n");
FCGX_FPrintF(req->out, "\r\n");
FCGX_FFlush(req->out);
goto ok;
}
if ((ret = clixon_xml_parse_string(cbuf_get(cbmsg), YB_NONE, NULL, &xtop, NULL)) < 0)
goto done;
@ -229,9 +231,9 @@ restconf_stream_cb(int s,
#endif
if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0)
goto done;
FCGX_FPrintF(r->out, "data: %s\r\n", cbuf_get(cb));
FCGX_FPrintF(r->out, "\r\n");
FCGX_FFlush(r->out);
FCGX_FPrintF(req->out, "data: %s\r\n", cbuf_get(cb));
FCGX_FPrintF(req->out, "\r\n");
FCGX_FFlush(req->out);
ok:
retval = 0;
done:
@ -251,7 +253,7 @@ restconf_stream_cb(int s,
* @param[in] req Generic Www handle (can be part of clixon handle)
*/
static int
stream_checkuplink(int s,
stream_fcgi_uplink_cb(int s,
void *arg)
{
FCGX_Request *r = (FCGX_Request *)arg;
@ -289,7 +291,7 @@ fcgi_stream_timeout(int s,
/*! Timeout of notification stream, limit lifetime, for debug
*/
static int
fcgi_stream_timeout2(int s,
stream_timeout_end(int s,
void *arg)
{
clixon_debug(CLIXON_DBG_STREAM, "Terminate stream");
@ -297,121 +299,49 @@ fcgi_stream_timeout2(int s,
return 0;
}
/*! Process a stream request
/*! FCGI specific code for setting up stream sockets
*
* @param[in] h Clixon handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @param[in] timeout Stream timeout
* @param[in] besock Socket to backend
* @param[out] finish Set to zero, if request should not be finnished by upper layer
* @retval 0 OK
* @retval -1 Error
* Consider moving timeout and backend sock to generic code
*/
int
api_stream(clixon_handle h,
stream_sockets_setup(clixon_handle h,
void *req,
cvec *qvec,
int timeout,
int besock,
int *finish)
{
int retval = -1;
FCGX_Request *rfcgi = (FCGX_Request *)req; /* XXX */
char *path = NULL;
char *method;
char **pvec = NULL;
int pn;
cvec *pcvec = NULL; /* for rest api */
cbuf *cb = NULL;
char *indata;
int pretty;
restconf_media media_out = YANG_DATA_XML; /* XXX default */
cbuf *cbret = NULL;
int s = -1;
int ret;
cxobj *xerr = NULL;
char *streampath;
#ifdef STREAM_FORK
int pid;
struct stream_child *sc;
#endif
clixon_debug(CLIXON_DBG_STREAM, "");
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
if ((path = restconf_uripath(h)) == NULL)
goto done;
pretty = restconf_pretty_get(h);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /stream/<name> */
if (pn != 3){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (strlen(pvec[0]) != 0){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (strcmp(pvec[1], streampath)){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if ((method = pvec[2]) == NULL){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
clixon_debug(CLIXON_DBG_STREAM, "method=%s", method);
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = restconf_get_indata(req)) == NULL)
goto done;
indata = cbuf_get(cb);
clixon_debug(CLIXON_DBG_STREAM, "DATA=%s", indata);
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((ret = restconf_authentication_cb(h, req, pretty, media_out)) < 0)
goto done;
if (ret == 0)
goto ok;
if (restconf_subscription(h, req, method, qvec, pretty, media_out, &s) < 0)
goto done;
if (s != -1){
#ifdef STREAM_FORK
if ((pid = fork()) == 0){ /* child */
#if 0 // Leaks
if (pvec)
free(pvec);
if (qvec)
cvec_free(qvec);
if (pcvec)
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
#endif
#endif /* STREAM_FORK */
backend_eof = 0;
/* Listen to backend socket */
if (clixon_event_reg_fd(s,
restconf_stream_cb,
if (clixon_event_reg_fd(besock,
stream_fcgi_backend_cb,
req,
"stream socket") < 0)
goto done;
if (clixon_event_reg_fd(rfcgi->listen_sock,
stream_checkuplink,
stream_fcgi_uplink_cb,
req,
"stream socket") < 0)
goto done;
@ -420,20 +350,21 @@ api_stream(clixon_handle h,
struct timeval t;
gettimeofday(&t, NULL);
t.tv_sec += timeout;
clixon_event_reg_timeout(t, fcgi_stream_timeout2, req, "Stream timeout");
clixon_event_reg_timeout(t, stream_timeout_end, req, "Stream timeout");
}
/* Poll upstream errors */
fcgi_stream_timeout(0, req);
/* Start loop */
clixon_event_loop(h);
clixon_debug(CLIXON_DBG_STREAM, "after loop");
clicon_rpc_close_session(h);
clixon_event_unreg_fd(s, restconf_stream_cb);
close(s);
clixon_event_unreg_fd(rfcgi->listen_sock,
restconf_stream_cb);
if (backend_eof == 0)
if (clicon_rpc_close_session(h) < 0)
goto done;
clixon_event_unreg_fd(besock, stream_fcgi_backend_cb);
close(besock);
clixon_event_unreg_fd(rfcgi->listen_sock, stream_fcgi_uplink_cb);
clixon_event_unreg_timeout(fcgi_stream_timeout, (void*)req);
clixon_event_unreg_timeout(fcgi_stream_timeout2, (void*)req);
clixon_event_unreg_timeout(stream_timeout_end, (void*)req);
clixon_exit_set(0); /* reset */
#ifdef STREAM_FORK
#if 0 /* Seems to be a global resource, but there is till some timing error here */
@ -458,22 +389,8 @@ api_stream(clixon_handle h,
ADDQ(sc, STREAM_CHILD);
*finish = 0; /* If spawn child, we should not finish this stream */
#endif /* STREAM_FORK */
}
ok:
retval = 0;
done:
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
if (xerr)
xml_free(xerr);
if (pvec)
free(pvec);
if (pcvec)
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
if (path)
free(path);
return retval;
}

View file

@ -86,101 +86,6 @@
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#include "restconf_stream.h"
// XXX: copy from restconf_native.c
static int
native_buf_write_xxx(clixon_handle h,
char *buf,
size_t buflen,
restconf_conn *rc,
const char *callfn)
{
int retval = -1;
ssize_t len;
ssize_t totlen = 0;
int er;
SSL *ssl;
if (rc == NULL){
clixon_err(OE_RESTCONF, EINVAL, "rc is NULL");
goto done;
}
ssl = rc->rc_ssl;
/* Two problems with debugging buffers that this fixes:
* 1. they are not "strings" in the sense they are not NULL-terminated
* 2. they are often very long
*/
if (clixon_debug_get()) {
char *dbgstr = NULL;
size_t sz;
sz = buflen>256?256:buflen; /* Truncate to 256 */
if ((dbgstr = malloc(sz+1)) == NULL){
clixon_err(OE_UNIX, errno, "malloc");
goto done;
}
memcpy(dbgstr, buf, sz);
dbgstr[sz] = '\0';
clixon_debug(CLIXON_DBG_RESTCONF, "%s buflen:%zu buf:\n%s", callfn, buflen, dbgstr);
free(dbgstr);
}
while (totlen < buflen){
if (ssl){
if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){
er = errno;
switch (SSL_get_error(ssl, len)){
case SSL_ERROR_SYSCALL: /* 5 */
if (er == ECONNRESET || /* Connection reset by peer */
er == EPIPE) { /* Reading end of socket is closed */
goto closed; /* Close socket and ssl */
}
else if (er == EAGAIN){
clixon_debug(CLIXON_DBG_RESTCONF, "write EAGAIN");
usleep(10000);
continue;
}
else{
clixon_err(OE_RESTCONF, er, "SSL_write %d", er);
goto done;
}
break;
default:
clixon_err(OE_SSL, 0, "SSL_write");
goto done;
break;
}
goto done;
}
}
else{
if ((len = write(rc->rc_s, buf+totlen, buflen-totlen)) < 0){
switch (errno){
case EAGAIN: /* Operation would block */
clixon_debug(CLIXON_DBG_RESTCONF, "write EAGAIN");
usleep(10000);
continue;
break;
// case EBADF: // XXX if this happens there is some larger error
case ECONNRESET: /* Connection reset by peer */
case EPIPE: /* Broken pipe */
goto closed; /* Close socket and ssl */
break;
default:
clixon_err(OE_UNIX, errno, "write %d", errno);
goto done;
break;
}
}
}
totlen += len;
} /* while */
retval = 1;
done:
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
return retval;
closed:
retval = 0;
goto done;
}
/*! Callback when stream notifications arrive from backend
*
* @param[in] s Socket
@ -190,7 +95,7 @@ native_buf_write_xxx(clixon_handle h,
* @see netconf_notification_cb
*/
static int
restconf_native_stream_cb(int s,
stream_native_backend_cb(int s,
void *arg)
{
int retval = -1;
@ -258,7 +163,7 @@ restconf_native_stream_cb(int s,
cprintf(cb, "\r\n");
cprintf(cb, "\r\n");
#endif
if ((ret = native_buf_write_xxx(h, cbuf_get(cb), cbuf_len(cb), rc, "native stream")) < 0)
if ((ret = native_buf_write(h, cbuf_get(cb), cbuf_len(cb), rc, "native stream")) < 0)
goto done;
ok:
retval = 0;
@ -278,7 +183,7 @@ restconf_native_stream_cb(int s,
/*! Timeout of notification stream, limit lifetime, for debug
*/
static int
native_stream_timeout(int s,
stream_timeout_end(int s,
void *arg)
{
restconf_conn *rc = (restconf_conn *)arg;
@ -299,170 +204,52 @@ stream_close(clixon_handle h,
restconf_conn *rc = (restconf_conn *)req;
clicon_rpc_close_session(h);
clixon_event_unreg_fd(rc->rc_event_stream, restconf_native_stream_cb);
clixon_event_unreg_timeout(native_stream_timeout, req);
clixon_event_unreg_fd(rc->rc_event_stream, stream_native_backend_cb);
clixon_event_unreg_timeout(stream_timeout_end, req);
close(rc->rc_event_stream);
rc->rc_event_stream = 0;
return 0;
}
/*! Process a stream request, native variant
/*! Native specific code for setting up stream sockets
*
* @param[in] h Clixon handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @param[in] timeout Stream timeout
* @param[out] finish Not used in native?
* @param[in] besock Socket to backend
* @param[out] finish Set to zero, if request should not be finnished by upper layer
* @retval 0 OK
* @retval -1 Error
* @see api_stream fcgi implementation
* @note According to RFC8040 Sec 6 accept-stream is text/event-stream, but stream data
* is XML according to RFC5277. But what is error return? assume XML here
* Consider moving timeout and backend sock to generic code
*/
static int
api_native_stream(clixon_handle h,
int
stream_sockets_setup(clixon_handle h,
void *req,
cvec *qvec,
int timeout,
int besock,
int *finish)
{
int retval = -1;
restconf_stream_data *sd = (restconf_stream_data *)req;
restconf_conn *rc;
char *path = NULL;
char *request_method = NULL; /* GET,.. */
char *streampath;
int pretty;
char **pvec = NULL;
int pn;
cvec *pcvec = NULL; /* for rest api */
cxobj *xerr = NULL;
char *media_str = NULL;
char *stream_name;
restconf_media media_reply = YANG_DATA_XML;
int ret;
int backend_socket = -1;
clixon_debug(CLIXON_DBG_STREAM, "");
if (req == NULL){
clixon_err(OE_RESTCONF, EINVAL, "req is NULL");
goto done;
}
rc = sd->sd_conn;
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
if ((path = restconf_uripath(h)) == NULL)
goto done;
clixon_debug(CLIXON_DBG_STREAM, "path:%s", path);
request_method = restconf_param_get(h, "REQUEST_METHOD");
clixon_debug(CLIXON_DBG_STREAM, "method:%s", request_method);
pretty = restconf_pretty_get(h);
clixon_debug(CLIXON_DBG_STREAM, "pretty:%d", pretty);
/* Get media for output (proactive negotiation) RFC7231 by using
* Accept:. This is for methods that have output, such as GET,
* operation POST, etc
* If accept is * default is yang-json
*/
media_str = restconf_param_get(h, "HTTP_ACCEPT");
clixon_debug(CLIXON_DBG_STREAM, "accept(media):%s", media_str);
if (media_str == NULL){
if (restconf_not_acceptable(h, sd, pretty, media_reply) < 0)
goto done;
goto ok;
}
/* Accept only text_event-stream or */
if (strcmp(media_str, "*/*") != 0 &&
strcmp(media_str, "text/event-stream") != 0){
if (restconf_not_acceptable(h, req, pretty, media_reply) < 0)
goto done;
goto ok;
}
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
if (strlen(pvec[0]) != 0){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
else if (strcmp(pvec[1], streampath)){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
else if ((stream_name = pvec[2]) == NULL ||
strlen(stream_name) == 0){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid path, /stream/<name> expected") < 0)
goto done;
if (api_return_err0(h, req, xerr, pretty, media_reply, 0) < 0)
goto done;
goto ok;
}
clixon_debug(CLIXON_DBG_STREAM, "stream-name: %s", stream_name);
if (uri_str2cvec(path, '/', '=', 1, &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((ret = restconf_authentication_cb(h, req, pretty, media_reply)) < 0)
goto done;
if (ret == 0)
goto ok;
clixon_debug(CLIXON_DBG_STREAM, "passed auth");
if (restconf_subscription(h, req, stream_name, qvec, pretty, media_reply, &backend_socket) < 0)
goto done;
if (backend_socket != -1){
// XXX Could add forking here eventurally
/* Listen to backend socket */
if (clixon_event_reg_fd(backend_socket,
restconf_native_stream_cb,
sd,
if (clixon_event_reg_fd(besock,
stream_native_backend_cb,
req,
"stream socket") < 0)
goto done;
rc->rc_event_stream = backend_socket;
rc = sd->sd_conn;
rc->rc_event_stream = besock;
/* Timeout of notification stream, close after limited lifetime, for debug */
if (timeout){
struct timeval t;
gettimeofday(&t, NULL);
t.tv_sec += timeout;
clixon_event_reg_timeout(t, native_stream_timeout, rc, "Stream timeout");
clixon_event_reg_timeout(t, stream_timeout_end, rc, "Stream timeout");
}
}
ok:
retval = 0;
done:
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
if (xerr)
xml_free(xerr);
if (path)
free(path);
if (pvec)
free(pvec);
if (pcvec)
cvec_free(pcvec);
return retval;
}
/*! Process a stream request, native variant
*
* @param[in] h Clixon handle
* @param[in] req Generic Www handle (can be part of clixon handle)
* @param[in] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @param[out] finish Not used in native?
* @retval 0 OK
* @retval -1 Error
* @see api_stream fcgi implementation
* @note According to RFC8040 Sec 6 accept-stream is text/event-stream, but stream data
* is XML according to RFC5277. But what is error return? assume XML here
*/
int
api_stream(clixon_handle h,
void *req,
cvec *qvec,
int timeout,
int *finish)
{
return api_native_stream(h, req, qvec, timeout, finish);
}

View file

@ -212,10 +212,6 @@
*/
#undef USE_SHA256
/*! Restconf native stream support
*/
#define RESTCONF_NATIVE_STREAM
/*! Temporary comparison of xyanglibs, should be removed asap
*/
#define YANG_SCHEMA_CMP_KLUDGE

View file

@ -133,7 +133,6 @@ function runtest()
new "2a) start $extra timeout:${TIMEOUT}s - expect ${LBOUND}-${UBOUND} notifications"
ret=$(curl $CURLOPTS $extra -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/EXAMPLE)
match=$(echo "$ret" | grep -Eo "$expect")
if [ -z "$match" ]; then
err "$expect" "$ret"
@ -149,7 +148,6 @@ function runtest()
new "2b) start $extra timeout:${TIMEOUT} stop after 5s - expect ${LB}-${UB} notifications"
ret=$(curl $CURLOPTS $extra -X GET -H "Accept: text/event-stream" -H "Cache-Control: no-cache" -H "Connection: keep-alive" $RCPROTO://localhost/streams/EXAMPLE?stop-time=${time1})
match=$(echo "$ret" | grep -Eo "$expect")
if [ -z "$match" ]; then
err "$expect" "$ret"
@ -181,6 +179,9 @@ if [ $BE -ne 0 ]; then
if [ $? -ne 0 ]; then
err
fi
sudo pkill -f clixon_backend # to be sure
fi
if [ $BE -ne 0 ]; then
new "start backend -s init -f $cfg -- -n ${PERIOD}"
# create example notification stream with periodic timeout ${PERIOD} seconds
start_backend -s init -f $cfg -- -n ${PERIOD}
@ -193,6 +194,7 @@ if [ $RC -ne 0 ]; then
new "kill old restconf daemon"
stop_restconf_pre
sleep 1
new "start restconf daemon -f $cfg -t ${TIMEOUT}"
start_restconf -f $cfg -t ${TIMEOUT}
fi
@ -233,23 +235,6 @@ if [ "${WITH_RESTCONF}" = "native" ]; then
fi
if false; then # NYI
test-pause
# 2b) start subscription 8s - stoptime after 5s - expect 1-2 notifications
new "2b) start subscriptions 8s - stoptime after 5s - expect 1-2 notifications"
ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 8 -e +10)
expect="data: <notification xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\"><eventTime>${DATE}T[0-9:.]*Z</eventTime><event xmlns=\"urn:example:clixon\"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>"
match=$(echo "$ret" | grep -Eo "$expect")
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
nr=$(echo "$ret" | grep -c "data:")
if [ $nr -lt 1 -o $nr -gt 2 ]; then
err 1 "$nr"
fi
test-pause
# 2c
new "2c) start sub 8s - replay from start -8s - expect 3-4 notifications"
ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 10 -s -8)