- Restconf nghttp2 compiles

This commit is contained in:
Olof hagsand 2021-06-03 22:47:54 +02:00
parent 37da0aa45e
commit b680e3c5ac
13 changed files with 1375 additions and 378 deletions

View file

@ -35,7 +35,7 @@ Expected: June 2021
### New features
* Started EXPERIMENTAL HTTP/2 work using nghttp2
* Added autoconf config options, temporary for nghttp2 development: `--disable-evhtp`and `--enable-nghttp2` enabling http/1 only / http/2 only linking
* Added autoconf config options, temporary for nghttp2 development: `--disable-evhtp`and `--enable-nghttp2` enabling http/1 only / http/2 only linki and compile.
* YANG when statement in conjunction with grouping/uses/augment
* Several cases were not implemented fully according to RFC 7950:
* Do not extend default values if when statements evaluate to false
@ -72,6 +72,12 @@ Users may have to change how they access the system
* Previous meaning (wrong): Return all `a` elements.
* New meaning (correct): Return the `a` instance with empty key string: "".
### C/CLI-API changes on existing features
Developers may need to change their code
*
### Minor features
* Added new startup-mode: `running-startup`: First try running db, if it is empty try startup db.

View file

@ -99,6 +99,10 @@ APPSRC += restconf_methods_post.c
APPSRC += restconf_methods_get.c
APPSRC += restconf_root.c
APPSRC += restconf_main_$(with_restconf).c
ifeq ($(with_restconf),native)
APPSRC += restconf_evhtp.c # HTTP/1
APPSRC += restconf_nghttp2.c # HTTP/2
endif
# Fcgi-specific source including main
ifeq ($(with_restconf),fcgi)

View file

@ -53,10 +53,14 @@
/* evhtp */
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
#include <evhtp/evhtp.h>
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2
#include <nghttp2/nghttp2.h>
#endif
/* cligen */
#include <cligen/cligen.h>

View file

@ -0,0 +1,405 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 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 *****
* Evhtp specific HTTP/1 code complementing restconf_main_native.c
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
/* The clixon evhtp code can be compiled with or without threading support
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <pwd.h>
#include <ctype.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/resource.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#ifdef HAVE_LIBEVHTP
/* evhtp */
#include <event2/buffer.h> /* evbuffer */
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
#include <evhtp/evhtp.h>
#include <evhtp/sslutils.h> /* XXX inline this / use SSL directly */
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2 /* To get restconf_native.h include files right */
#include <nghttp2/nghttp2.h>
#endif
/* restconf */
#include "restconf_lib.h" /* generic shared with plugins */
#include "restconf_handle.h"
#include "restconf_api.h" /* generic not shared with plugins */
#include "restconf_err.h"
#include "restconf_root.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#include "restconf_evhtp.h" /* evhtp http/1 */
#ifdef HAVE_LIBEVHTP
static char*
evhtp_method2str(enum htp_method m)
{
switch (m){
case htp_method_GET:
return "GET";
break;
case htp_method_HEAD:
return "HEAD";
break;
case htp_method_POST:
return "POST";
break;
case htp_method_PUT:
return "PUT";
break;
case htp_method_DELETE:
return "DELETE";
break;
case htp_method_OPTIONS:
return "OPTIONS";
break;
case htp_method_PATCH:
return "PATCH";
break;
#ifdef NOTUSED
case htp_method_MKCOL:
return "MKCOL";
break;
case htp_method_COPY:
return "COPY";
break;
case htp_method_MOVE:
return "MOVE";
break;
case htp_method_OPTIONS:
return "OPTIONS";
break;
case htp_method_PROPFIND:
return "PROPFIND";
break;
case htp_method_PROPPATCH:
return "PROPPATCH";
break;
case htp_method_LOCK:
return "LOCK";
break;
case htp_method_UNLOCK:
return "UNLOCK";
break;
case htp_method_TRACE:
return "TRACE";
break;
case htp_method_CONNECT:
return "CONNECT";
break;
#endif /* NOTUSED */
default:
return "UNKNOWN";
break;
}
}
static int
evhtp_print_header(evhtp_header_t *header,
void *arg)
{
clicon_debug(1, "%s %s %s", __FUNCTION__, header->key, header->val);
return 0;
}
static int
evhtp_query_iterator(evhtp_header_t *hdr,
void *arg)
{
cvec *qvec = (cvec *)arg;
char *key;
char *val;
char *valu = NULL; /* unescaped value */
cg_var *cv;
key = hdr->key;
val = hdr->val;
if (uri_percent_decode(val, &valu) < 0)
return -1;
if ((cv = cvec_add(qvec, CGV_STRING)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_add");
return -1;
}
cv_name_set(cv, key);
cv_string_set(cv, valu);
if (valu)
free(valu);
return 0;
}
/*! Translate http header by capitalizing, prepend w HTTP_ and - -> _
* Example: Host -> HTTP_HOST
*/
static int
evhtp_convert_fcgi(evhtp_header_t *hdr,
void *arg)
{
clicon_handle h = (clicon_handle)arg;
return restconf_convert_hdr(h, hdr->key, hdr->val);
}
/*! Map from evhtp information to "fcgi" type parameters used in clixon code
*
* While all these params come via one call in fcgi, the information must be taken from
* several different places in evhtp
* @param[in] h Clicon handle
* @param[in] req Evhtp request struct
* @param[out] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @retval 1 OK continue
* @retval 0 Fail, dont continue
* @retval -1 Error
* The following parameters are set:
* QUERY_STRING
* REQUEST_METHOD
* REQUEST_URI
* HTTPS
* HTTP_HOST
* HTTP_ACCEPT
* HTTP_CONTENT_TYPE
* @note there may be more used by an application plugin
*/
static int
evhtp_params_set(clicon_handle h,
evhtp_request_t *req,
cvec *qvec)
{
int retval = -1;
htp_method meth;
evhtp_uri_t *uri;
evhtp_path_t *path;
evhtp_ssl_t *ssl = NULL;
char *subject = NULL;
cvec *cvv = NULL;
char *cn;
cxobj *xerr = NULL;
int pretty;
if ((uri = req->uri) == NULL){
clicon_err(OE_DAEMON, EFAULT, "No uri");
goto done;
}
if ((path = uri->path) == NULL){
clicon_err(OE_DAEMON, EFAULT, "No path");
goto done;
}
meth = evhtp_request_get_method(req);
/* QUERY_STRING in fcgi but go direct to the info instead of putting it in a string?
* This is different from all else: Ie one could have re-created a string here but
* that would mean double parsing,...
*/
if (qvec && uri->query)
if (evhtp_kvs_for_each(uri->query, evhtp_query_iterator, qvec) < 0){
clicon_err(OE_CFG, errno, "evhtp_kvs_for_each");
goto done;
}
if (restconf_param_set(h, "REQUEST_METHOD", evhtp_method2str(meth)) < 0)
goto done;
if (restconf_param_set(h, "REQUEST_URI", path->full) < 0)
goto done;
clicon_debug(1, "%s proto:%d", __FUNCTION__, req->proto);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* XXX: Any two http numbers seem accepted by evhtp, like 1.99, 99.3 as http/1.1*/
if (req->proto != EVHTP_PROTO_10 &&
req->proto != EVHTP_PROTO_11){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid HTTP version number") < 0)
goto done;
/* Select json as default since content-type header may not be accessible yet */
if (api_return_err0(h, req, xerr, pretty, YANG_DATA_JSON, 0) < 0)
goto done;
goto fail;
}
clicon_debug(1, "%s conn->ssl:%d", __FUNCTION__, req->conn->ssl?1:0);
if ((ssl = req->conn->ssl) != NULL){
if (restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */
goto done;
/* SSL subject fields, eg CN (Common Name) , can add more here? */
if ((subject = (char*)htp_sslutil_subject_tostr(req->conn->ssl)) != NULL){
if (uri_str2cvec(subject, '/', '=', 1, &cvv) < 0)
goto done;
if ((cn = cvec_find_str(cvv, "CN")) != NULL){
if (restconf_param_set(h, "SSL_CN", cn) < 0)
goto done;
}
}
}
/* Translate all http headers by capitalizing, prepend w HTTP_ and - -> _
* Example: Host -> HTTP_HOST
*/
if (evhtp_headers_for_each(req->headers_in, evhtp_convert_fcgi, h) < 0)
goto done;
retval = 1;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (subject)
free(subject);
if (xerr)
xml_free(xerr);
if (cvv)
cvec_free(cvv);
return retval;
fail:
retval = 0;
goto done;
}
/*! Callback for each incoming http request for path /
*
* This are all messages except /.well-known, Registered with evhtp_set_cb
*
* @param[in] req evhtp http request structure defining the incoming message
* @param[in] arg cx_evhtp handle clixon specific fields
* @retval void
* Discussion: problematic if fatal error -1 is returneod from clixon routines
* without actually terminating. Consider:
* 1) sending some error? and/or
* 2) terminating the process?
*/
void
restconf_path_root(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
int ret;
cvec *qvec = NULL;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
/* get accepted connection */
/* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, qvec)) < 0)
goto done;
if (ret == 1){
/* call generic function */
if (api_root_restconf(h, req, qvec) < 0)
goto done;
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
goto done;
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
if (qvec)
cvec_free(qvec);
return; /* void */
}
/*! /.well-known callback
*
* @param[in] req evhtp http request structure defining the incoming message
* @param[in] arg cx_evhtp handle clixon specific fields
* @retval void
*/
void
restconf_path_wellknown(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
int ret;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
/* get accepted connection */
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, NULL)) < 0)
goto done;
if (ret == 1){
/* call generic function */
if (api_well_known(h, req) < 0)
goto done;
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
goto done;
retval = 0;
done:
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
return; /* void */
}
#endif /* HAVE_LIBEVHTP */

View file

@ -0,0 +1,47 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 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 *****
*
* Virtual clixon restconf API functions.
*/
#ifndef _RESTCONF_EVHTP_H_
#define _RESTCONF_EVHTP_H_
/*
* Prototypes
*/
void restconf_path_root(evhtp_request_t *req, void *arg);
void restconf_path_wellknown(evhtp_request_t *req, void *arg);
#endif /* _RESTCONF_EVHTP_H_ */

View file

@ -275,6 +275,43 @@ restconf_content_type(clicon_handle h)
return m;
}
/*! Translate http header by capitalizing, prepend w HTTP_ and - -> _
* Example: Host -> HTTP_HOST
*/
int
restconf_convert_hdr(clicon_handle h,
char *name,
char *val)
{
int retval = -1;
cbuf *cb = NULL;
int i;
char c;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* convert key name */
cprintf(cb, "HTTP_");
for (i=0; i<strlen(name); i++){
c = name[i] & 0xff;
if (islower(c))
cprintf(cb, "%c", toupper(c));
else if (c == '-')
cprintf(cb, "_");
else
cprintf(cb, "%c", c);
}
if (restconf_param_set(h, cbuf_get(cb), val) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Parse a cookie string and return value of cookie attribute
* @param[in] cookiestr cookie string according to rfc6265 (modified)
* @param[in] attribute cookie attribute

View file

@ -86,6 +86,7 @@ const char *restconf_media_int2str(restconf_media media);
int restconf_str2proto(char *str);
const char *restconf_proto2str(int proto);
restconf_media restconf_content_type(clicon_handle h);
int restconf_convert_hdr(clicon_handle h, char *name, char *val);
int get_user_cookie(char *cookiestr, char *attribute, char **val);
int restconf_terminate(clicon_handle h);
int restconf_insert_attributes(cxobj *xdata, cvec *qvec);
@ -96,6 +97,7 @@ int restconf_authentication_cb(clicon_handle h, void *req, int pretty, restcon
int restconf_config_init(clicon_handle h, cxobj *xrestconf);
int restconf_socket_init(const char *netns0, const char *addrstr, const char *addrtype, uint16_t port, int backlog, int flags, int *ss);
int restconf_socket_extract(clicon_handle h, cxobj *xs, cvec *nsc, char **namespace, char **address, char **addrtype, uint16_t *port, uint16_t *ssl);
int restconf_convert_hdr(clicon_handle h, char *name, char *val);
#endif /* _RESTCONF_LIB_H_ */

View file

@ -173,6 +173,12 @@
#include "restconf_err.h"
#include "restconf_root.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#ifdef HAVE_LIBEVHTP
#include "restconf_evhtp.h" /* http/1 */
#endif
#ifdef HAVE_LIBNGHTTP2
#include "restconf_nghttp2.h" /* http/2 */
#endif
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:E:l:p:y:a:u:rW:R:o:"
@ -332,363 +338,19 @@ print_cb(const char *str, size_t len, void *cb)
/* Clixon error category log callback
* @param[in] handle Application-specific handle
* @param[in] suberr Application-specific handle
* @param[out] cb Read log/error string into this buffer
*/
static int
openssl_cat_log_cb(void *handle,
cbuf *cb)
clixon_openssl_log_cb(void *handle,
int suberr,
cbuf *cb)
{
clicon_debug(1, "%s", __FUNCTION__);
ERR_print_errors_cb(print_cb, cb);
return 0;
}
#ifdef HAVE_LIBEVHTP
static char*
evhtp_method2str(enum htp_method m)
{
switch (m){
case htp_method_GET:
return "GET";
break;
case htp_method_HEAD:
return "HEAD";
break;
case htp_method_POST:
return "POST";
break;
case htp_method_PUT:
return "PUT";
break;
case htp_method_DELETE:
return "DELETE";
break;
case htp_method_OPTIONS:
return "OPTIONS";
break;
case htp_method_PATCH:
return "PATCH";
break;
#ifdef NOTUSED
case htp_method_MKCOL:
return "MKCOL";
break;
case htp_method_COPY:
return "COPY";
break;
case htp_method_MOVE:
return "MOVE";
break;
case htp_method_OPTIONS:
return "OPTIONS";
break;
case htp_method_PROPFIND:
return "PROPFIND";
break;
case htp_method_PROPPATCH:
return "PROPPATCH";
break;
case htp_method_LOCK:
return "LOCK";
break;
case htp_method_UNLOCK:
return "UNLOCK";
break;
case htp_method_TRACE:
return "TRACE";
break;
case htp_method_CONNECT:
return "CONNECT";
break;
#endif /* NOTUSED */
default:
return "UNKNOWN";
break;
}
}
static int
evhtp_print_header(evhtp_header_t *header,
void *arg)
{
clicon_debug(1, "%s %s %s", __FUNCTION__, header->key, header->val);
return 0;
}
static int
evhtp_query_iterator(evhtp_header_t *hdr,
void *arg)
{
cvec *qvec = (cvec *)arg;
char *key;
char *val;
char *valu = NULL; /* unescaped value */
cg_var *cv;
key = hdr->key;
val = hdr->val;
if (uri_percent_decode(val, &valu) < 0)
return -1;
if ((cv = cvec_add(qvec, CGV_STRING)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_add");
return -1;
}
cv_name_set(cv, key);
cv_string_set(cv, valu);
if (valu)
free(valu);
return 0;
}
/*! Translate http header by capitalizing, prepend w HTTP_ and - -> _
* Example: Host -> HTTP_HOST
*/
static int
evhtp_convert_fcgi(evhtp_header_t *hdr,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
cbuf *cb = NULL;
int i;
char c;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* convert key name */
cprintf(cb, "HTTP_");
for (i=0; i<strlen(hdr->key); i++){
c = hdr->key[i] & 0xff;
if (islower(c))
cprintf(cb, "%c", toupper(c));
else if (c == '-')
cprintf(cb, "_");
else
cprintf(cb, "%c", c);
}
if (restconf_param_set(h, cbuf_get(cb), hdr->val) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Map from evhtp information to "fcgi" type parameters used in clixon code
*
* While all these params come via one call in fcgi, the information must be taken from
* several different places in evhtp
* @param[in] h Clicon handle
* @param[in] req Evhtp request struct
* @param[out] qvec Query parameters, ie the ?<id>=<val>&<id>=<val> stuff
* @retval 1 OK continue
* @retval 0 Fail, dont continue
* @retval -1 Error
* The following parameters are set:
* QUERY_STRING
* REQUEST_METHOD
* REQUEST_URI
* HTTPS
* HTTP_HOST
* HTTP_ACCEPT
* HTTP_CONTENT_TYPE
* @note there may be more used by an application plugin
*/
static int
evhtp_params_set(clicon_handle h,
evhtp_request_t *req,
cvec *qvec)
{
int retval = -1;
htp_method meth;
evhtp_uri_t *uri;
evhtp_path_t *path;
evhtp_ssl_t *ssl = NULL;
char *subject = NULL;
cvec *cvv = NULL;
char *cn;
cxobj *xerr = NULL;
int pretty;
if ((uri = req->uri) == NULL){
clicon_err(OE_DAEMON, EFAULT, "No uri");
goto done;
}
if ((path = uri->path) == NULL){
clicon_err(OE_DAEMON, EFAULT, "No path");
goto done;
}
meth = evhtp_request_get_method(req);
/* QUERY_STRING in fcgi but go direct to the info instead of putting it in a string?
* This is different from all else: Ie one could have re-created a string here but
* that would mean double parsing,...
*/
if (qvec && uri->query)
if (evhtp_kvs_for_each(uri->query, evhtp_query_iterator, qvec) < 0){
clicon_err(OE_CFG, errno, "evhtp_kvs_for_each");
goto done;
}
if (restconf_param_set(h, "REQUEST_METHOD", evhtp_method2str(meth)) < 0)
goto done;
if (restconf_param_set(h, "REQUEST_URI", path->full) < 0)
goto done;
clicon_debug(1, "%s proto:%d", __FUNCTION__, req->proto);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
/* XXX: Any two http numbers seem accepted by evhtp, like 1.99, 99.3 as http/1.1*/
if (req->proto != EVHTP_PROTO_10 &&
req->proto != EVHTP_PROTO_11){
if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid HTTP version number") < 0)
goto done;
/* Select json as default since content-type header may not be accessible yet */
if (api_return_err0(h, req, xerr, pretty, YANG_DATA_JSON, 0) < 0)
goto done;
goto fail;
}
clicon_debug(1, "%s conn->ssl:%d", __FUNCTION__, req->conn->ssl?1:0);
if ((ssl = req->conn->ssl) != NULL){
if (restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */
goto done;
/* SSL subject fields, eg CN (Common Name) , can add more here? */
if ((subject = (char*)htp_sslutil_subject_tostr(req->conn->ssl)) != NULL){
if (uri_str2cvec(subject, '/', '=', 1, &cvv) < 0)
goto done;
if ((cn = cvec_find_str(cvv, "CN")) != NULL){
if (restconf_param_set(h, "SSL_CN", cn) < 0)
goto done;
}
}
}
/* Translate all http headers by capitalizing, prepend w HTTP_ and - -> _
* Example: Host -> HTTP_HOST
*/
if (evhtp_headers_for_each(req->headers_in, evhtp_convert_fcgi, h) < 0)
goto done;
retval = 1;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (subject)
free(subject);
if (xerr)
xml_free(xerr);
if (cvv)
cvec_free(cvv);
return retval;
fail:
retval = 0;
goto done;
}
/*! Callback for each incoming http request for path /
*
* This are all messages except /.well-known, Registered with evhtp_set_cb
*
* @param[in] req evhtp http request structure defining the incoming message
* @param[in] arg cx_evhtp handle clixon specific fields
* @retval void
* Discussion: problematic if fatal error -1 is returneod from clixon routines
* without actually terminating. Consider:
* 1) sending some error? and/or
* 2) terminating the process?
*/
static void
restconf_path_root(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
int ret;
cvec *qvec = NULL;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
/* get accepted connection */
/* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, qvec)) < 0)
goto done;
if (ret == 1){
/* call generic function */
if (api_root_restconf(h, req, qvec) < 0)
goto done;
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
goto done;
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
if (qvec)
cvec_free(qvec);
return; /* void */
}
/*! /.well-known callback
*
* @param[in] req evhtp http request structure defining the incoming message
* @param[in] arg cx_evhtp handle clixon specific fields
* @retval void
*/
static void
restconf_path_wellknown(evhtp_request_t *req,
void *arg)
{
int retval = -1;
clicon_handle h;
int ret;
clicon_debug(1, "------------");
if ((h = (clicon_handle)arg) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, evhtp_print_header, h);
/* get accepted connection */
/* set fcgi-like paramaters (ignore query vector) */
if ((ret = evhtp_params_set(h, req, NULL)) < 0)
goto done;
if (ret == 1){
/* call generic function */
if (api_well_known(h, req) < 0)
goto done;
}
/* Clear (fcgi) paramaters from this request */
if (restconf_param_del_all(h) < 0)
goto done;
retval = 0;
done:
/* Catch all on fatal error. This does not terminate the process but closes request stream */
if (retval < 0){
evhtp_send_reply(req, EVHTP_RES_ERROR);
}
return; /* void */
}
#endif /* HAVE_LIBEVHTP */
/*
* see restconf_config ->cv_evhtp_init(x2) -> cx_evhtp_socket ->
* evhtp_ssl_init:4757
@ -951,7 +613,7 @@ close_ssl_socket(restconf_conn_h *rc,
#ifdef HAVE_LIBEVHTP
evhtp_connection_t *evconn;
evconn = (evhtp_connection_t *)rc->rc_arg;
evconn = rc->rc_evconn;
clicon_debug(1, "%s evconn-free (%p) 1", __FUNCTION__, evconn);
evhtp_connection_free(evconn); /* evhtp */
#endif /* HAVE_LIBEVHTP */
@ -1037,7 +699,7 @@ restconf_connection(int s,
int retval = -1;
restconf_conn_h *rc = NULL;
ssize_t n;
char buf[BUFSIZ]; /* from stdio.h, typically 8K */
char buf[BUFSIZ]; /* from stdio.h, typically 8K XXX: reduce for test */
int readmore = 1;
#ifdef HAVE_LIBEVHTP
clicon_handle h;
@ -1083,12 +745,15 @@ restconf_connection(int s,
goto done;
goto ok;
}
switch (rc->rc_proto){
#ifdef HAVE_LIBEVHTP
case HTTP_10:
case HTTP_11:
h = rc->rc_h;
/* parse incoming packet using evhtp
* signature:
*/
evconn = (evhtp_connection_t*)rc->rc_arg;
evconn = rc->rc_evconn;
if (connection_parse_nobev(buf, n, evconn) < 0){
clicon_debug(1, "%s connection_parse error", __FUNCTION__);
/* XXX To get more nuanced evhtp error check
@ -1155,6 +820,15 @@ restconf_connection(int s,
goto done;
}
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2
case HTTP_2:
if (http2_recv(rc, (unsigned char *)buf, n) < 0)
goto done;
break;
#endif /* HAVE_LIBNGHTTP2 */
default:
break;
} /* switch rc_proto */
} /* while readmore */
ok:
retval = 0;
@ -1282,7 +956,7 @@ ssl_alpn_check(clicon_handle h,
{
evhtp_connection_t *evconn;
if ((evconn = (evhtp_connection_t *)rc->rc_arg) != NULL)
if ((evconn = rc->rc_evconn) != NULL)
evhtp_connection_free(evconn); /* evhtp */
}
#endif /* HAVE_LIBEVHTP */
@ -1537,21 +1211,29 @@ restconf_accept_client(int fd,
#ifdef HAVE_LIBEVHTP
case HTTP_10:
case HTTP_11:{
evhtp_t *evhtp = (evhtp_t *)rh->rh_arg;
evhtp_connection_t *evconn;
/* Create evhtp-specific struct */
if ((evconn = evhtp_connection_new_server(rh->rh_evhtp, rc->rc_s)) == NULL){
if ((evconn = evhtp_connection_new_server(evhtp, rc->rc_s)) == NULL){
clicon_err(OE_UNIX, errno, "evhtp_connection_new_server");
goto done;
}
/* Mutual pointers, from generic rc to evhtp specific and from evhtp conn to generic
*/
rc->rc_arg = evconn; /* Generic to specific */
rc->rc_evconn = evconn; /* Generic to specific */
evconn->arg = rc; /* Specific to generic */
evconn->ssl = rc->rc_ssl; /* evhtp */
}
break;
#endif /* HAVE_LIBEVHTP */
case HTTP_2:
#ifdef HAVE_LIBNGHTTP2
case HTTP_2:{
if (http2_session_init(rc) < 0)
goto done;
break;
}
#endif /* HAVE_LIBNGHTTP2 */
default:
break;
} /* switch proto */
@ -1587,10 +1269,14 @@ restconf_native_terminate(clicon_handle h)
if (rh->rh_ctx)
SSL_CTX_free(rh->rh_ctx);
#ifdef HAVE_LIBEVHTP
if (rh->rh_evhtp){
if (rh->rh_evhtp->evbase)
event_base_free(rh->rh_evhtp->evbase);
evhtp_free(rh->rh_evhtp);
{
evhtp_t *evhtp = (evhtp_t *)rh->rh_arg;
if (evhtp){
if (evhtp->evbase)
event_base_free(evhtp->evbase);
evhtp_free(evhtp);
rh->rh_arg = NULL;
}
}
#endif /* HAVE_LIBEVHTP */
@ -1836,7 +1522,7 @@ restconf_openssl_init(clicon_handle h,
clicon_err(OE_UNIX, errno, "evhtp_new");
goto done;
}
rh->rh_evhtp = evhtp;
rh->rh_arg = evhtp;
if (evhtp_set_cb(evhtp, "/" RESTCONF_API, restconf_path_root, h) == NULL){
clicon_err(OE_EVENTS, errno, "evhtp_set_cb");
goto done;
@ -2140,9 +1826,19 @@ main(int argc,
*/
if (clixon_err_cat_reg(OE_SSL, /* category */
h, /* handle (can be NULL) */
openssl_cat_log_cb /* log fn */
clixon_openssl_log_cb /* log fn */
) < 0)
goto done;
#ifdef HAVE_LIBNGHTTP2
/*
* Register error category and error/log callbacks for openssl special error handling
*/
if (clixon_err_cat_reg(OE_NGHTTP2, /* category */
h, /* handle (can be NULL) */
clixon_nghttp2_log_cb /* log fn */
) < 0)
goto done;
#endif
clicon_debug_init(dbg, NULL);
clicon_log(LOG_NOTICE, "%s native %u Started", __PROGRAM__, getpid());
if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){

View file

@ -59,19 +59,36 @@ extern "C" {
/*
* Types
*/
/* http/2 session stream struct
*/
typedef struct {
qelem_t sd_qelem; /* List header */
int32_t sd_stream_id;
int sd_fd;
} restconf_stream_data;
/* Restconf connection handle
* Per connection request
*/
typedef struct {
// qelem_t rs_qelem; /* List header */
cvec *rc_outp_hdrs; /* List of output headers */
cbuf *rc_outp_buf; /* Output buffer */
size_t rc_bufferevent_output_offset; /* Kludge to drain libevent output buffer */
cvec *rc_outp_hdrs; /* List of output headers */
cbuf *rc_outp_buf; /* Output buffer */
size_t rc_bufferevent_output_offset; /* Kludge to drain libevent output buffer */
restconf_http_proto rc_proto; /* HTTP protocol: http/1 or http/2 */
int rc_s; /* Connection socket */
clicon_handle rc_h; /* Clixon handle */
SSL *rc_ssl; /* Structure for SSL connection */
void *rc_arg; /* Specific connection pointer, eg evhtp conn struct */
int rc_s; /* Connection socket */
clicon_handle rc_h; /* Clixon handle */
SSL *rc_ssl; /* Structure for SSL connection */
restconf_stream_data *rc_streams; /* List of http/2 session streams */
/* Decision to keep lib-specific data here, otherwise new struct necessary
* drawback is specific includes need to go everywhere */
#ifdef HAVE_LIBEVHTP
evhtp_connection_t *rc_evconn;
#endif
#ifdef HAVE_LIBNGHTTP2
nghttp2_session *rc_ngsession;
#endif
} restconf_conn_h;
/* Restconf request handle
@ -94,9 +111,7 @@ typedef struct {
typedef struct {
SSL_CTX *rh_ctx; /* SSL context */
restconf_socket *rh_sockets; /* List of restconf server (ready for accept) sockets */
#ifdef HAVE_LIBEVHTP
evhtp_t *rh_evhtp; /* Evhtp struct */
#endif
void *rh_arg; /* Packet specific handle (eg evhtp) */
} restconf_native_handle;
/*

View file

@ -0,0 +1,727 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 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 *****
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
/* The clixon evhtp code can be compiled with or without threading support
* The choice is set at libevhtp compile time by cmake. Eg:
* cmake -DEVHTP_DISABLE_EVTHR=ON # Disable threads.
* Default in testing is disabled threads.
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <pwd.h>
#include <ctype.h>
#include <assert.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/resource.h>
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/x509v3.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#ifdef HAVE_LIBEVHTP /* To get restconf_native.h include files right */
/* evhtp */
#include <event2/buffer.h> /* evbuffer */
#define EVHTP_DISABLE_REGEX
#define EVHTP_DISABLE_EVTHR
#include <evhtp/evhtp.h>
#endif /* HAVE_LIBEVHTP */
#ifdef HAVE_LIBNGHTTP2
#include <nghttp2/nghttp2.h>
#endif
/* restconf */
#include "restconf_lib.h" /* generic shared with plugins */
#include "restconf_handle.h"
#include "restconf_api.h" /* generic not shared with plugins */
#include "restconf_err.h"
#include "restconf_root.h"
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/
#ifdef HAVE_LIBNGHTTP2 /* XXX MOVE */
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
static restconf_stream_data *
restconf_stream_data_new(restconf_conn_h *rc,
int32_t stream_id)
{
restconf_stream_data *sd;
sd = malloc(sizeof(restconf_stream_data));
memset(sd, 0, sizeof(restconf_stream_data));
sd->sd_stream_id = stream_id;
sd->sd_fd = -1;
INSQ(sd, rc->rc_streams);
return sd;
}
#ifdef NOTUSED
static void
delete_http2_stream_data(restconf_stream_data *sd)
{
if (sd->fd != -1) {
close(sd->fd);
}
free(sd->request_path);
free(sd);
}
#endif
#ifdef NOTUSED
static int
send_client_connection_header(nghttp2_session *session)
{
int retval = -1;
nghttp2_settings_entry iv[1] = {
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}};
int rv;
clicon_debug(1, "%s", __FUNCTION__);
/* client 24 bytes magic string will be sent by nghttp2 library */
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv));
if (rv != 0) {
clicon_err(OE_XML, 0, "Could not submit SETTINGS: %s", nghttp2_strerror(rv));
goto done;
}
retval = 0;
done:
return retval;
}
#endif /* NOTUSED */
/* Clixon error category specialized log callback for nghttp2
* @param[in] handle Application-specific handle
* @param[in] suberr Application-specific handle
* @param[out] cb Read log/error string into this buffer
*/
int
clixon_nghttp2_log_cb(void *handle,
int suberr,
cbuf *cb)
{
clicon_debug(1, "%s", __FUNCTION__);
cprintf(cb, "Fatal error: %s", nghttp2_strerror(suberr));
return 0;
}
#ifdef NOTUSED
static void
nghttp2_print_header(const uint8_t *name,
size_t namelen,
const uint8_t *value,
size_t valuelen)
{
clicon_debug(1, "%s %s", name, value);
}
/* Print HTTP headers to |f|. Please note that this function does not
take into account that header name and value are sequence of
octets, therefore they may contain non-printable characters. */
static void
nghttp2_print_headers(nghttp2_nv *nva,
size_t nvlen)
{
size_t i;
for (i = 0; i < nvlen; ++i)
nghttp2_print_header(nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen);
}
#endif /* NOTUSED */
/*! Transmit the |data|, |length| bytes, to the network.
* This callback is required if the application uses
* `nghttp2_session_send()` to send data to the remote endpoint. If
* the application uses solely `nghttp2_session_mem_send()` instead,
* this callback function is unnecessary.
*/
static ssize_t
send_callback(nghttp2_session *session,
const uint8_t *data,
size_t length,
int flags,
void *user_data)
{
restconf_conn_h *rc = (restconf_conn_h *)user_data;
int ret;
clicon_debug(1, "%s %zu:", __FUNCTION__, length);
#if 0
{
int i;
for (i=0; i<length; i++)
fprintf(stderr, "%02x", data[i]&255);
fprintf(stderr, "\n");
}
#endif
/* encrypt & send message */
if ((ret = SSL_write(rc->rc_ssl, data, length)) < 0)
return ret;
return ret;
}
/*! Invoked when |session| wants to receive data from the remote peer.
*/
static ssize_t
recv_callback(nghttp2_session *session,
uint8_t *buf,
size_t length,
int flags,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*! Callback for each incoming http request for path /
*
* This are all messages except /.well-known, Registered with evhtp_set_cb
*
* @param[in] req evhtp http request structure defining the incoming message
* @param[in] arg cx_evhtp handle clixon specific fields
* @retval void
* Discussion: problematic if fatal error -1 is returneod from clixon routines
* without actually terminating. Consider:
* 1) sending some error? and/or
* 2) terminating the process?
*/
static int
restconf_nghttp2_root(restconf_conn_h *rc)
{
int retval = -1;
clicon_handle h;
// int ret;
cvec *qvec = NULL;
clicon_debug(1, "------------");
if ((h = rc->rc_h) == NULL){
clicon_err(OE_RESTCONF, EINVAL, "arg is NULL");
goto done;
}
/* get accepted connection */
/* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){
clicon_err(OE_UNIX, errno, "cvec_new");
goto done;
}
/* call generic function */
if (api_root_restconf(h, rc, qvec) < 0)
goto done;
// /* Clear (fcgi) paramaters from this request */
// if (restconf_param_del_all(h) < 0)
// goto done;
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
/* Catch all on fatal error. This does not terminate the process but closes request stream */
// if (retval < 0){
// evhtp_send_reply(req, EVHTP_RES_ERROR);
// }
if (qvec)
cvec_free(qvec);
return retval; /* void */
}
/*!
*/
static int
on_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
int retval = -1;
restconf_conn_h *rc = (restconf_conn_h *)user_data;
restconf_stream_data *sd;
char *path;
clicon_debug(1, "%s %d", __FUNCTION__, frame->hd.stream_id);
switch (frame->hd.type) {
case NGHTTP2_DATA:
case NGHTTP2_HEADERS:
/* Check that the client request has finished */
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
/* For DATA and HEADERS frame, this callback may be called after
on_stream_close_callback. Check that stream still alive. */
if ((sd = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) == NULL)
return 0;
if ((path = restconf_uripath(rc->rc_h)) == NULL)
goto ok;
if (strcmp(path, "/" RESTCONF_API) == 0){
if (restconf_nghttp2_root(rc) < 0)
goto done;
}
else if (strcmp(path, RESTCONF_WELL_KNOWN) == 0){
}
else
; /* ignore */
}
break;
default:
break;
}
ok:
retval = 0;
done:
return retval;
}
/*!
*/
static int
on_invalid_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame,
int lib_error_code,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
on_data_chunk_recv_callback(nghttp2_session *session,
uint8_t flags,
int32_t stream_id,
const uint8_t *data,
size_t len,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s %d", __FUNCTION__, stream_id);
// if (sd->sd_session == session &&
// sd->sd_stream_id == stream_id)
// fwrite(data, 1, len, stdout); /* This is where data is printed */
return 0;
}
/*!
*/
static int
before_frame_send_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
on_frame_send_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
on_frame_not_send_callback(nghttp2_session *session,
const nghttp2_frame *frame,
int lib_error_code,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
on_stream_close_callback(nghttp2_session *session,
int32_t stream_id,
nghttp2_error_code error_code,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
//session_data *sd = (session_data*)user_data;
return 0;
}
/*! Reception of header block in HEADERS or PUSH_PROMISE is started.
*/
static int
on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data)
{
restconf_conn_h *rc = (restconf_conn_h *)user_data;
restconf_stream_data *sd;
clicon_debug(1, "%s", __FUNCTION__);
if (frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_REQUEST) {
sd = restconf_stream_data_new(rc, frame->hd.stream_id);
nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, sd);
}
return 0;
}
#ifdef XXX
/*! Translate http header by capitalizing, prepend w HTTP_ and - -> _
* Example: Host -> HTTP_HOST
*/
static int
evhtp_convert_fcgi(evhtp_header_t *hdr,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
cbuf *cb = NULL;
int i;
char c;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* convert key name */
cprintf(cb, "HTTP_");
for (i=0; i<strlen(hdr->key); i++){
c = hdr->key[i] & 0xff;
if (islower(c))
cprintf(cb, "%c", toupper(c));
else if (c == '-')
cprintf(cb, "_");
else
cprintf(cb, "%c", c);
}
if (restconf_param_set(h, cbuf_get(cb), hdr->val) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
#endif
/*! Map from nghttp2 headers to "fcgi" type parameters used in clixon code
* Both |name| and |value| are guaranteed to be NULL-terminated.
*/
static int
nghttp2_hdr2clixon(clicon_handle h,
char *name,
char *value)
{
int retval = -1;
if (strcmp(name, ":path") == 0){
/* XXX "/restconf" Is PATH really REQUEST_URI? */
if (restconf_param_set(h, "REQUEST_URI", value) < 0) /* XXX string? */
goto done;
}
else if (strcmp(name, ":method") == 0){
if (restconf_param_set(h, "REQUEST_METHOD", value) < 0)
goto done;
}
else if (strcmp(name, ":scheme") == 0){
if (strcmp(value, "https") == 0 &&
restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */
goto done;
}
else if (strcmp(name, ":authority") == 0){
if (restconf_param_set(h, "HTTP_HOST", value) < 0)
goto done;
}
else if (restconf_convert_hdr(h, name, value) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Header name/value pair is received
* Both |name| and |value| are guaranteed to be NULL-terminated.
* If the application uses `nghttp2_session_mem_recv()`, it can return
* :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()`
* return without processing further input bytes.
*/
static int
on_header_callback(nghttp2_session *session,
const nghttp2_frame *frame,
const uint8_t *name,
size_t namelen,
const uint8_t *value,
size_t valuelen,
uint8_t flags,
void *user_data)
{
int retval = -1;
restconf_conn_h *rc = (restconf_conn_h *)user_data;
restconf_stream_data *sd;
clicon_debug(1, "%s %d:", __FUNCTION__, frame->hd.stream_id);
switch (frame->hd.type){
case NGHTTP2_HEADERS:
assert (frame->headers.cat == NGHTTP2_HCAT_REQUEST);
clicon_debug(1, "%s %s %s", __FUNCTION__, name, value);
if ((sd = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) == NULL)
break;
if (nghttp2_hdr2clixon(rc->rc_h, (char*)name, (char*)value) < 0)
goto done;
break;
default:
break;
}
retval = 0;
done:
return retval;
}
/*!
*/
static ssize_t
select_padding_callback(nghttp2_session *session,
const nghttp2_frame *frame,
size_t max_payloadlen,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static ssize_t
data_source_read_length_callback(nghttp2_session *session,
uint8_t frame_type,
int32_t stream_id,
int32_t session_remote_window_size,
int32_t stream_remote_window_size,
uint32_t remote_max_frame_size,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*! Invoked when a frame header is received.
* Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will
* also be called when frame header of CONTINUATION frame is received.
*/
static int
on_begin_frame_callback(nghttp2_session *session,
const nghttp2_frame_hd *hd,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s type:%d", __FUNCTION__, hd->type);
if (hd->type == NGHTTP2_CONTINUATION)
assert(0);
return 0;
}
/*!
*/
static int
send_data_callback(nghttp2_session *session,
nghttp2_frame *frame,
const uint8_t *framehd, size_t length,
nghttp2_data_source *source,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static ssize_t
pack_extension_callback(nghttp2_session *session,
uint8_t *buf, size_t len,
const nghttp2_frame *frame,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
unpack_extension_callback(nghttp2_session *session,
void **payload,
const nghttp2_frame_hd *hd,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
on_extension_chunk_recv_callback(nghttp2_session *session,
const nghttp2_frame_hd *hd,
const uint8_t *data,
size_t len,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
error_callback(nghttp2_session *session,
const char *msg,
size_t len,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*!
*/
static int
error_callback2(nghttp2_session *session,
int lib_error_code,
const char *msg,
size_t len,
void *user_data)
{
// restconf_conn_h *rc = (restconf_conn_h *)user_data;
clicon_debug(1, "%s", __FUNCTION__);
return 0;
}
/*
* XXX see session_recv
*/
int
http2_recv(restconf_conn_h *rc,
const unsigned char *buf,
size_t n)
{
int retval = -1;
nghttp2_error ngerr;
clicon_debug(1, "%s", __FUNCTION__);
if (rc->rc_ngsession == NULL){
clicon_err(OE_RESTCONF, EINVAL, "No nghttp2 session");
goto done;
}
if ((ngerr = nghttp2_session_mem_recv(rc->rc_ngsession, buf, n)) < 0){
clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_mem_recv");
goto done;
}
retval = 0;
done:
return retval;
}
/*! Initialize callbacks
*/
int
http2_session_init(restconf_conn_h *rc)
{
nghttp2_session_callbacks *callbacks = NULL;
nghttp2_session *session = NULL;
nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback);
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(callbacks, on_invalid_frame_recv_callback);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback);
nghttp2_session_callbacks_set_before_frame_send_callback(callbacks, before_frame_send_callback);
nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, on_frame_send_callback);
nghttp2_session_callbacks_set_on_frame_not_send_callback(callbacks, on_frame_not_send_callback);
nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback);
nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback);
nghttp2_session_callbacks_set_select_padding_callback(callbacks, select_padding_callback);
nghttp2_session_callbacks_set_data_source_read_length_callback(callbacks, data_source_read_length_callback);
nghttp2_session_callbacks_set_on_begin_frame_callback(callbacks, on_begin_frame_callback);
nghttp2_session_callbacks_set_send_data_callback(callbacks, send_data_callback);
nghttp2_session_callbacks_set_pack_extension_callback(callbacks, pack_extension_callback);
nghttp2_session_callbacks_set_unpack_extension_callback(callbacks, unpack_extension_callback);
nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(callbacks, on_extension_chunk_recv_callback);
nghttp2_session_callbacks_set_error_callback(callbacks, error_callback);
nghttp2_session_callbacks_set_error_callback2(callbacks, error_callback2);
/* Register callbacks with nghttp2 */
nghttp2_session_server_new(&session, callbacks, rc);
nghttp2_session_callbacks_del(callbacks);
rc->rc_ngsession = session;
return 0;
}
#endif /* HAVE_LIBNGHTTP2 */

View file

@ -0,0 +1,48 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020-2021 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 *****
*
* Virtual clixon restconf API functions.
*/
#ifndef _RESTCONF_NGHTTP2_H_
#define _RESTCONF_NGHTTP2_H_
/*
* Prototypes
*/
int clixon_nghttp2_log_cb(void *handle, int suberr, cbuf *cb);
int http2_recv(restconf_conn_h *rc, const unsigned char *buf, size_t n);
int http2_session_init(restconf_conn_h *rc);
#endif /* _RESTCONF_NGHTTP2_H_ */

View file

@ -70,19 +70,23 @@ enum clicon_err{
OE_SYSLOG, /* syslog error */
OE_ROUTING, /* routing daemon error (eg quagga) */
OE_XML, /* xml parsing etc */
OE_SSL, /* Openssl errors, see eg ssl_get_error */
OE_RESTCONF, /* RESTCONF errors */
OE_PLUGIN, /* plugin loading, etc */
OE_YANG , /* Yang error */
OE_FATAL, /* Fatal error */
OE_UNDEF,
/*-- From here error extensions using clixon_err_cat_reg, XXX register dynamically? --*/
OE_SSL, /* Openssl errors, see eg ssl_get_error */
OE_NGHTTP2, /* nghttp2 errors, see HAVE_LIBNGHTTP2 */
};
/* Clixon error category log callback
* @param[in] handle Application-specific handle
* @param[in] suberr Application-specific handle
* @param[out] cb Read log/error string into this buffer
*/
typedef int (clixon_cat_log_cb)(void *handle, cbuf *cb);
typedef int (clixon_cat_log_cb)(void *handle, int suberr, cbuf *cb);
/*
* Variables

View file

@ -116,12 +116,14 @@ static struct errvec EV[] = {
{"Syslog error", OE_SYSLOG},
{"Routing demon error", OE_ROUTING},
{"XML error", OE_XML},
{"OpenSSL error", OE_SSL},
{"RESTCONF error", OE_RESTCONF},
{"Plugins", OE_PLUGIN},
{"Yang error", OE_YANG},
{"FATAL", OE_FATAL},
{"Undefined", OE_UNDEF},
/* From here error extensions using clixon_err_cat_reg */
{"OpenSSL error", OE_SSL},
{"Nghttp2 error", OE_NGHTTP2},
{NULL, -1}
};
@ -224,7 +226,7 @@ clicon_err_fn(const char *fn,
va_end(args);
strncpy(clicon_err_reason, msg, ERR_STRLEN-1);
/* Check category callbacks */
/* Check category callbacks as defined in clixon_err_cat_reg */
if ((cec = find_category(category)) != NULL &&
cec->cec_logfn){
cbuf *cb = NULL;
@ -232,7 +234,7 @@ clicon_err_fn(const char *fn,
fprintf(stderr, "cbuf_new: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
goto done;
}
if (cec->cec_logfn(cec->cec_handle, cb) < 0)
if (cec->cec_logfn(cec->cec_handle, suberr, cb) < 0)
goto done;
/* Here we could take care of specific errno, like application-defined errors */
clicon_log(LOG_ERR, "%s: %d: %s: %s: %s",