From b680e3c5ac4f20a4d5446bf4271a0cc985dd03d1 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Thu, 3 Jun 2021 22:47:54 +0200 Subject: [PATCH] - Restconf nghttp2 compiles --- CHANGELOG.md | 8 +- apps/restconf/Makefile.in | 4 + apps/restconf/restconf_api_native.c | 6 +- apps/restconf/restconf_evhtp.c | 405 +++++++++++++++ apps/restconf/restconf_evhtp.h | 47 ++ apps/restconf/restconf_lib.c | 37 ++ apps/restconf/restconf_lib.h | 2 + apps/restconf/restconf_main_native.c | 418 +++------------ apps/restconf/restconf_native.h | 35 +- apps/restconf/restconf_nghttp2.c | 727 +++++++++++++++++++++++++++ apps/restconf/restconf_nghttp2.h | 48 ++ lib/clixon/clixon_err.h | 8 +- lib/src/clixon_err.c | 8 +- 13 files changed, 1375 insertions(+), 378 deletions(-) create mode 100644 apps/restconf/restconf_evhtp.c create mode 100644 apps/restconf/restconf_evhtp.h create mode 100644 apps/restconf/restconf_nghttp2.c create mode 100644 apps/restconf/restconf_nghttp2.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 85333298..3ca1a0e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 075e3acd..5dbca293 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -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) diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index 80bdd61b..1bab4c4a 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -53,10 +53,14 @@ /* evhtp */ #define EVHTP_DISABLE_REGEX #define EVHTP_DISABLE_EVTHR - #include #endif /* HAVE_LIBEVHTP */ +#ifdef HAVE_LIBNGHTTP2 +#include +#endif + + /* cligen */ #include diff --git a/apps/restconf/restconf_evhtp.c b/apps/restconf/restconf_evhtp.c new file mode 100644 index 00000000..ef376edb --- /dev/null +++ b/apps/restconf/restconf_evhtp.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#ifdef HAVE_LIBEVHTP +/* evhtp */ +#include /* evbuffer */ +#define EVHTP_DISABLE_REGEX +#define EVHTP_DISABLE_EVTHR + +#include +#include /* XXX inline this / use SSL directly */ +#endif /* HAVE_LIBEVHTP */ + +#ifdef HAVE_LIBNGHTTP2 /* To get restconf_native.h include files right */ +#include +#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 ?=&= 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 */ + diff --git a/apps/restconf/restconf_evhtp.h b/apps/restconf/restconf_evhtp.h new file mode 100644 index 00000000..085b0496 --- /dev/null +++ b/apps/restconf/restconf_evhtp.h @@ -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_ */ diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index b0f545a2..8a979fd0 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -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; ikey, 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; ikey); 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 ?=&= 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){ diff --git a/apps/restconf/restconf_native.h b/apps/restconf/restconf_native.h index 54fd61d2..86cfa026 100644 --- a/apps/restconf/restconf_native.h +++ b/apps/restconf/restconf_native.h @@ -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; /* diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c new file mode 100644 index 00000000..1ac0a759 --- /dev/null +++ b/apps/restconf/restconf_nghttp2.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#ifdef HAVE_LIBEVHTP /* To get restconf_native.h include files right */ +/* evhtp */ +#include /* evbuffer */ +#define EVHTP_DISABLE_REGEX +#define EVHTP_DISABLE_EVTHR +#include +#endif /* HAVE_LIBEVHTP */ + +#ifdef HAVE_LIBNGHTTP2 +#include +#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; irc_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; ikey); 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 */ diff --git a/apps/restconf/restconf_nghttp2.h b/apps/restconf/restconf_nghttp2.h new file mode 100644 index 00000000..35059f7b --- /dev/null +++ b/apps/restconf/restconf_nghttp2.h @@ -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_ */ diff --git a/lib/clixon/clixon_err.h b/lib/clixon/clixon_err.h index 511bc6a6..7f630073 100644 --- a/lib/clixon/clixon_err.h +++ b/lib/clixon/clixon_err.h @@ -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 diff --git a/lib/src/clixon_err.c b/lib/src/clixon_err.c index 501cf65e..ba49e879 100644 --- a/lib/src/clixon_err.c +++ b/lib/src/clixon_err.c @@ -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",