RESTCONF notification for native mode
This commit is contained in:
parent
7a842846db
commit
1336a4ccfb
14 changed files with 282 additions and 533 deletions
|
|
@ -15,6 +15,9 @@ Expected: June 2024
|
||||||
|
|
||||||
### Features
|
### 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
|
* Optimization of yang schema mount: share yang-specs if equal
|
||||||
* Changed datastore modstate to be last in file, as prior to 7.0
|
* Changed datastore modstate to be last in file, as prior to 7.0
|
||||||
* New: Event priority. Backend socket has higher prio
|
* New: Event priority. Backend socket has higher prio
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,6 @@ LEX = @LEX@
|
||||||
|
|
||||||
CPPFLAGS = @CPPFLAGS@
|
CPPFLAGS = @CPPFLAGS@
|
||||||
|
|
||||||
|
|
||||||
ifeq ($(LINKAGE),dynamic)
|
ifeq ($(LINKAGE),dynamic)
|
||||||
CPPFLAGS += -fPIC
|
CPPFLAGS += -fPIC
|
||||||
endif
|
endif
|
||||||
|
|
@ -100,7 +99,6 @@ APPL = clixon_restconf
|
||||||
|
|
||||||
# Common source - not accessible from plugin - independent of restconf package (fcgi|native)
|
# Common source - not accessible from plugin - independent of restconf package (fcgi|native)
|
||||||
APPSRC =
|
APPSRC =
|
||||||
APPSRC += restconf_api.c # maybe empty
|
|
||||||
APPSRC += restconf_err.c
|
APPSRC += restconf_err.c
|
||||||
APPSRC += restconf_methods.c
|
APPSRC += restconf_methods.c
|
||||||
APPSRC += restconf_methods_post.c
|
APPSRC += restconf_methods_post.c
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,25 @@
|
||||||
* [Nginx](#nginx)
|
* [Nginx](#nginx)
|
||||||
* [Streams](#streams)
|
* [Streams](#streams)
|
||||||
* [Nchan Streams](#nchan)
|
* [Nchan Streams](#nchan)
|
||||||
* [Debugging](#debugging)
|
* [Debugging](#debugging)
|
||||||
|
|
||||||
There are two installation instructions: for native and nginx.
|
There are two installation instructions: for native and nginx.
|
||||||
|
|
||||||
## Native
|
## Native
|
||||||
|
|
||||||
|
Native with http1 and http2 is the main variant, with most regression testing.
|
||||||
|
|
||||||
Configure clixon with native restconf:
|
Configure clixon with native restconf:
|
||||||
```
|
```
|
||||||
./configure --with-restconf=native
|
./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:
|
Ensure www-data is member of the CLICON_SOCK_GROUP (default clicon). If not, add it:
|
||||||
```
|
```
|
||||||
sudo usermod -a -G clicon www-data
|
sudo usermod -a -G clicon www-data
|
||||||
|
|
@ -164,6 +172,8 @@ See (stream tests)[../test/test_streams.sh] for more examples.
|
||||||
|
|
||||||
## Nchan
|
## Nchan
|
||||||
|
|
||||||
|
This is not supported
|
||||||
|
|
||||||
As an alternative streams implementation, Nginx/Nchan can be used.
|
As an alternative streams implementation, Nginx/Nchan can be used.
|
||||||
Nginx uses pub/sub channels and can be configured in a variety of
|
Nginx uses pub/sub channels and can be configured in a variety of
|
||||||
ways. The following uses a simple variant with one generic subscription
|
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
|
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
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
|
||||||
|
|
@ -67,9 +67,7 @@
|
||||||
#include "clixon_http1_parse.h"
|
#include "clixon_http1_parse.h"
|
||||||
#include "restconf_http1.h"
|
#include "restconf_http1.h"
|
||||||
#include "clixon_http_data.h"
|
#include "clixon_http_data.h"
|
||||||
#ifdef RESTCONF_NATIVE_STREAM
|
|
||||||
#include "restconf_stream.h"
|
#include "restconf_stream.h"
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Size of xml read buffer */
|
/* Size of xml read buffer */
|
||||||
#define BUFLEN 1024
|
#define BUFLEN 1024
|
||||||
|
|
@ -453,13 +451,11 @@ restconf_http1_path_root(clixon_handle h,
|
||||||
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
#ifdef RESTCONF_NATIVE_STREAM
|
|
||||||
else if (api_path_is_stream(h)){
|
else if (api_path_is_stream(h)){
|
||||||
restconf_socket *rs = rc->rc_socket;
|
restconf_socket *rs = rc->rc_socket;
|
||||||
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
|
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
else
|
else
|
||||||
sd->sd_code = 404; /* catch all without body/media */
|
sd->sd_code = 404; /* catch all without body/media */
|
||||||
fail:
|
fail:
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,7 @@
|
||||||
#ifdef HAVE_HTTP1
|
#ifdef HAVE_HTTP1
|
||||||
#include "restconf_http1.h"
|
#include "restconf_http1.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef RESTCONF_NATIVE_STREAM
|
|
||||||
#include "restconf_stream.h"
|
#include "restconf_stream.h"
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Forward */
|
/* Forward */
|
||||||
static int restconf_idle_cb(int fd, void *arg);
|
static int restconf_idle_cb(int fd, void *arg);
|
||||||
|
|
@ -384,7 +382,8 @@ restconf_connection_sanity(clixon_handle h,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write buf to socket
|
/* 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] h Clixon handle
|
||||||
* @param[in] buf Buffer to write
|
* @param[in] buf Buffer to write
|
||||||
* @param[in] buflen Length of buffer
|
* @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 0 OK, but socket write returned error, caller should close rc
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
*/
|
*/
|
||||||
static int
|
int
|
||||||
native_buf_write(clixon_handle h,
|
native_buf_write(clixon_handle h,
|
||||||
char *buf,
|
char *buf,
|
||||||
size_t buflen,
|
size_t buflen,
|
||||||
|
|
@ -1081,11 +1080,9 @@ restconf_connection_close1(restconf_conn *rc)
|
||||||
if (restconf_callhome_timer(rsock, 1) < 0)
|
if (restconf_callhome_timer(rsock, 1) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
#ifdef RESTCONF_NATIVE_STREAM
|
|
||||||
if (rc->rc_event_stream){
|
if (rc->rc_event_stream){
|
||||||
stream_close(rc->rc_h, rc);
|
stream_close(rc->rc_h, rc);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
|
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
|
||||||
|
|
|
||||||
|
|
@ -121,9 +121,7 @@ typedef struct restconf_conn {
|
||||||
restconf_socket *rc_socket; /* Backpointer to restconf_socket needed for callhome */
|
restconf_socket *rc_socket; /* Backpointer to restconf_socket needed for callhome */
|
||||||
struct timeval rc_t; /* Timestamp of last read/write activity, used by callhome
|
struct timeval rc_t; /* Timestamp of last read/write activity, used by callhome
|
||||||
idle-timeout algorithm */
|
idle-timeout algorithm */
|
||||||
#ifdef RESTCONF_NATIVE_STREAM
|
|
||||||
int rc_event_stream; /* Event notification stream socket (maybe in sd?) */
|
int rc_event_stream; /* Event notification stream socket (maybe in sd?) */
|
||||||
#endif
|
|
||||||
} restconf_conn;
|
} restconf_conn;
|
||||||
|
|
||||||
/* Restconf per socket handle
|
/* 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_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 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);
|
restconf_native_handle *restconf_native_handle_get(clixon_handle h);
|
||||||
int restconf_connection(int s, void *arg);
|
int restconf_connection(int s, void *arg);
|
||||||
int restconf_ssl_accept_client(clixon_handle h, int s, restconf_socket *rsock, restconf_conn **rcp);
|
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,
|
int restconf_socket_extract(clixon_handle h, cxobj *xs, cvec *nsc, restconf_socket *rsock,
|
||||||
char **namespace, char **address, char **addrtype, uint16_t *port);
|
char **namespace, char **address, char **addrtype, uint16_t *port);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* _RESTCONF_NATIVE_H_ */
|
#endif /* _RESTCONF_NATIVE_H_ */
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,7 @@
|
||||||
#include "restconf_err.h"
|
#include "restconf_err.h"
|
||||||
#include "restconf_root.h"
|
#include "restconf_root.h"
|
||||||
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
||||||
#ifdef RESTCONF_NATIVE_STREAM
|
|
||||||
#include "restconf_stream.h"
|
#include "restconf_stream.h"
|
||||||
#endif
|
|
||||||
#ifdef HAVE_LIBNGHTTP2 /* Ends at end-of-file */
|
#ifdef HAVE_LIBNGHTTP2 /* Ends at end-of-file */
|
||||||
#include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/
|
#include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/
|
||||||
#include "clixon_http_data.h"
|
#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)
|
if (api_http_data(h, sd, sd->sd_qvec) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
#ifdef RESTCONF_NATIVE_STREAM
|
|
||||||
else if (api_path_is_stream(h)){
|
else if (api_path_is_stream(h)){
|
||||||
restconf_socket *rs = rc->rc_socket;
|
restconf_socket *rs = rc->rc_socket;
|
||||||
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
|
if (api_stream(h, sd, sd->sd_qvec, rs->rs_stream_timeout, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */
|
else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) /* error handling */
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
@ -497,9 +493,7 @@ http2_exec(restconf_conn *rc,
|
||||||
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|
if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0
|
||||||
|| api_path_is_restconf(rc->rc_h)
|
|| api_path_is_restconf(rc->rc_h)
|
||||||
|| api_path_is_data(rc->rc_h)
|
|| api_path_is_data(rc->rc_h)
|
||||||
#ifdef RESTCONF_NATIVE_STREAM
|
|
||||||
|| api_path_is_stream(rc->rc_h)
|
|| api_path_is_stream(rc->rc_h)
|
||||||
#endif
|
|
||||||
) {
|
) {
|
||||||
clixon_debug(CLIXON_DBG_RESTCONF, "path found");
|
clixon_debug(CLIXON_DBG_RESTCONF, "path found");
|
||||||
if (restconf_nghttp2_path(sd) < 0)
|
if (restconf_nghttp2_path(sd) < 0)
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,8 @@ restconf_subscription(clixon_handle h,
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
/* Setting up stream */
|
/* Setting up stream */
|
||||||
|
if (restconf_reply_header(req, "Server", "clixon") < 0)
|
||||||
|
goto done;
|
||||||
if (restconf_reply_header(req, "Server", "clixon") < 0)
|
if (restconf_reply_header(req, "Server", "clixon") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0)
|
if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0)
|
||||||
|
|
@ -187,10 +189,9 @@ restconf_subscription(clixon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
if (restconf_reply_header(req, "Connection", "keep-alive") < 0)
|
if (restconf_reply_header(req, "Connection", "keep-alive") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
#ifndef RESTCONF_NATIVE_STREAM
|
/* Must be there for FCGI caching */
|
||||||
if (restconf_reply_header(req, "X-Accel-Buffering", "no") < 0)
|
if (restconf_reply_header(req, "X-Accel-Buffering", "no") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
#endif
|
|
||||||
if (restconf_reply_send(req, 201, NULL, 0) < 0)
|
if (restconf_reply_send(req, 201, NULL, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
*sp = s;
|
*sp = s;
|
||||||
|
|
@ -204,3 +205,120 @@ restconf_subscription(clixon_handle h,
|
||||||
cbuf_free(cb);
|
cbuf_free(cb);
|
||||||
return retval;
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,10 @@
|
||||||
*/
|
*/
|
||||||
int api_path_is_stream(clixon_handle h);
|
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 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 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_ */
|
#endif /* _RESTCONF_STREAM_H_ */
|
||||||
|
|
|
||||||
|
|
@ -104,8 +104,9 @@
|
||||||
*/
|
*/
|
||||||
/* Enable for forking stream subscription loop.
|
/* Enable for forking stream subscription loop.
|
||||||
* Disable to get single threading but blocking on streams
|
* 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
|
/* Keep track of children - when they exit - their FCGX handle needs to be
|
||||||
* freed with FCGX_Free(&rbk, 0);
|
* freed with FCGX_Free(&rbk, 0);
|
||||||
|
|
@ -120,6 +121,8 @@ struct stream_child{
|
||||||
*/
|
*/
|
||||||
static struct stream_child *STREAM_CHILD = NULL;
|
static struct stream_child *STREAM_CHILD = NULL;
|
||||||
|
|
||||||
|
static int backend_eof = 0;
|
||||||
|
|
||||||
/*! Find restconf child using PID and cleanup FCGI Request data
|
/*! Find restconf child using PID and cleanup FCGI Request data
|
||||||
*
|
*
|
||||||
* For forked, called on SIGCHILD
|
* For forked, called on SIGCHILD
|
||||||
|
|
@ -174,11 +177,11 @@ stream_child_freeall(clixon_handle h)
|
||||||
* @see netconf_notification_cb
|
* @see netconf_notification_cb
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
restconf_stream_cb(int s,
|
stream_fcgi_backend_cb(int s,
|
||||||
void *arg)
|
void *arg)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
FCGX_Request *r = (FCGX_Request *)arg;
|
FCGX_Request *req = (FCGX_Request *)arg;
|
||||||
int eof;
|
int eof;
|
||||||
cxobj *xtop = NULL; /* top xml */
|
cxobj *xtop = NULL; /* top xml */
|
||||||
cxobj *xn; /* notification 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
|
clixon_debug(CLIXON_DBG_STREAM, "%s", cbuf_get(cbmsg)); // Also MSG
|
||||||
/* handle close from remote end: this will exit the client */
|
/* handle close from remote end: this will exit the client */
|
||||||
if (eof){
|
if (eof){
|
||||||
clixon_debug(CLIXON_DBG_STREAM, "eof");
|
clixon_debug(CLIXON_DBG_STREAM, "eof, terminate stream");
|
||||||
clixon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
|
backend_eof = 1;
|
||||||
errno = ESHUTDOWN;
|
clixon_exit_set(1); // local timeout
|
||||||
FCGX_FPrintF(r->out, "SHUTDOWN\r\n");
|
FCGX_FPrintF(req->out, "SHUTDOWN\r\n");
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(req->out, "\r\n");
|
||||||
FCGX_FFlush(r->out);
|
FCGX_FFlush(req->out);
|
||||||
clixon_exit_set(1);
|
goto ok;
|
||||||
goto done;
|
|
||||||
}
|
}
|
||||||
if ((ret = clixon_xml_parse_string(cbuf_get(cbmsg), YB_NONE, NULL, &xtop, NULL)) < 0)
|
if ((ret = clixon_xml_parse_string(cbuf_get(cbmsg), YB_NONE, NULL, &xtop, NULL)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -229,9 +231,9 @@ restconf_stream_cb(int s,
|
||||||
#endif
|
#endif
|
||||||
if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0)
|
if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
FCGX_FPrintF(r->out, "data: %s\r\n", cbuf_get(cb));
|
FCGX_FPrintF(req->out, "data: %s\r\n", cbuf_get(cb));
|
||||||
FCGX_FPrintF(r->out, "\r\n");
|
FCGX_FPrintF(req->out, "\r\n");
|
||||||
FCGX_FFlush(r->out);
|
FCGX_FFlush(req->out);
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
|
@ -251,8 +253,8 @@ restconf_stream_cb(int s,
|
||||||
* @param[in] req Generic Www handle (can be part of clixon handle)
|
* @param[in] req Generic Www handle (can be part of clixon handle)
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
stream_checkuplink(int s,
|
stream_fcgi_uplink_cb(int s,
|
||||||
void *arg)
|
void *arg)
|
||||||
{
|
{
|
||||||
FCGX_Request *r = (FCGX_Request *)arg;
|
FCGX_Request *r = (FCGX_Request *)arg;
|
||||||
|
|
||||||
|
|
@ -289,191 +291,106 @@ fcgi_stream_timeout(int s,
|
||||||
/*! Timeout of notification stream, limit lifetime, for debug
|
/*! Timeout of notification stream, limit lifetime, for debug
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
fcgi_stream_timeout2(int s,
|
stream_timeout_end(int s,
|
||||||
void *arg)
|
void *arg)
|
||||||
{
|
{
|
||||||
clixon_debug(CLIXON_DBG_STREAM, "Terminate stream");
|
clixon_debug(CLIXON_DBG_STREAM, "Terminate stream");
|
||||||
clixon_exit_set(1); // XXX This is local eventloop see below, not global
|
clixon_exit_set(1); // XXX This is local eventloop see below, not global
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Process a stream request
|
/*! FCGI specific code for setting up stream sockets
|
||||||
*
|
*
|
||||||
* @param[in] h Clixon handle
|
* @param[in] h Clixon handle
|
||||||
* @param[in] req Generic Www handle (can be part of 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] timeout Stream timeout
|
||||||
|
* @param[in] besock Socket to backend
|
||||||
* @param[out] finish Set to zero, if request should not be finnished by upper layer
|
* @param[out] finish Set to zero, if request should not be finnished by upper layer
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
|
* Consider moving timeout and backend sock to generic code
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
api_stream(clixon_handle h,
|
stream_sockets_setup(clixon_handle h,
|
||||||
void *req,
|
void *req,
|
||||||
cvec *qvec,
|
int timeout,
|
||||||
int timeout,
|
int besock,
|
||||||
int *finish)
|
int *finish)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
FCGX_Request *rfcgi = (FCGX_Request *)req; /* XXX */
|
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
|
#ifdef STREAM_FORK
|
||||||
int pid;
|
int pid;
|
||||||
struct stream_child *sc;
|
struct stream_child *sc;
|
||||||
|
|
||||||
|
if ((pid = fork()) == 0){ /* child */
|
||||||
|
#if 0 // Leaks
|
||||||
|
if (pvec)
|
||||||
|
free(pvec);
|
||||||
|
if (qvec)
|
||||||
|
cvec_free(qvec);
|
||||||
|
if (pcvec)
|
||||||
|
cvec_free(pcvec);
|
||||||
#endif
|
#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 (pvec)
|
|
||||||
free(pvec);
|
|
||||||
if (qvec)
|
|
||||||
cvec_free(qvec);
|
|
||||||
if (pcvec)
|
|
||||||
cvec_free(pcvec);
|
|
||||||
if (cb)
|
|
||||||
cbuf_free(cb);
|
|
||||||
if (cbret)
|
|
||||||
cbuf_free(cbret);
|
|
||||||
#endif /* STREAM_FORK */
|
#endif /* STREAM_FORK */
|
||||||
/* Listen to backend socket */
|
backend_eof = 0;
|
||||||
if (clixon_event_reg_fd(s,
|
/* Listen to backend socket */
|
||||||
restconf_stream_cb,
|
if (clixon_event_reg_fd(besock,
|
||||||
req,
|
stream_fcgi_backend_cb,
|
||||||
"stream socket") < 0)
|
req,
|
||||||
|
"stream socket") < 0)
|
||||||
|
goto done;
|
||||||
|
if (clixon_event_reg_fd(rfcgi->listen_sock,
|
||||||
|
stream_fcgi_uplink_cb,
|
||||||
|
req,
|
||||||
|
"stream socket") < 0)
|
||||||
|
goto done;
|
||||||
|
/* 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, 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");
|
||||||
|
if (backend_eof == 0)
|
||||||
|
if (clicon_rpc_close_session(h) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (clixon_event_reg_fd(rfcgi->listen_sock,
|
clixon_event_unreg_fd(besock, stream_fcgi_backend_cb);
|
||||||
stream_checkuplink,
|
close(besock);
|
||||||
req,
|
clixon_event_unreg_fd(rfcgi->listen_sock, stream_fcgi_uplink_cb);
|
||||||
"stream socket") < 0)
|
clixon_event_unreg_timeout(fcgi_stream_timeout, (void*)req);
|
||||||
goto done;
|
clixon_event_unreg_timeout(stream_timeout_end, (void*)req);
|
||||||
/* Timeout of notification stream, close after limited lifetime, for debug */
|
clixon_exit_set(0); /* reset */
|
||||||
if (timeout){
|
|
||||||
struct timeval t;
|
|
||||||
gettimeofday(&t, NULL);
|
|
||||||
t.tv_sec += timeout;
|
|
||||||
clixon_event_reg_timeout(t, fcgi_stream_timeout2, 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);
|
|
||||||
clixon_event_unreg_timeout(fcgi_stream_timeout, (void*)req);
|
|
||||||
clixon_event_unreg_timeout(fcgi_stream_timeout2, (void*)req);
|
|
||||||
clixon_exit_set(0); /* reset */
|
|
||||||
#ifdef STREAM_FORK
|
#ifdef STREAM_FORK
|
||||||
#if 0 /* Seems to be a global resource, but there is till some timing error here */
|
#if 0 /* Seems to be a global resource, but there is till some timing error here */
|
||||||
FCGX_Finish_r(rfcgi);
|
FCGX_Finish_r(rfcgi);
|
||||||
FCGX_Free(rfcgi, 0);
|
FCGX_Free(rfcgi, 0);
|
||||||
#endif
|
#endif
|
||||||
restconf_terminate(h);
|
restconf_terminate(h);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
|
||||||
/* parent */
|
|
||||||
/* Create stream_child struct and store pid and FCGI data, when child
|
|
||||||
* killed, call FCGX_Free
|
|
||||||
*/
|
|
||||||
if ((sc = malloc(sizeof(struct stream_child))) == NULL){
|
|
||||||
clixon_err(OE_XML, errno, "malloc");
|
|
||||||
goto done;
|
|
||||||
}
|
|
||||||
memset(sc, 0, sizeof(struct stream_child));
|
|
||||||
sc->sc_pid = pid;
|
|
||||||
sc->sc_r = *rfcgi; /* XXX by value */
|
|
||||||
|
|
||||||
ADDQ(sc, STREAM_CHILD);
|
|
||||||
*finish = 0; /* If spawn child, we should not finish this stream */
|
|
||||||
#endif /* STREAM_FORK */
|
|
||||||
}
|
}
|
||||||
ok:
|
/* parent */
|
||||||
|
/* Create stream_child struct and store pid and FCGI data, when child
|
||||||
|
* killed, call FCGX_Free
|
||||||
|
*/
|
||||||
|
if ((sc = malloc(sizeof(struct stream_child))) == NULL){
|
||||||
|
clixon_err(OE_XML, errno, "malloc");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
memset(sc, 0, sizeof(struct stream_child));
|
||||||
|
sc->sc_pid = pid;
|
||||||
|
sc->sc_r = *rfcgi; /* XXX by value */
|
||||||
|
|
||||||
|
ADDQ(sc, STREAM_CHILD);
|
||||||
|
*finish = 0; /* If spawn child, we should not finish this stream */
|
||||||
|
#endif /* STREAM_FORK */
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
|
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;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,101 +86,6 @@
|
||||||
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
|
||||||
#include "restconf_stream.h"
|
#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
|
/*! Callback when stream notifications arrive from backend
|
||||||
*
|
*
|
||||||
* @param[in] s Socket
|
* @param[in] s Socket
|
||||||
|
|
@ -190,8 +95,8 @@ native_buf_write_xxx(clixon_handle h,
|
||||||
* @see netconf_notification_cb
|
* @see netconf_notification_cb
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
restconf_native_stream_cb(int s,
|
stream_native_backend_cb(int s,
|
||||||
void *arg)
|
void *arg)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
restconf_stream_data *sd = (restconf_stream_data *)arg;
|
restconf_stream_data *sd = (restconf_stream_data *)arg;
|
||||||
|
|
@ -258,7 +163,7 @@ restconf_native_stream_cb(int s,
|
||||||
cprintf(cb, "\r\n");
|
cprintf(cb, "\r\n");
|
||||||
cprintf(cb, "\r\n");
|
cprintf(cb, "\r\n");
|
||||||
#endif
|
#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;
|
goto done;
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -278,8 +183,8 @@ restconf_native_stream_cb(int s,
|
||||||
/*! Timeout of notification stream, limit lifetime, for debug
|
/*! Timeout of notification stream, limit lifetime, for debug
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
native_stream_timeout(int s,
|
stream_timeout_end(int s,
|
||||||
void *arg)
|
void *arg)
|
||||||
{
|
{
|
||||||
restconf_conn *rc = (restconf_conn *)arg;
|
restconf_conn *rc = (restconf_conn *)arg;
|
||||||
|
|
||||||
|
|
@ -299,170 +204,52 @@ stream_close(clixon_handle h,
|
||||||
restconf_conn *rc = (restconf_conn *)req;
|
restconf_conn *rc = (restconf_conn *)req;
|
||||||
|
|
||||||
clicon_rpc_close_session(h);
|
clicon_rpc_close_session(h);
|
||||||
clixon_event_unreg_fd(rc->rc_event_stream, restconf_native_stream_cb);
|
clixon_event_unreg_fd(rc->rc_event_stream, stream_native_backend_cb);
|
||||||
clixon_event_unreg_timeout(native_stream_timeout, req);
|
clixon_event_unreg_timeout(stream_timeout_end, req);
|
||||||
close(rc->rc_event_stream);
|
close(rc->rc_event_stream);
|
||||||
rc->rc_event_stream = 0;
|
rc->rc_event_stream = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Process a stream request, native variant
|
/*! Native specific code for setting up stream sockets
|
||||||
*
|
*
|
||||||
* @param[in] h Clixon handle
|
* @param[in] h Clixon handle
|
||||||
* @param[in] req Generic Www handle (can be part of 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] 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 0 OK
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @see api_stream fcgi implementation
|
* Consider moving timeout and backend sock to generic code
|
||||||
* @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
|
|
||||||
*/
|
*/
|
||||||
static int
|
int
|
||||||
api_native_stream(clixon_handle h,
|
stream_sockets_setup(clixon_handle h,
|
||||||
void *req,
|
void *req,
|
||||||
cvec *qvec,
|
int timeout,
|
||||||
int timeout,
|
int besock,
|
||||||
int *finish)
|
int *finish)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
restconf_stream_data *sd = (restconf_stream_data *)req;
|
restconf_stream_data *sd = (restconf_stream_data *)req;
|
||||||
restconf_conn *rc;
|
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, "");
|
/* Listen to backend socket */
|
||||||
if (req == NULL){
|
if (clixon_event_reg_fd(besock,
|
||||||
clixon_err(OE_RESTCONF, EINVAL, "req is NULL");
|
stream_native_backend_cb,
|
||||||
|
req,
|
||||||
|
"stream socket") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
}
|
|
||||||
rc = sd->sd_conn;
|
rc = sd->sd_conn;
|
||||||
streampath = clicon_option_str(h, "CLICON_STREAM_PATH");
|
rc->rc_event_stream = besock;
|
||||||
if ((path = restconf_uripath(h)) == NULL)
|
/* Timeout of notification stream, close after limited lifetime, for debug */
|
||||||
goto done;
|
if (timeout){
|
||||||
clixon_debug(CLIXON_DBG_STREAM, "path:%s", path);
|
struct timeval t;
|
||||||
request_method = restconf_param_get(h, "REQUEST_METHOD");
|
gettimeofday(&t, NULL);
|
||||||
clixon_debug(CLIXON_DBG_STREAM, "method:%s", request_method);
|
t.tv_sec += timeout;
|
||||||
pretty = restconf_pretty_get(h);
|
clixon_event_reg_timeout(t, stream_timeout_end, rc, "Stream timeout");
|
||||||
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,
|
|
||||||
"stream socket") < 0)
|
|
||||||
goto done;
|
|
||||||
rc->rc_event_stream = backend_socket;
|
|
||||||
/* 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ok:
|
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
clixon_debug(CLIXON_DBG_STREAM, "retval:%d", retval);
|
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;
|
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);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -212,10 +212,6 @@
|
||||||
*/
|
*/
|
||||||
#undef USE_SHA256
|
#undef USE_SHA256
|
||||||
|
|
||||||
/*! Restconf native stream support
|
|
||||||
*/
|
|
||||||
#define RESTCONF_NATIVE_STREAM
|
|
||||||
|
|
||||||
/*! Temporary comparison of xyanglibs, should be removed asap
|
/*! Temporary comparison of xyanglibs, should be removed asap
|
||||||
*/
|
*/
|
||||||
#define YANG_SCHEMA_CMP_KLUDGE
|
#define YANG_SCHEMA_CMP_KLUDGE
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,6 @@ function runtest()
|
||||||
|
|
||||||
new "2a) start $extra timeout:${TIMEOUT}s - expect ${LBOUND}-${UBOUND} notifications"
|
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)
|
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")
|
match=$(echo "$ret" | grep -Eo "$expect")
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
err "$expect" "$ret"
|
err "$expect" "$ret"
|
||||||
|
|
@ -149,7 +148,6 @@ function runtest()
|
||||||
|
|
||||||
new "2b) start $extra timeout:${TIMEOUT} stop after 5s - expect ${LB}-${UB} notifications"
|
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})
|
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")
|
match=$(echo "$ret" | grep -Eo "$expect")
|
||||||
if [ -z "$match" ]; then
|
if [ -z "$match" ]; then
|
||||||
err "$expect" "$ret"
|
err "$expect" "$ret"
|
||||||
|
|
@ -181,6 +179,9 @@ if [ $BE -ne 0 ]; then
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
err
|
err
|
||||||
fi
|
fi
|
||||||
|
sudo pkill -f clixon_backend # to be sure
|
||||||
|
fi
|
||||||
|
if [ $BE -ne 0 ]; then
|
||||||
new "start backend -s init -f $cfg -- -n ${PERIOD}"
|
new "start backend -s init -f $cfg -- -n ${PERIOD}"
|
||||||
# create example notification stream with periodic timeout ${PERIOD} seconds
|
# create example notification stream with periodic timeout ${PERIOD} seconds
|
||||||
start_backend -s init -f $cfg -- -n ${PERIOD}
|
start_backend -s init -f $cfg -- -n ${PERIOD}
|
||||||
|
|
@ -192,7 +193,8 @@ wait_backend
|
||||||
if [ $RC -ne 0 ]; then
|
if [ $RC -ne 0 ]; then
|
||||||
new "kill old restconf daemon"
|
new "kill old restconf daemon"
|
||||||
stop_restconf_pre
|
stop_restconf_pre
|
||||||
|
|
||||||
|
sleep 1
|
||||||
new "start restconf daemon -f $cfg -t ${TIMEOUT}"
|
new "start restconf daemon -f $cfg -t ${TIMEOUT}"
|
||||||
start_restconf -f $cfg -t ${TIMEOUT}
|
start_restconf -f $cfg -t ${TIMEOUT}
|
||||||
fi
|
fi
|
||||||
|
|
@ -233,23 +235,6 @@ if [ "${WITH_RESTCONF}" = "native" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if false; then # NYI
|
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
|
# 2c
|
||||||
new "2c) start sub 8s - replay from start -8s - expect 3-4 notifications"
|
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)
|
ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 10 -s -8)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue