From 4f513385e99aa62ae0a31592cc1f730cc294b86d Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sun, 13 Jun 2021 12:43:19 +0200 Subject: [PATCH] - Restconf native http/1 to http/2 upgrade (non-tls) --- CHANGELOG.md | 4 +- apps/restconf/restconf_api_native.c | 18 +--- apps/restconf/restconf_evhtp.c | 141 ++++++++++++++++++++------- apps/restconf/restconf_lib.h | 1 - apps/restconf/restconf_main_native.c | 61 +++++++++--- apps/restconf/restconf_native.c | 4 + apps/restconf/restconf_native.h | 8 +- apps/restconf/restconf_nghttp2.c | 105 ++++++++++++-------- apps/restconf/restconf_nghttp2.h | 1 + configure | 12 ++- configure.ac | 6 +- test/config.sh.in | 3 +- test/lib.sh | 11 ++- test/test_perf_restconf.sh | 13 ++- test/test_restconf.sh | 7 +- test/test_restconf_err.sh | 2 +- test/test_yang_anydata.sh | 11 +-- 17 files changed, 286 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ba84e79..d4c74257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,7 +38,9 @@ Expected: June 2021 * Enable using: `--with-restconf=native --enable-nghttp2` * FCGI/nginx not affected only for `--with-restconf=native` * HTTP/1 co-exists, unless `--disable-evhtp` which results in http/2 only - * TLS ALPN upgrade works but http (non SSL) http/1->http/2 upgrade is not yet implemented + * Upgrade from HTTP/1.1 to HTTP/2 + * https: ALPN upgrade + * http: Upgrade header * 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 diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index 3917296b..cf5df644 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -151,12 +151,10 @@ restconf_reply_send(void *req0, if (cb != NULL){ if (cbuf_len(cb)){ cprintf(cb, "\r\n"); -#if 0 /* Double for evhtp, single for nghttp2 */ - if (restconf_reply_header(sd, "Content-Length", "%d", cbuf_len(cb)) < 0) - goto done; -#endif - if (head) + sd->sd_body_len = cbuf_len(cb); + if (head){ cbuf_free(cb); + } else{ sd->sd_body = cb; sd->sd_body_offset = 0; @@ -164,17 +162,11 @@ restconf_reply_send(void *req0, } else{ cbuf_free(cb); -#if 0 - if (restconf_reply_header(sd, "Content-Length", "0") < 0) - goto done; -#endif + sd->sd_body_len = 0; } } -#if 0 else - if (restconf_reply_header(sd, "Content-Length", "0") < 0) - goto done; -#endif + sd->sd_body_len = 0; retval = 0; done: return retval; diff --git a/apps/restconf/restconf_evhtp.c b/apps/restconf/restconf_evhtp.c index 09acd04f..b69eba42 100644 --- a/apps/restconf/restconf_evhtp.c +++ b/apps/restconf/restconf_evhtp.c @@ -195,7 +195,7 @@ evhtp_convert_fcgi(evhtp_header_t *hdr, return restconf_convert_hdr(h, hdr->key, hdr->val); } -/*! Map from evhtp information to "fcgi" type parameters used in clixon code +/*! convert parameters from evhtp to fcgi-like parameters used by clixon * * While all these params come via one call in fcgi, the information must be taken from * several different places in evhtp @@ -216,9 +216,9 @@ evhtp_convert_fcgi(evhtp_header_t *hdr, * @note there may be more used by an application plugin */ static int -evhtp_params_set(clicon_handle h, - evhtp_request_t *req, - cvec *qvec) +convert_evhtp_params2clixon(clicon_handle h, + evhtp_request_t *req, + cvec *qvec) { int retval = -1; htp_method meth; @@ -395,9 +395,9 @@ restconf_evhtp_reply(restconf_conn *rc, * (Successful) response to a CONNECT request (Section 4.3.6 of * [RFC7231]). */ - if (restconf_reply_header(sd, "Content-Length", "%d", - (sd->sd_body!=NULL)?cbuf_len(sd->sd_body):0) < 0) - goto done; + if (sd->sd_code != 204 && sd->sd_code > 199) + if (restconf_reply_header(sd, "Content-Length", "%lu", sd->sd_body_len) < 0) + goto done; /* Create reply and write headers */ if (native_send_reply(rc, sd, req) < 0) goto done; @@ -410,6 +410,60 @@ restconf_evhtp_reply(restconf_conn *rc, return retval; } +#ifdef HAVE_LIBNGHTTP2 +/*! Check http/1 UPGRADE to http/2 + * If upgrade headers are encountered AND http/2 is configured, then + * - add upgrade headers or signal error + * - set http2 flag get settings to and signal to upper layer to do the actual transition. + * @retval -1 Error + * @retval 0 Yes, upgrade dont proceed with request + * @retval 1 No upgrade, proceed with request + * @note currently upgrade header is checked always if nghttp2 is configured but may be a + * runtime config option + */ +static int +evhtp_upgrade_http2(clicon_handle h, + restconf_stream_data *sd) +{ + int retval = -1; + char *str; + char *settings; + cxobj *xerr = NULL; + + if ((str = restconf_param_get(h, "HTTP_UPGRADE")) != NULL){ + /* Only accept "h2c" */ + if (strcmp(str, "h2c") != 0){ + if (netconf_invalid_value_xml(&xerr, "protocol", "Invalid upgrade token") < 0) + goto done; + if (api_return_err0(h, sd, xerr, 1, YANG_DATA_JSON, 0) < 0) + goto done; + if (xerr) + xml_free(xerr); + } + else { + if (restconf_reply_header(sd, "Connection", "Upgrade") < 0) + goto done; + if (restconf_reply_header(sd, "Upgrade", "h2c") < 0) + goto done; + if (restconf_reply_send(sd, 101, NULL, 0) < 0) /* Swithcing protocols */ + goto done; + /* Signal http/2 upgrade to http/2 to upper restconf_connection handling */ + sd->sd_upgrade2 = 1; + if ((settings = restconf_param_get(h, "HTTP_HTTP2_Settings")) != NULL && + (sd->sd_settings2 = (uint8_t*)strdup(settings)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + retval = 0; /* Yes, upgrade or error */ + } + else + retval = 1; /* No upgrade, proceed with request */ + done: + return retval; +} +#endif /* HAVE_LIBNGHTTP2 */ + /*! Callback for each incoming http request for path / * * This are all messages except /.well-known, Registered with evhtp_set_cb @@ -429,10 +483,12 @@ restconf_path_root(evhtp_request_t *req, int retval = -1; clicon_handle h; int ret; - cvec *qvec = NULL; evhtp_connection_t *evconn; restconf_conn *rc; restconf_stream_data *sd; + size_t len; + unsigned char *buf; + int keep_params = 0; /* set to 1 if dont delete params */ clicon_debug(1, "------------"); if ((h = (clicon_handle)arg) == NULL){ @@ -458,46 +514,48 @@ restconf_path_root(evhtp_request_t *req, /* 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){ + if ((sd->sd_qvec = cvec_new(0)) ==NULL){ clicon_err(OE_UNIX, errno, "cvec_new"); evhtp_internal_error(req); goto done; } /* Get indata */ - { - size_t len; - unsigned char *buf; - - len = evbuffer_get_length(req->buffer_in); - if (len > 0){ - if ((buf = evbuffer_pullup(req->buffer_in, len)) == NULL){ - clicon_err(OE_CFG, errno, "evbuffer_pullup"); - goto done; - } - /* Note the pullup may not be null-terminated */ - cbuf_append_buf(sd->sd_indata, buf, len); + if ((len = evbuffer_get_length(req->buffer_in)) > 0){ + if ((buf = evbuffer_pullup(req->buffer_in, len)) == NULL){ + clicon_err(OE_CFG, errno, "evbuffer_pullup"); + goto done; } + /* Note the pullup may not be null-terminated */ + cbuf_append_buf(sd->sd_indata, buf, len); } - /* set fcgi-like paramaters (ignore query vector) + /* Convert parameters from evhtp to fcgi-like parameters used by clixon * ret = 0 means an error has already been sent */ - if ((ret = evhtp_params_set(h, req, qvec)) < 0){ + if ((ret = convert_evhtp_params2clixon(h, req, sd->sd_qvec)) < 0){ evhtp_internal_error(req); goto done; } +#ifdef HAVE_LIBNGHTTP2 + if (ret == 1){ + if ((ret = evhtp_upgrade_http2(h, sd)) < 0){ + evhtp_internal_error(req); + goto done; + } + if (ret == 0) + keep_params = 1; + } +#endif if (ret == 1){ /* call generic function */ - if (api_root_restconf(h, sd, qvec) < 0){ + if (api_root_restconf(h, sd, sd->sd_qvec) < 0){ evhtp_internal_error(req); goto done; } } /* Clear input request parameters from this request */ - if (restconf_param_del_all(h) < 0){ + if (!keep_params && restconf_param_del_all(h) < 0){ evhtp_internal_error(req); goto done; } @@ -512,8 +570,6 @@ restconf_path_root(evhtp_request_t *req, retval = 0; done: clicon_debug(1, "%s %d", __FUNCTION__, retval); - if (qvec) - cvec_free(qvec); return; /* void */ } @@ -533,6 +589,7 @@ restconf_path_wellknown(evhtp_request_t *req, evhtp_connection_t *evconn; restconf_conn *rc; restconf_stream_data *sd; + int keep_params = 0; /* set to 1 if dont delete params */ clicon_debug(1, "------------"); if ((h = (clicon_handle)arg) == NULL){ @@ -558,13 +615,29 @@ restconf_path_wellknown(evhtp_request_t *req, /* 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){ + /* Query vector, ie the ?a=x&b=y stuff */ + if ((sd->sd_qvec = cvec_new(0)) ==NULL){ + clicon_err(OE_UNIX, errno, "cvec_new"); evhtp_internal_error(req); goto done; } + /* Convert parameters from evhtp to fcgi-like parameters used by clixon + * ret = 0 means an error has already been sent + */ + if ((ret = convert_evhtp_params2clixon(h, req, sd->sd_qvec)) < 0){ + evhtp_internal_error(req); + goto done; + } +#ifdef HAVE_LIBNGHTTP2 + if (ret == 1){ + if ((ret = evhtp_upgrade_http2(h, sd)) < 0){ + evhtp_internal_error(req); + goto done; + } + if (ret == 0) + keep_params = 1; + } +#endif if (ret == 1){ /* call generic function */ if (api_well_known(h, sd) < 0){ @@ -573,7 +646,7 @@ restconf_path_wellknown(evhtp_request_t *req, } } /* Clear input request parameters from this request */ - if (restconf_param_del_all(h) < 0){ + if (!keep_params && restconf_param_del_all(h) < 0){ evhtp_internal_error(req); goto done; } diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index e4670df3..53292f5c 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -97,7 +97,6 @@ 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_ */ diff --git a/apps/restconf/restconf_main_native.c b/apps/restconf/restconf_main_native.c index c03cbe6a..f2fda9a8 100644 --- a/apps/restconf/restconf_main_native.c +++ b/apps/restconf/restconf_main_native.c @@ -585,7 +585,7 @@ restconf_ssl_context_configure(clixon_handle h, * There are many variants to closing, one could probably make this more generic * and always use this function, but it is difficult. */ -static int +int restconf_close_ssl_socket(restconf_conn *rc, int shutdown) { @@ -594,9 +594,11 @@ restconf_close_ssl_socket(restconf_conn *rc, #ifdef HAVE_LIBEVHTP evhtp_connection_t *evconn; - evconn = rc->rc_evconn; - clicon_debug(1, "%s evconn-free (%p) 1", __FUNCTION__, evconn); - evhtp_connection_free(evconn); /* evhtp */ + if ((evconn = rc->rc_evconn) != NULL){ + clicon_debug(1, "%s evconn-free (%p)", __FUNCTION__, evconn); + if (evconn) + evhtp_connection_free(evconn); /* evhtp */ + } #endif /* HAVE_LIBEVHTP */ if (rc->rc_ssl != NULL){ if (shutdown && (ret = SSL_shutdown(rc->rc_ssl)) < 0){ @@ -714,9 +716,9 @@ restconf_connection(int s, if ((n = read(rc->rc_s, buf, sizeof(buf))) < 0){ /* XXX atomicio ? */ if (errno == ECONNRESET) {/* Connection reset by peer */ clicon_debug(1, "%s %d Connection reset by peer", __FUNCTION__, rc->rc_s); + clixon_event_unreg_fd(rc->rc_s, restconf_connection); close(rc->rc_s); restconf_conn_free(rc); - clixon_event_unreg_fd(rc->rc_s, restconf_connection); goto ok; /* Close socket and ssl */ } clicon_err(OE_XML, errno, "read"); @@ -762,7 +764,7 @@ restconf_connection(int s, restconf_conn_free(rc); evhtp_connection_free(evconn); goto ok; - } + } /* connection_parse_nobev */ clicon_debug(1, "%s connection_parse OK", __FUNCTION__); /* default stream */ if ((sd = restconf_stream_find(rc, 0)) == NULL){ @@ -816,6 +818,39 @@ restconf_connection(int s, "protocolmalformed-messageNo evhtp output") < 0) goto done; } +#ifdef HAVE_LIBNGHTTP2 + if (sd->sd_upgrade2){ + nghttp2_error ngerr; + /* Switch to http/2 according to RFC 7540 Sec 3.2 and RFC 7230 Sec 6.7 */ + rc->rc_proto = HTTP_2; + if (http2_session_init(rc) < 0){ + restconf_close_ssl_socket(rc, 1); + goto done; + } + /* The HTTP/1.1 request that is sent prior to upgrade is assigned a + * stream identifier of 1 (see Section 5.1.1) with default priority + */ + sd->sd_stream_id = 1; + /* The first HTTP/2 frame sent by the server MUST be a server connection + * preface (Section 3.5) consisting of a SETTINGS frame (Section 6.5). + */ + if ((ngerr = nghttp2_session_upgrade2(rc->rc_ngsession, + sd->sd_settings2, + sd->sd_settings2?strlen((const char*)sd->sd_settings2):0, + 0, /* XXX: 1 if HEAD */ + NULL)) < 0){ + clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_upgrade2"); + goto done; + } + if (http2_send_server_connection(rc) < 0){ + restconf_close_ssl_socket(rc, 1); + goto done; + } + /* Use params from original http/1 session to http/2 stream */ + if (http2_exec(rc, sd, rc->rc_ngsession, 1) < 0) + goto done; + } +#endif break; #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 @@ -834,7 +869,7 @@ restconf_connection(int s, done: clicon_debug(1, "%s retval %d", __FUNCTION__, retval); return retval; -} +} /* restconf_connection */ #if 0 /* debug */ /*! Debug print all loaded certs @@ -983,7 +1018,7 @@ ssl_alpn_check(clicon_handle h, if (cberr) cbuf_free(cberr); return retval; -} +} /* ssl_alpn_check */ /*! Accept new socket client * @param[in] fd Socket (unix or ip) @@ -1013,8 +1048,10 @@ restconf_accept_client(int fd, clicon_debug(1, "%s %d", __FUNCTION__, fd); #ifdef HAVE_LIBNGHTTP2 - /* If nghttp2 let default be 2.0 NOTE http protocol negotiation */ - proto = HTTP_2; +#ifndef HAVE_LIBEVHTP + proto = HTTP_2; /* If nghttp2 only let default be 2.0 */ +#endif + /* If also evhtp, keep HTTP_11 */ #endif if ((rsock = (restconf_socket *)arg) == NULL){ clicon_err(OE_YANG, EINVAL, "rsock is NULL"); @@ -1266,7 +1303,7 @@ restconf_accept_client(int fd, if (name) free(name); return retval; -} +} /* restconf_accept_client */ static int restconf_native_terminate(clicon_handle h) @@ -1599,7 +1636,7 @@ restconf_clixon_init(clicon_handle h, clixon_plugin_t *cp = NULL; char *str; cvec *nsctx_global = NULL; /* Global namespace context */ - cxobj *xrestconf; + cxobj *xrestconf = NULL; cxobj *xerr = NULL; int ret; diff --git a/apps/restconf/restconf_native.c b/apps/restconf/restconf_native.c index ebc08189..9bd1ff86 100644 --- a/apps/restconf/restconf_native.c +++ b/apps/restconf/restconf_native.c @@ -139,6 +139,10 @@ restconf_stream_free(restconf_stream_data *sd) cbuf_free(sd->sd_body); if (sd->sd_path) free(sd->sd_path); + if (sd->sd_settings2) + free(sd->sd_settings2); + if (sd->sd_qvec) + cvec_free(sd->sd_qvec); free(sd); return 0; } diff --git a/apps/restconf/restconf_native.h b/apps/restconf/restconf_native.h index 3b152dc5..5a78017c 100644 --- a/apps/restconf/restconf_native.h +++ b/apps/restconf/restconf_native.h @@ -71,16 +71,20 @@ typedef struct { cvec *sd_outp_hdrs; /* List of output headers */ cbuf *sd_outp_buf; /* Output buffer */ cbuf *sd_body; /* http output body as cbuf terminated with \r\n */ + size_t sd_body_len; /* Content-Length, note for HEAD body body can be NULL and this non-zero */ size_t sd_body_offset; /* Offset into body */ cbuf *sd_indata; /* Receive/input data */ char *sd_path; /* Uri path, uri-encoded, without args (eg ?) */ uint16_t sd_code; /* If != 0 send a reply XXX: need reply flag? */ struct restconf_conn *sd_conn; /* Backpointer to connection this stream is part of */ restconf_http_proto sd_proto; /* http protocol XXX not sure this is needed */ + cvec *sd_qvec; /* Query parameters, ie ?a=b&c=d */ void *sd_req; /* Lib-specific request, eg evhtp_request_t * */ + int sd_upgrade2; /* Upgrade to http/2 */ + uint8_t *sd_settings2; /* Settings for upgrade to http/2 request */ } restconf_stream_data; - /* Restconf connection handle +/* Restconf connection handle * Per connection request */ typedef struct restconf_conn { @@ -132,6 +136,8 @@ int restconf_stream_free(restconf_stream_data *sd); restconf_conn *restconf_conn_new(clicon_handle h, int s); int restconf_conn_free(restconf_conn *rc); int ssl_x509_name_oneline(SSL *ssl, char **oneline); + +int restconf_close_ssl_socket(restconf_conn *rc, int shutdown); /* XXX in restconf_main_native.c */ #endif /* _RESTCONF_NATIVE_H_ */ diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c index f770aa68..65f83ea2 100644 --- a/apps/restconf/restconf_nghttp2.c +++ b/apps/restconf/restconf_nghttp2.c @@ -99,10 +99,9 @@ #include "restconf_err.h" #include "restconf_root.h" #include "restconf_native.h" /* Restconf-openssl mode specific headers*/ +#ifdef HAVE_LIBNGHTTP2 #include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/ -#ifdef HAVE_LIBNGHTTP2 /* XXX MOVE */ - #define ARRLEN(x) (sizeof(x) / sizeof(x[0])) /*! Map http2 frame types in nghttp2 @@ -283,12 +282,10 @@ restconf_nghttp2_path(restconf_stream_data *sd) { int retval = -1; clicon_handle h; - cvec *qvec = NULL; - char *query = NULL; restconf_conn *rc; char *oneline = NULL; cvec *cvv = NULL; - char *cn; + char *cn; clicon_debug(1, "------------"); rc = sd->sd_conn; @@ -296,15 +293,6 @@ restconf_nghttp2_path(restconf_stream_data *sd) clicon_err(OE_RESTCONF, EINVAL, "arg is NULL"); goto done; } - /* get accepted connection */ - /* Query vector, ie the ?a=x&b=y stuff */ - query = restconf_param_get(h, "REQUEST_URI"); - if ((query = index(query, '?')) != NULL){ - query++; - if (strlen(query) && - uri_str2cvec(query, '&', '=', 1, &qvec) < 0) - goto done; - } /* Slightly awkward way of taking SSL cert subject and CN and add it to restconf parameters * instead of accessing it directly */ if (rc->rc_ssl != NULL){ @@ -325,7 +313,7 @@ restconf_nghttp2_path(restconf_stream_data *sd) if (api_well_known(h, sd) < 0) goto done; } - else if (api_root_restconf(h, sd, qvec) < 0) + else if (api_root_restconf(h, sd, sd->sd_qvec) < 0) goto done; /* Clear (fcgi) paramaters from this request */ if (restconf_param_del_all(h) < 0) @@ -341,8 +329,6 @@ restconf_nghttp2_path(restconf_stream_data *sd) cvec_free(cvv); if (oneline) free(oneline); - if (qvec) - cvec_free(qvec); return retval; /* void */ } @@ -429,7 +415,6 @@ restconf_submit_response(nghttp2_session *session, hdr->value = (uint8_t*)valstr; hdr->namelen = strlen(":status"); hdr->valuelen = strlen(valstr); - clicon_debug(1, "%s val:'%s' valuelen:%lu", __FUNCTION__, hdr->value, hdr->valuelen); hdr->flags = 0; cv = NULL; @@ -453,6 +438,49 @@ restconf_submit_response(nghttp2_session *session, return retval; } +/*! Simulate a received request in an upgrade scenario by talking the http/1 parameters + */ +int +http2_exec(restconf_conn *rc, + restconf_stream_data *sd, + nghttp2_session *session, + int32_t stream_id) +{ + int retval = -1; + + if ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL) + goto done; + sd->sd_proto = HTTP_2; /* XXX is this necessary? */ + if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0 || + strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){ + if (restconf_nghttp2_path(sd) < 0) + goto done; + } + else + ; /* ignore */ + /* If body, add a content-length header + * A server MUST NOT send a Content-Length header field in any response + * with a status code of 1xx (Informational) or 204 (No Content). A + * server MUST NOT send a Content-Length header field in any 2xx + * (Successful) response to a CONNECT request (Section 4.3.6 of + * [RFC7231]). + */ + if (sd->sd_code != 204 && sd->sd_code > 199) + if (restconf_reply_header(sd, "Content-Length", "%lu", sd->sd_body_len) < 0) + goto done; + if (sd->sd_code){ + if (restconf_submit_response(session, rc, stream_id, sd) < 0) + goto done; + } + else { + /* 500 Internal server error ? */ + } + + retval = 0; + done: + return retval; +} + /*! A frame is received */ static int @@ -463,6 +491,7 @@ on_frame_recv_callback(nghttp2_session *session, int retval = -1; restconf_conn *rc = (restconf_conn *)user_data; restconf_stream_data *sd = NULL; + char *query; clicon_debug(1, "%s %s %d", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, frame->hd.type), @@ -473,33 +502,25 @@ on_frame_recv_callback(nghttp2_session *session, /* 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. - */ + * 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 ((sd->sd_path = restconf_uripath(rc->rc_h)) == NULL) - goto ok; - sd->sd_proto = HTTP_2; /* XXX is this necessary? */ - if (strncmp(sd->sd_path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0 || - strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){ - if (restconf_nghttp2_path(sd) < 0) + /* Query vector, ie the ?a=x&b=y stuff */ + query = restconf_param_get(rc->rc_h, "REQUEST_URI"); + if ((query = index(query, '?')) != NULL){ + query++; + if (strlen(query) && + uri_str2cvec(query, '&', '=', 1, &sd->sd_qvec) < 0) goto done; } - else - ; /* ignore */ - if (sd->sd_code){ - if (restconf_submit_response(session, rc, frame->hd.stream_id, sd) < 0) - goto done; - } - else { - /* 500 Internal server error ? */ - } + if (http2_exec(rc, sd, session, frame->hd.stream_id) < 0) + goto done; } break; default: break; } - ok: retval = 0; done: return retval; @@ -584,8 +605,16 @@ on_stream_close_callback(nghttp2_session *session, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; - clicon_debug(1, "%s", __FUNCTION__); - //session_data *sd = (session_data*)user_data; + + clicon_debug(1, "%s %d %s", __FUNCTION__, error_code, nghttp2_strerror(error_code)); +#ifdef NOTNEEDED /* XXX think this is not necessary? */ + if (error_code){ + if (restconf_close_ssl_socket(rc, 0) < 0) + return -1; + if (restconf_conn_free(rc) < 0) + return -1; + } +#endif return 0; } diff --git a/apps/restconf/restconf_nghttp2.h b/apps/restconf/restconf_nghttp2.h index 351dd88d..ea522ec1 100644 --- a/apps/restconf/restconf_nghttp2.h +++ b/apps/restconf/restconf_nghttp2.h @@ -42,6 +42,7 @@ * Prototypes */ int clixon_nghttp2_log_cb(void *handle, int suberr, cbuf *cb); +int http2_exec(restconf_conn *rc, restconf_stream_data *sd, nghttp2_session *session, int32_t stream_id); int http2_recv(restconf_conn *rc, const unsigned char *buf, size_t n); int http2_send_server_connection(restconf_conn *rc); int http2_session_init(restconf_conn *rc); diff --git a/configure b/configure index b542f5ba..95b77bdb 100755 --- a/configure +++ b/configure @@ -637,7 +637,8 @@ CPP wwwdir enable_optyangs with_libxml2 -with_http2 +HAVE_LIBNGHTTP2 +HAVE_LIBEVHTP with_restconf LINKAGE SH_SUFFIX @@ -3357,8 +3358,10 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' # Set to native or fcgi -> compile apps/restconf -with_http2=false - +HAVE_LIBEVHTP=false + # consider using neutral constant such as with-http1 +HAVE_LIBNGHTTP2=false + # consider using neutral constant such as with-http2 # Home dir for web user, such as nginx fcgi sockets @@ -5283,6 +5286,7 @@ else as_fn_error $? "libevhtp missing" "$LINENO" 5 fi + HAVE_LIBEVHTP=true fi # Check if nghttp2 is enabled for http/2 @@ -5363,7 +5367,7 @@ else as_fn_error $? "nghttp2 missing" "$LINENO" 5 fi - with_http2=true + HAVE_LIBNGHTTP2=true fi $as_echo "#define WITH_RESTCONF_NATIVE 1" >>confdefs.h diff --git a/configure.ac b/configure.ac index 280dd7cc..df126452 100644 --- a/configure.ac +++ b/configure.ac @@ -96,7 +96,8 @@ AC_SUBST(LIBS) AC_SUBST(SH_SUFFIX) AC_SUBST(LINKAGE) AC_SUBST(with_restconf) # Set to native or fcgi -> compile apps/restconf -AC_SUBST(with_http2,false) +AC_SUBST(HAVE_LIBEVHTP,false) # consider using neutral constant such as with-http1 +AC_SUBST(HAVE_LIBNGHTTP2,false) # consider using neutral constant such as with-http2 AC_SUBST(with_libxml2) AC_SUBST(enable_optyangs) # Home dir for web user, such as nginx fcgi sockets @@ -239,6 +240,7 @@ elif test "x${with_restconf}" == xnative; then ]]) AC_CHECK_LIB(event, event_init,, AC_MSG_ERROR([libevent missing])) AC_CHECK_LIB(evhtp, evhtp_new,, AC_MSG_ERROR([libevhtp missing]),[-levent -lssl -lcrypto]) + HAVE_LIBEVHTP=true fi # Check if nghttp2 is enabled for http/2 @@ -254,7 +256,7 @@ elif test "x${with_restconf}" == xnative; then if test "$ac_enable_nghttp2" = "yes"; then AC_CHECK_HEADERS(nghttp2/nghttp2.h,[], AC_MSG_ERROR([nghttp2 missing])) AC_CHECK_LIB(nghttp2, nghttp2_session_server_new,, AC_MSG_ERROR([nghttp2 missing])) - with_http2=true + HAVE_LIBNGHTTP2=true fi AC_DEFINE(WITH_RESTCONF_NATIVE, 1, [Use native restconf mode]) # For c-code that cant use strings elif test "x${with_restconf}" == xno; then diff --git a/test/config.sh.in b/test/config.sh.in index 1f288a3f..5ee28787 100755 --- a/test/config.sh.in +++ b/test/config.sh.in @@ -39,7 +39,8 @@ # --with-restconf=native Integration with embedded web server libevhtp WITH_RESTCONF=@with_restconf@ # native, fcgi or "" -WITH_HTTP2=@with_http2@ # true if nghttp2 is enabled, otherwise false +HAVE_LIBNGHTTP2=@HAVE_LIBNGHTTP2@ +HAVE_LIBEVHTP=@HAVE_LIBEVHTP@ # This is for libxml2 XSD regex engine # Note this only enables the compiling of the code. In order to actually diff --git a/test/lib.sh b/test/lib.sh index 08a4a83d..60ba56c8 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -100,14 +100,19 @@ DEFAULTHELLO=" $ftest +if ${HAVE_LIBNGHTTP2}; then + if ${HAVE_LIBEVHTP}; then + # Add 101 switch protocols for http 1->2 upgrade + echo "HTTP/1.1 101 Switching Protocols " > $ftest + echo "Upgrade: h2c " >> $ftest + echo " " >> $ftest + echo "HTTP/$HVER 200 " >> $ftest + else + echo "HTTP/$HVER 200 " > $ftest + fi else echo "HTTP/$HVER 200 OK " > $ftest fi @@ -157,6 +165,7 @@ echo " " >> $ftest ret=$(diff -i $ftest $foutput) if [ $? -ne 0 ]; then + echo "$ret" err1 "Matching running-db with $fconfigonly" fi diff --git a/test/test_restconf.sh b/test/test_restconf.sh index 6ba4b22d..99b227ec 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -51,7 +51,7 @@ if [ "${WITH_RESTCONF}" = "native" ]; then # Create server certs and CA cacerts $cakey $cacert servercerts $cakey $cacert $srvkey $srvcert - USEBACKEND=true + USEBACKEND=false else # Define default restconfig config: RESTCONFIG RESTCONFIG=$(restconf_config none false) @@ -169,9 +169,10 @@ function testrun() wait_restconf new "restconf root discovery. RFC 8040 3.1 (xml+xrd)" + echo "curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta" expectpart "$(curl $CURLOPTS -X GET $proto://$addr/.well-known/host-meta)" 0 "HTTP/$HVER 200" "" "" "" - if ! ${WITH_HTTP2}; then # http/2 + if ! ${HAVE_LIBNGHTTP2}; then # http/2 if [ "${WITH_RESTCONF}" = "native" ]; then # XXX does not work with nginx new "restconf GET http/1.0 - returns 1.0" @@ -244,7 +245,7 @@ function testrun() new "restconf HEAD. RFC 8040 4.2" expectpart "$(curl $CURLOPTS --head -H "Accept: application/yang-data+json" $proto://$addr/restconf/data)" 0 "HTTP/$HVER 200" "Content-Type: application/yang-data+json" - + new "restconf empty rpc JSON" expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" -d {\"clixon-example:input\":null} $proto://$addr/restconf/operations/clixon-example:empty)" 0 "HTTP/$HVER 204" diff --git a/test/test_restconf_err.sh b/test/test_restconf_err.sh index 4756093e..dadb9486 100755 --- a/test/test_restconf_err.sh +++ b/test/test_restconf_err.sh @@ -196,7 +196,7 @@ expectpart "$(curl $CURLOPTS -X GET -H 'Accept: application/yang-data+xml' $RCPR # But leave it here for debugging where netcat works properly # Alt try something like: # printf "Hello World!" | (exec 3<>/dev/tcp/127.0.0.1/80; cat >&3; cat <&3; exec 3<&-) -if [ false -a ! ${WITH_HTTP2} ] ; then +if [ false -a ! ${HAVE_LIBNGHTTP2} ] ; then # Look for netcat or nc for direct socket http calls if [ -n "$(type netcat 2> /dev/null)" ]; then netcat="netcat -w 1" # -N works on evhtp but not fcgi diff --git a/test/test_yang_anydata.sh b/test/test_yang_anydata.sh index a2342341..2f2f29ca 100755 --- a/test/test_yang_anydata.sh +++ b/test/test_yang_anydata.sh @@ -174,10 +174,9 @@ EOF new "start backend -s init -f $cfg -- -sS $fstate" start_backend -s init -f $cfg -- -sS $fstate fi - - new "waiting" - wait_backend fi + new "wait backend" + wait_backend if [ $RC -ne 0 ]; then new "kill old restconf daemon" @@ -216,8 +215,8 @@ EOF new "restconf get config" expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=config)" 0 "HTTP/$HVER 200" "$XML" - - # Save partial state in state file with unknown removed (positive test) + + new "Save partial state with unknowns removed in state file $fstate" echo "$STATE1" > $fstate new "Get state (positive test)" @@ -226,7 +225,7 @@ EOF new "restconf get state(positive)" expectpart "$(curl $CURLOPTS -X GET -H "Accept: application/yang-data+xml" $RCPROTO://localhost/restconf/data?content=nonconfig)" 0 "HTTP/$HVER 200" "$STATE1" - # full state with unknowns + new "Save full state with unknowns in state file $fstate" echo "$STATE0" > $fstate new "Get state (negative test)"