diff --git a/apps/restconf/restconf_api_native.c b/apps/restconf/restconf_api_native.c index 13c3cd30..a05c3550 100644 --- a/apps/restconf/restconf_api_native.c +++ b/apps/restconf/restconf_api_native.c @@ -66,12 +66,13 @@ /*! Add HTTP header field name and value to reply * + * Generic code, add to restconf/native struct. Specific http/1 or /2 code actually sends * @param[in] req request handle * @param[in] name HTTP header field name * @param[in] vfmt HTTP header field value format string w variable parameter * @retval 0 OK * @retval -1 Error - * @see eg RFC 7230 + * @see restconf_submit_response http/2 actual send function */ int restconf_reply_header(void *req0, @@ -86,7 +87,6 @@ restconf_reply_header(void *req0, char *value = NULL; va_list ap; - clixon_debug(CLIXON_DBG_RESTCONF, "%s", name); if (sd == NULL || name == NULL || vfmt == NULL){ clixon_err(OE_CFG, EINVAL, "sd, name or value is NULL"); goto done; @@ -95,6 +95,17 @@ restconf_reply_header(void *req0, clixon_err(OE_CFG, EINVAL, "rc is NULL"); goto done; } + /* + * If HTTP/2, filter some headers + * The following header fields must not appear: "Connection", "Keep-Alive", "Proxy-Connection", + * "Transfer-Encoding" and "Upgrade". + */ + if (rc->rc_proto == HTTP_2){ // NO http/2 + if (strcmp(name, "Connection") == 0){ + clixon_debug(CLIXON_DBG_RESTCONF, "Skip: %s: %s", name, value); + goto ok; + } + } /* First round: compute vlen and allocate value */ va_start(ap, vfmt); vlen = vsnprintf(NULL, 0, vfmt, ap); @@ -112,10 +123,12 @@ restconf_reply_header(void *req0, goto done; } va_end(ap); + clixon_debug(CLIXON_DBG_RESTCONF, "%s: %s", name, value); if (cvec_add_string(sd->sd_outp_hdrs, (char*)name, value) < 0){ clixon_err(OE_RESTCONF, errno, "cvec_add_string"); goto done; } + ok: retval = 0; done: if (value) @@ -123,8 +136,9 @@ restconf_reply_header(void *req0, return retval; } -/*! Send HTTP reply with potential message body +/*! Assign values to HTTP reply with potential message body * + * Generic code, add to restconf/native struct. Specific http/1 or /2 code actually sends * @param[in] req http request handle * @param[in] code Status code * @param[in] cb Body as a cbuf if non-NULL. Note: is consumed diff --git a/apps/restconf/restconf_native.c b/apps/restconf/restconf_native.c index 89ca5d2c..f4db752a 100644 --- a/apps/restconf/restconf_native.c +++ b/apps/restconf/restconf_native.c @@ -387,7 +387,7 @@ restconf_connection_sanity(clixon_handle h, /* Write buf to socket * - * see also this function in restconf_api_openssl.c + * Only (at least mostly?) for HTTP/1 * @param[in] h Clixon handle * @param[in] buf Buffer to write * @param[in] buflen Length of buffer @@ -419,7 +419,7 @@ native_buf_write(clixon_handle h, * 1. they are not "strings" in the sense they are not NULL-terminated * 2. they are often very long */ - if (clixon_debug_get()) { + if ((clixon_debug_get() & CLIXON_DBG_RESTCONF) != 0) { char *dbgstr = NULL; size_t sz; sz = buflen>256?256:buflen; /* Truncate to 256 */ @@ -1071,7 +1071,7 @@ restconf_connection_close1(restconf_conn *rc) goto done; } rsock = rc->rc_socket; - clixon_debug(CLIXON_DBG_RESTCONF, "\"%s\"", rsock->rs_description); + clixon_debug(CLIXON_DBG_RESTCONF, "%s", rsock->rs_description?rsock->rs_description:""); if (close(rc->rc_s) < 0){ clixon_err(OE_UNIX, errno, "close"); goto done; @@ -1390,34 +1390,6 @@ restconf_ssl_accept_client(clixon_handle h, } clixon_debug(CLIXON_DBG_RESTCONF, "proto:%s", restconf_proto2str(proto)); -#if 0 /* Seems too early to fail here, instead let authentication callback deal with this */ - /* For client-cert authentication, check if any certs are present, - * if not, send bad request - * Alt: set SSL_CTX_set_verify(ctx, SSL_VERIFY_FAIL_IF_NO_PEER_CERT) - * but then SSL_accept fails. - */ - if (restconf_auth_type_get(h) == CLIXON_AUTH_CLIENT_CERTIFICATE){ - X509 *peercert; -#if OPENSSL_VERSION_NUMBER < 0x30000000L - if ((peercert = SSL_get_peer_certificate(rc->rc_ssl)) != NULL){ - X509_free(peercert); - } -#else - if ((peercert = SSL_get1_peer_certificate(rc->rc_ssl)) != NULL){ - X509_free(peercert); - } -#endif - else { /* Get certificates (if available) */ - if (proto != HTTP_2 && - native_send_badrequest(h, rc->rc_s, rc->rc_ssl, "application/yang-data+xml", - "protocolmalformed-messagePeer certificate required", rc->rc_socket, rc) < 0) - goto done; - if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0) - goto done; - goto closed; - } - } -#endif /* Get the actual peer, XXX this maybe could be done in ca-auth client-cert code ? * Note this _only_ works if SSL_set1_host() was set previously,... */ diff --git a/apps/restconf/restconf_nghttp2.c b/apps/restconf/restconf_nghttp2.c index b0ee3211..4417ed18 100644 --- a/apps/restconf/restconf_nghttp2.c +++ b/apps/restconf/restconf_nghttp2.c @@ -32,6 +32,7 @@ ***** END LICENSE BLOCK ***** + * @see RFC9113 * nghttp2 callback mechanism * * nghttp2_session_mem_recv() @@ -112,9 +113,11 @@ static const map_str2int nghttp2_frame_type_map[] = { }; /* 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 + * + * @param[in] handle Application-specific handle + * @param[in] suberr Application-specific handle + * @param[out] cb Read log/error string into this buffer + * @retval 0 OK */ int clixon_nghttp2_log_cb(void *handle, @@ -136,11 +139,11 @@ nghttp2_print_header(const uint8_t *name, clixon_debug(CLIXON_DBG_RESTCONF, "%s %s", name, value); } -/*! Print HTTP headers to |f|. +/*! 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. + * octets, therefore they may contain non-printable characters. */ static void nghttp2_print_headers(nghttp2_nv *nva, @@ -163,6 +166,8 @@ nghttp2_print_headers(nghttp2_nv *nva, * If it cannot send any single byte without blocking, * it must return :enum:`NGHTTP2_ERR_WOULDBLOCK`. * For other errors, it must return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. + * @param[in] session Nghttp2 session struct + * @param[in] user_data User data, in effect Restconf connection */ static ssize_t session_send_callback(nghttp2_session *session, @@ -261,6 +266,9 @@ session_send_callback(nghttp2_session *session, } /*! Invoked when |session| wants to receive data from the remote peer. + * + * @param[in] session Nghttp2 session struct + * @param[in] user_data User data, in effect Restconf connection */ static ssize_t recv_callback(nghttp2_session *session, @@ -278,7 +286,7 @@ recv_callback(nghttp2_session *session, * * This are all messages except /.well-known, * - * @param[in] sd session stream struct (http/1 has a single) + * @param[in] sd Restconf native stream struct * @retval void * Discussion: problematic if fatal error -1 is returned from clixon routines * without actually terminating. Consider: @@ -360,11 +368,27 @@ restconf_nghttp2_path(restconf_stream_data *sd) return retval; /* void */ } -/*! data callback, just pass pointer to cbuf +/*! Data callback, just pass pointer to cbuf * - * XXX handle several chunks with cbuf + * @param[in] session Nghttp2 session struct + * @param[in] stream_id Nghttp2 stream id + * @param[in] buf + * @param[in] length + * @param[in] data_flags + * @param[in] source + * @param[in] user_data User data, in effect Restconf stream data + * @retval 0 OK + * @retval -1 Error + * + * If the application wants to postpone DATA frames (e.g., + * asynchronous I/O, or reading data blocks for long time), it is + * achieved by returning :enum:`nghttp2_error.NGHTTP2_ERR_DEFERRED` + * without reading any data in this invocation. The library removes + * DATA frame from the outgoing queue temporarily. To move back + * deferred DATA frame to outgoing queue, call + * `nghttp2_session_resume_data()`. */ -static ssize_t +ssize_t restconf_sd_read(nghttp2_session *session, int32_t stream_id, uint8_t *buf, @@ -374,27 +398,19 @@ restconf_sd_read(nghttp2_session *session, void *user_data) { restconf_stream_data *sd = (restconf_stream_data *)source->ptr; + restconf_conn *rc = sd->sd_conn; cbuf *cb; size_t len = 0; size_t remain; + clixon_debug(CLIXON_DBG_RESTCONF, ""); if ((cb = sd->sd_body) == NULL){ /* shouldnt happen */ + if (rc->rc_event_stream && rc->rc_exit == 0) { + return NGHTTP2_ERR_DEFERRED; + } *data_flags |= NGHTTP2_DATA_FLAG_EOF; return 0; } -#if 0 - if (cbuf_len(cb) <= length){ - len = remain; - *data_flags |= NGHTTP2_DATA_FLAG_EOF; - } - else{ - len = length; - } - memcpy(buf, cbuf_get(cb) + sd->sd_body_offset, len); - *data_flags |= NGHTTP2_DATA_FLAG_EOF; - return len; -#endif - assert(cbuf_len(cb) > sd->sd_body_offset); remain = cbuf_len(cb) - sd->sd_body_offset; clixon_debug(CLIXON_DBG_RESTCONF, "length:%zu totlen:%zu, offset:%zu remain:%zu", length, @@ -415,6 +431,18 @@ restconf_sd_read(nghttp2_session *session, return len; } +/*! Create nghttp2 response message + * + * Create nghttp2 name/value pairs from previously assigned sd_outp_hdrs + * Call nghttp2 submit response function + * @param[in] session Nghttp2 session struct + * @param[in] rc Restconf connection + * @param[in] stream_id Nghttp2 stream id + * @param[in] sd Restconf native stream struct + * @retval 0 OK + * @retval -1 Error + * @see restconf_reply_header + */ static int restconf_submit_response(nghttp2_session *session, restconf_conn *rc, @@ -439,28 +467,41 @@ restconf_submit_response(nghttp2_session *session, hdr = &hdrs[i++]; hdr->name = (uint8_t*)":status"; snprintf(valstr, 15, "%u", sd->sd_code); - clixon_debug(CLIXON_DBG_RESTCONF, "status %d", sd->sd_code); + clixon_debug(CLIXON_DBG_RESTCONF, "Status: %d", sd->sd_code); hdr->value = (uint8_t*)valstr; hdr->namelen = strlen(":status"); hdr->valuelen = strlen(valstr); hdr->flags = 0; - cv = NULL; while ((cv = cvec_each(sd->sd_outp_hdrs, cv)) != NULL){ hdr = &hdrs[i++]; hdr->name = (uint8_t*)cv_name_get(cv); - clixon_debug(CLIXON_DBG_RESTCONF, "hdr: %s", hdr->name); hdr->value = (uint8_t*)cv_string_get(cv); hdr->namelen = strlen(cv_name_get(cv)); hdr->valuelen = strlen(cv_string_get(cv)); + clixon_debug(CLIXON_DBG_RESTCONF, "%s: %s", hdr->name, hdr->value); hdr->flags = 0; } - if ((ngerr = nghttp2_submit_response(session, - stream_id, - hdrs, i, - (data_prd.source.ptr != NULL)?&data_prd:NULL)) < 0){ - clixon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_response"); - goto done; + if (rc->rc_event_stream){ + if ((ngerr = nghttp2_submit_headers(session, + 0, // flags + stream_id, + NULL, // pri_spec + hdrs, i, + NULL // stream_user_data + )) < 0){ + clixon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_response"); + goto done; + } + } + else { + if ((ngerr = nghttp2_submit_response(session, + stream_id, + hdrs, i, + (data_prd.source.ptr != NULL)?&data_prd:NULL)) < 0){ + clixon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_response"); + goto done; + } } retval = 0; done: @@ -471,12 +512,19 @@ restconf_submit_response(nghttp2_session *session, } /*! Simulate a received request in an upgrade scenario by talking the http/1 parameters + * + * @param[in] rc Restconf connection + * @param[in] sd Restconf native stream struct + * @param[in] session Nghttp2 session struct + * @param[in] stream_id Nghttp2 stream id + * @retval 0 OK + * @retval -1 Error */ int http2_exec(restconf_conn *rc, restconf_stream_data *sd, - nghttp2_session *session, - int32_t stream_id) + nghttp2_session *session, + int32_t stream_id) { int retval = -1; @@ -530,6 +578,12 @@ http2_exec(restconf_conn *rc, } /*! A frame is received + * + * @param[in] session Nghttp2 session struct + * @param[in] frame Nghttp2 frame + * @param[in] user_data User-data, in effect Restconf connection + * @retval 0 OK + * @retval -1 Error */ static int on_frame_recv_callback(nghttp2_session *session, @@ -575,12 +629,16 @@ on_frame_recv_callback(nghttp2_session *session, } /*! An invalid non-DATA frame is received. + * + * @param[in] session Nghttp2 session struct + * @param[in] frame Nghttp2 frame + * @param[in] user_data User data, in effect Restconf connection */ static int -on_invalid_frame_recv_callback(nghttp2_session *session, +on_invalid_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, - int lib_error_code, - void *user_data) + int lib_error_code, + void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clixon_debug(CLIXON_DBG_RESTCONF, ""); @@ -593,6 +651,8 @@ on_invalid_frame_recv_callback(nghttp2_session *session, * necessarily mean this chunk of data is the last one in the stream. * You should use :type:`nghttp2_on_frame_recv_callback` to know all * data frames are received. + * @param[in] session Nghttp2 session struct + * @param[in] user_data User data, in effect Restconf connection */ static int on_data_chunk_recv_callback(nghttp2_session *session, @@ -613,6 +673,10 @@ on_data_chunk_recv_callback(nghttp2_session *session, } /*! Just before the non-DATA frame |frame| is sent + * + * @param[in] session Nghttp2 session struct + * @param[in] frame Nghttp2 frame + * @param[in] user_data User data, in effect Restconf connection */ static int before_frame_send_callback(nghttp2_session *session, @@ -625,6 +689,10 @@ before_frame_send_callback(nghttp2_session *session, } /*! After the frame |frame| is sent + * + * @param[in] session Nghttp2 session struct + * @param[in] frame Nghttp2 frame + * @param[in] user_data User data, in effect Restconf connection */ static int on_frame_send_callback(nghttp2_session *session, @@ -637,12 +705,16 @@ on_frame_send_callback(nghttp2_session *session, } /*! After the non-DATA frame |frame| is not sent because of error + * + * @param[in] session Nghttp2 session struct + * @param[in] frame Nghttp2 frame + * @param[in] user_data User data, in effect Restconf connection */ static int -on_frame_not_send_callback(nghttp2_session *session, +on_frame_not_send_callback(nghttp2_session *session, const nghttp2_frame *frame, - int lib_error_code, - void *user_data) + int lib_error_code, + void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clixon_debug(CLIXON_DBG_RESTCONF, ""); @@ -650,6 +722,9 @@ on_frame_not_send_callback(nghttp2_session *session, } /*! Stream |stream_id| is closed. + * + * @param[in] session Nghttp2 session struct + * @param[in] user_data User data, in effect Restconf connection */ static int on_stream_close_callback(nghttp2_session *session, @@ -670,13 +745,17 @@ on_stream_close_callback(nghttp2_session *session, } /*! Reception of header block in HEADERS or PUSH_PROMISE is started. + * + * @param[in] session Nghttp2 session struct + * @param[in] frame Nghttp2 frame + * @param[in] user_data User data, in effect Restconf connection */ static int on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { - restconf_conn *rc = (restconf_conn *)user_data; + restconf_conn *rc = (restconf_conn *)user_data; restconf_stream_data *sd; clixon_debug(CLIXON_DBG_RESTCONF, "%s", clicon_int2str(nghttp2_frame_type_map, frame->hd.type)); @@ -730,6 +809,9 @@ nghttp2_hdr2clixon(clixon_handle h, * 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. + * @param[in] session Nghttp2 session struct + * @param[in] frame Nghttp2 frame + * @param[in] user_data User data, in effect Restconf connection */ static int on_header_callback(nghttp2_session *session, @@ -760,46 +842,18 @@ on_header_callback(nghttp2_session *session, return retval; } -#ifdef NOTUSED -/*! How many padding bytes are required for the transmission of the |frame|? - */ -static ssize_t -select_padding_callback(nghttp2_session *session, - const nghttp2_frame *frame, - size_t max_payloadlen, - void *user_data) -{ - // restconf_conn *rc = (restconf_conn *)user_data; - clixon_debug(CLIXON_DBG_RESTCONF, ""); - return frame->hd.length; -} - -/*! Get max length of data to send data to the remote peer - */ -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 *rc = (restconf_conn *)user_data; - clixon_debug(CLIXON_DBG_RESTCONF, ""); - return 0; -} -#endif /* NOTUSED */ - /*! 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. + * @param[in] session Nghttp2 session struct + * @param[in] hd Nghttp2 frame header + * @param[in] user_data User data, in effect Restconf connection */ static int -on_begin_frame_callback(nghttp2_session *session, +on_begin_frame_callback(nghttp2_session *session, const nghttp2_frame_hd *hd, - void *user_data) + void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clixon_debug(CLIXON_DBG_RESTCONF, "%s", clicon_int2str(nghttp2_frame_type_map, hd->type)); @@ -813,55 +867,35 @@ on_begin_frame_callback(nghttp2_session *session, * Callback function invoked when :enum:`NGHTTP2_DATA_FLAG_NO_COPY` is * used in :type:`nghttp2_data_source_read_callback` to send complete * DATA frame. + * @param[in] session Nghttp2 session struct + * @param[in] frame Nghttp2 frame + * @param[in] user_data User data, in effect Restconf connection */ static int -send_data_callback(nghttp2_session *session, - nghttp2_frame *frame, - const uint8_t *framehd, size_t length, +send_data_callback(nghttp2_session *session, + nghttp2_frame *frame, + const uint8_t *framehd, + size_t length, nghttp2_data_source *source, - void *user_data) + void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clixon_debug(CLIXON_DBG_RESTCONF, ""); return 0; } -#ifdef NOTUSED -/*! Pack extension payload in its wire format - */ -static ssize_t -pack_extension_callback(nghttp2_session *session, - uint8_t *buf, size_t len, - const nghttp2_frame *frame, - void *user_data) -{ - // restconf_conn *rc = (restconf_conn *)user_data; - clixon_debug(CLIXON_DBG_RESTCONF, ""); - return 0; -} - -/*! Unpack extension payload from its wire format. - */ -static int -unpack_extension_callback(nghttp2_session *session, - void **payload, - const nghttp2_frame_hd *hd, - void *user_data) -{ - // restconf_conn *rc = (restconf_conn *)user_data; - clixon_debug(CLIXON_DBG_RESTCONF, ""); - return 0; -} -#endif /* NOTUSED */ - /*! Chunk of extension frame payload is received + * + * @param[in] session Nghttp2 session struct + * @param[in] hd Nghttp2 frame header + * @param[in] user_data User data, in effect Restconf connection */ static int -on_extension_chunk_recv_callback(nghttp2_session *session, +on_extension_chunk_recv_callback(nghttp2_session *session, const nghttp2_frame_hd *hd, - const uint8_t *data, - size_t len, - void *user_data) + const uint8_t *data, + size_t len, + void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clixon_debug(CLIXON_DBG_RESTCONF, ""); @@ -869,12 +903,15 @@ on_extension_chunk_recv_callback(nghttp2_session *session, } /*! Library provides the error code, and message for debugging purpose. + * + * @param[in] session Nghttp2 session struct + * @param[in] user_data User data, in effect Restconf connection */ static int error_callback(nghttp2_session *session, - const char *msg, - size_t len, - void *user_data) + const char *msg, + size_t len, + void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clixon_debug(CLIXON_DBG_RESTCONF, ""); @@ -883,13 +920,16 @@ error_callback(nghttp2_session *session, #if (NGHTTP2_VERSION_NUM > 0x011201) /* Unsure of version number */ /*! Library provides the error code, and message for debugging purpose. + * + * @param[in] session Nghttp2 session struct + * @param[in] user_data User data, in effect Restconf connection */ static int error_callback2(nghttp2_session *session, - int lib_error_code, - const char *msg, - size_t len, - void *user_data) + int lib_error_code, + const char *msg, + size_t len, + void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clixon_debug(CLIXON_DBG_RESTCONF, ""); @@ -988,6 +1028,7 @@ http2_send_server_connection(restconf_conn *rc) } /*! Initialize callbacks + * */ int http2_session_init(restconf_conn *rc) @@ -1009,18 +1050,10 @@ http2_session_init(restconf_conn *rc) 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); -#ifdef NOTUSED - 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); -#endif nghttp2_session_callbacks_set_on_begin_frame_callback(callbacks, on_begin_frame_callback); nghttp2_session_callbacks_set_send_data_callback(callbacks, send_data_callback); -#ifdef NOTUSED - nghttp2_session_callbacks_set_pack_extension_callback(callbacks, pack_extension_callback); - nghttp2_session_callbacks_set_unpack_extension_callback(callbacks, unpack_extension_callback); -#endif nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(callbacks, on_extension_chunk_recv_callback); nghttp2_session_callbacks_set_error_callback(callbacks, error_callback); #if (NGHTTP2_VERSION_NUM > 0x011201) /* Unsure of version number */ diff --git a/apps/restconf/restconf_nghttp2.h b/apps/restconf/restconf_nghttp2.h index 8b7343ef..98d432fb 100644 --- a/apps/restconf/restconf_nghttp2.h +++ b/apps/restconf/restconf_nghttp2.h @@ -33,6 +33,7 @@ ***** END LICENSE BLOCK ***** * * Virtual clixon restconf API functions. + * @see RFC9113 */ #ifndef _RESTCONF_NGHTTP2_H_ @@ -42,6 +43,7 @@ * Prototypes */ int clixon_nghttp2_log_cb(void *handle, int suberr, cbuf *cb); +ssize_t restconf_sd_read(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data); 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); diff --git a/apps/restconf/restconf_root.c b/apps/restconf/restconf_root.c index 13ca2e4e..03741e1b 100644 --- a/apps/restconf/restconf_root.c +++ b/apps/restconf/restconf_root.c @@ -398,8 +398,8 @@ api_data(clixon_handle h, goto done; retval = api_return_err0(h, req, xerr, pretty, media_out, 0); } - clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval); done: + clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval); if (xerr) xml_free(xerr); return retval; diff --git a/apps/restconf/restconf_stream.c b/apps/restconf/restconf_stream.c index 255e3397..e7265cdd 100644 --- a/apps/restconf/restconf_stream.c +++ b/apps/restconf/restconf_stream.c @@ -119,24 +119,24 @@ api_path_is_stream(clixon_handle h) /*! Send subscription to backend * - * @param[in] h Clixon handle - * @param[in] req Generic Www handle (can be part of clixon handle) - * @param[in] name Stream name + * @param[in] h Clixon handle + * @param[in] req Generic Www handle (can be part of clixon handle) + * @param[in] name Stream name * @param[in] qvec * @param[in] pretty Pretty-print json/xml reply * @param[in] media_out Restconf output media - * @param[out] sp Socket -1 if not set + * @param[out] sp Socket -1 if not set (only fcgi) * @retval 0 OK * @retval -1 Error */ int -restconf_subscription(clixon_handle h, - void *req, - char *name, - cvec *qvec, - int pretty, +restconf_subscription(clixon_handle h, + void *req, + char *name, + cvec *qvec, + int pretty, restconf_media media_out, - int *sp) + int *sp) { int retval = -1; cxobj *xret = NULL; @@ -179,14 +179,15 @@ restconf_subscription(clixon_handle h, goto ok; } /* Setting up stream */ - if (restconf_reply_header(req, "Server", "clixon") < 0) - goto done; if (restconf_reply_header(req, "Server", "clixon") < 0) goto done; if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0) goto done; if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) goto done; + /* HTTP/2 does not use the Connection header field RFC 9113 + * filtered in restconf_reply_header + */ if (restconf_reply_header(req, "Connection", "keep-alive") < 0) goto done; /* Must be there for FCGI caching */ diff --git a/apps/restconf/restconf_stream_native.c b/apps/restconf/restconf_stream_native.c index 3f739f7b..0cc3fc4a 100644 --- a/apps/restconf/restconf_stream_native.c +++ b/apps/restconf/restconf_stream_native.c @@ -55,7 +55,6 @@ #include #include #include -#include #include #include #include @@ -86,6 +85,39 @@ #include "restconf_native.h" /* Restconf-openssl mode specific headers*/ #include "restconf_stream.h" +#ifdef HAVE_LIBNGHTTP2 +#include "restconf_nghttp2.h" + +static int +restconf_http2_send_notification(clixon_handle h, + restconf_stream_data *sd, + restconf_conn *rc) + +{ + int retval = -1; + nghttp2_session *session; + nghttp2_error ngerr; + nghttp2_data_provider data_prd; + int32_t stream_id; + + data_prd.source.ptr = sd; + data_prd.read_callback = restconf_sd_read; + session = rc->rc_ngsession; + stream_id = 1; + if ((ngerr = nghttp2_submit_data(session, + 0, // flags + stream_id, + (data_prd.source.ptr != NULL)?&data_prd:NULL + )) < 0){ + clixon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_response"); + goto done; + } + retval = 0; + done: + return retval; +} +#endif // NGHTTP2 + /*! Callback when stream notifications arrive from backend * * @param[in] s Socket @@ -98,18 +130,21 @@ static int stream_native_backend_cb(int s, void *arg) { - int retval = -1; + int retval = -1; restconf_stream_data *sd = (restconf_stream_data *)arg; - int eof; - cxobj *xtop = NULL; /* top xml */ - cxobj *xn; /* notification xml */ - cbuf *cbx = NULL; - cbuf *cb = NULL; - cbuf *cbmsg = NULL; - int pretty = 0; - int ret; - restconf_conn *rc = sd->sd_conn; - clixon_handle h = rc->rc_h; + int eof; + cxobj *xtop = NULL; /* top xml */ + cxobj *xn; /* notification xml */ + cbuf *cbx = NULL; + cbuf *cb = NULL; + cbuf *cbmsg = NULL; + int pretty = 0; + int ret; + restconf_conn *rc = sd->sd_conn; + clixon_handle h = rc->rc_h; +#ifdef HAVE_LIBNGHTTP2 + nghttp2_error ngerr; +#endif clixon_debug(CLIXON_DBG_STREAM|CLIXON_DBG_DETAIL, ""); pretty = restconf_pretty_get(h); @@ -139,31 +174,28 @@ stream_native_backend_cb(int s, } if ((xn = xpath_first(xtop, NULL, "notification")) == NULL) goto ok; -#if 0 // Cant get CHUNKED to work - { - size_t len; - cprintf(cbx, "data: "); - if (clixon_xml2cbuf(cbx, xn, 0, pretty, NULL, -1, 0) < 0) - goto done; - len = cbuf_len(cbx); - len +=2; - cprintf(cb, "%x", (int16_t)len&0xffff); - cprintf(cb, "\r\n"); - cprintf(cb, "%s", cbuf_get(cbx)); - cprintf(cb, "\r\n"); - cprintf(cb, "\r\n"); - cprintf(cb, "0\r\n"); - cprintf(cb, "\r\n"); - // XXX This terminates stream, but want it to continue / hang - } -#else cprintf(cb, "data: "); if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0) goto done; cprintf(cb, "\r\n"); cprintf(cb, "\r\n"); -#endif - if ((ret = native_buf_write(h, cbuf_get(cb), cbuf_len(cb), rc, "native stream")) < 0) +#ifdef HAVE_LIBNGHTTP2 + if (rc->rc_proto == HTTP_2){ + if (restconf_reply_send(sd, 200, cb, 0) < 0) + goto done; + cb = NULL; + if (restconf_http2_send_notification(h, sd, rc) < 0) + goto done; + if ((ngerr = nghttp2_session_send(rc->rc_ngsession)) != 0) + goto done; + if (sd->sd_body){ + cbuf_free(sd->sd_body); + sd->sd_body = NULL; + } + } + else +#endif // HAVE_LIBNGHTTP2 + if ((ret = native_buf_write(h, cbuf_get(cb), cbuf_len(cb), rc, "native stream")) < 0) goto done; ok: retval = 0; @@ -181,15 +213,39 @@ stream_native_backend_cb(int s, } /*! Timeout of notification stream, limit lifetime, for debug + * + * XXX HTTP/2 not closed cleanly */ static int stream_timeout_end(int s, void *arg) { + int retval = -1; restconf_conn *rc = (restconf_conn *)arg; clixon_debug(CLIXON_DBG_STREAM, ""); - return restconf_close_ssl_socket(rc, __FUNCTION__, 0); + rc->rc_exit = 1; +#if 0 // Termination is not clean + { + int ngerr; + int ret; + if ((ngerr = nghttp2_session_terminate_session(rc->rc_ngsession, 0)) < 0){ + clixon_err(OE_NGHTTP2, ngerr, "nghttp2_session_terminate_session %d", ngerr); + goto done; // XXX not here in original? + } + if ((ngerr = nghttp2_session_send(rc->rc_ngsession)) != 0){ + clixon_err(OE_NGHTTP2, ngerr, "nghttp2_session_send %d", ngerr); + goto done; // XXX not here in original? + } + ret = SSL_pending(rc->rc_ssl); + clixon_debug(CLIXON_DBG_STREAM, "SSL pending: %d", ret); + } +#endif + if (restconf_close_ssl_socket(rc, __FUNCTION__, 0) < 0) + goto done; + retval = 0; + done: + return retval; } /*! Close notification stream @@ -203,6 +259,7 @@ stream_close(clixon_handle h, { restconf_conn *rc = (restconf_conn *)req; + clixon_debug(CLIXON_DBG_STREAM, ""); clicon_rpc_close_session(h); clixon_event_unreg_fd(rc->rc_event_stream, stream_native_backend_cb); clixon_event_unreg_timeout(stream_timeout_end, req); diff --git a/test/test_restconf_notifications.sh b/test/test_restconf_notifications.sh index bed37706..3748e734 100755 --- a/test/test_restconf_notifications.sh +++ b/test/test_restconf_notifications.sh @@ -35,7 +35,6 @@ fi : ${SLEEP2:=1} SLEEP5=.5 APPNAME=example -: ${clixon_util_stream:=clixon_util_stream} : ${TIMEOUT:=10} : ${PERIOD:=2} @@ -231,13 +230,14 @@ if [ "${WITH_RESTCONF}" = "fcgi" ]; then runtest "" fi if [ "${WITH_RESTCONF}" = "native" ]; then - if false; then # XXX native + http/2 dont work yet -# if ${HAVE_LIBNGHTTP2}; then + if ${HAVE_LIBNGHTTP2}; then runtest --http2 fi fi if false; then # NYI +: ${clixon_util_stream:=clixon_util_stream} + # 2c new "2c) start sub 8s - replay from start -8s - expect 3-4 notifications" ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 10 -s -8)