RESTCONF notification for HTTP/2 native mode

This commit is contained in:
Olof hagsand 2024-06-06 12:57:46 +02:00
parent 3579d98243
commit 25e1bade8f
8 changed files with 286 additions and 207 deletions

View file

@ -66,12 +66,13 @@
/*! Add HTTP header field name and value to reply /*! 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] req request handle
* @param[in] name HTTP header field name * @param[in] name HTTP header field name
* @param[in] vfmt HTTP header field value format string w variable parameter * @param[in] vfmt HTTP header field value format string w variable parameter
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error
* @see eg RFC 7230 * @see restconf_submit_response http/2 actual send function
*/ */
int int
restconf_reply_header(void *req0, restconf_reply_header(void *req0,
@ -86,7 +87,6 @@ restconf_reply_header(void *req0,
char *value = NULL; char *value = NULL;
va_list ap; va_list ap;
clixon_debug(CLIXON_DBG_RESTCONF, "%s", name);
if (sd == NULL || name == NULL || vfmt == NULL){ if (sd == NULL || name == NULL || vfmt == NULL){
clixon_err(OE_CFG, EINVAL, "sd, name or value is NULL"); clixon_err(OE_CFG, EINVAL, "sd, name or value is NULL");
goto done; goto done;
@ -95,6 +95,17 @@ restconf_reply_header(void *req0,
clixon_err(OE_CFG, EINVAL, "rc is NULL"); clixon_err(OE_CFG, EINVAL, "rc is NULL");
goto done; 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 */ /* First round: compute vlen and allocate value */
va_start(ap, vfmt); va_start(ap, vfmt);
vlen = vsnprintf(NULL, 0, vfmt, ap); vlen = vsnprintf(NULL, 0, vfmt, ap);
@ -112,10 +123,12 @@ restconf_reply_header(void *req0,
goto done; goto done;
} }
va_end(ap); va_end(ap);
clixon_debug(CLIXON_DBG_RESTCONF, "%s: %s", name, value);
if (cvec_add_string(sd->sd_outp_hdrs, (char*)name, value) < 0){ if (cvec_add_string(sd->sd_outp_hdrs, (char*)name, value) < 0){
clixon_err(OE_RESTCONF, errno, "cvec_add_string"); clixon_err(OE_RESTCONF, errno, "cvec_add_string");
goto done; goto done;
} }
ok:
retval = 0; retval = 0;
done: done:
if (value) if (value)
@ -123,8 +136,9 @@ restconf_reply_header(void *req0,
return retval; 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] req http request handle
* @param[in] code Status code * @param[in] code Status code
* @param[in] cb Body as a cbuf if non-NULL. Note: is consumed * @param[in] cb Body as a cbuf if non-NULL. Note: is consumed

View file

@ -387,7 +387,7 @@ restconf_connection_sanity(clixon_handle h,
/* Write buf to socket /* 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] h Clixon handle
* @param[in] buf Buffer to write * @param[in] buf Buffer to write
* @param[in] buflen Length of buffer * @param[in] buflen Length of buffer
@ -419,7 +419,7 @@ native_buf_write(clixon_handle h,
* 1. they are not "strings" in the sense they are not NULL-terminated * 1. they are not "strings" in the sense they are not NULL-terminated
* 2. they are often very long * 2. they are often very long
*/ */
if (clixon_debug_get()) { if ((clixon_debug_get() & CLIXON_DBG_RESTCONF) != 0) {
char *dbgstr = NULL; char *dbgstr = NULL;
size_t sz; size_t sz;
sz = buflen>256?256:buflen; /* Truncate to 256 */ sz = buflen>256?256:buflen; /* Truncate to 256 */
@ -1071,7 +1071,7 @@ restconf_connection_close1(restconf_conn *rc)
goto done; goto done;
} }
rsock = rc->rc_socket; 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){ if (close(rc->rc_s) < 0){
clixon_err(OE_UNIX, errno, "close"); clixon_err(OE_UNIX, errno, "close");
goto done; goto done;
@ -1390,34 +1390,6 @@ restconf_ssl_accept_client(clixon_handle h,
} }
clixon_debug(CLIXON_DBG_RESTCONF, "proto:%s", restconf_proto2str(proto)); 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",
"<errors xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"><error><error-type>protocol</error-type><error-tag>malformed-message</error-tag><error-message>Peer certificate required</error-message></error></errors>", 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 ? /* 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,... * Note this _only_ works if SSL_set1_host() was set previously,...
*/ */

View file

@ -32,6 +32,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* @see RFC9113
* nghttp2 callback mechanism * nghttp2 callback mechanism
* *
* nghttp2_session_mem_recv() * nghttp2_session_mem_recv()
@ -112,9 +113,11 @@ static const map_str2int nghttp2_frame_type_map[] = {
}; };
/* Clixon error category specialized log callback for nghttp2 /* Clixon error category specialized log callback for nghttp2
*
* @param[in] handle Application-specific handle * @param[in] handle Application-specific handle
* @param[in] suberr Application-specific handle * @param[in] suberr Application-specific handle
* @param[out] cb Read log/error string into this buffer * @param[out] cb Read log/error string into this buffer
* @retval 0 OK
*/ */
int int
clixon_nghttp2_log_cb(void *handle, clixon_nghttp2_log_cb(void *handle,
@ -163,6 +166,8 @@ nghttp2_print_headers(nghttp2_nv *nva,
* If it cannot send any single byte without blocking, * If it cannot send any single byte without blocking,
* it must return :enum:`NGHTTP2_ERR_WOULDBLOCK`. * it must return :enum:`NGHTTP2_ERR_WOULDBLOCK`.
* For other errors, it must return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. * 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 static ssize_t
session_send_callback(nghttp2_session *session, 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. /*! 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 static ssize_t
recv_callback(nghttp2_session *session, recv_callback(nghttp2_session *session,
@ -278,7 +286,7 @@ recv_callback(nghttp2_session *session,
* *
* This are all messages except /.well-known, * 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 * @retval void
* Discussion: problematic if fatal error -1 is returned from clixon routines * Discussion: problematic if fatal error -1 is returned from clixon routines
* without actually terminating. Consider: * without actually terminating. Consider:
@ -360,11 +368,27 @@ restconf_nghttp2_path(restconf_stream_data *sd)
return retval; /* void */ 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, restconf_sd_read(nghttp2_session *session,
int32_t stream_id, int32_t stream_id,
uint8_t *buf, uint8_t *buf,
@ -374,27 +398,19 @@ restconf_sd_read(nghttp2_session *session,
void *user_data) void *user_data)
{ {
restconf_stream_data *sd = (restconf_stream_data *)source->ptr; restconf_stream_data *sd = (restconf_stream_data *)source->ptr;
restconf_conn *rc = sd->sd_conn;
cbuf *cb; cbuf *cb;
size_t len = 0; size_t len = 0;
size_t remain; size_t remain;
clixon_debug(CLIXON_DBG_RESTCONF, "");
if ((cb = sd->sd_body) == NULL){ /* shouldnt happen */ 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; *data_flags |= NGHTTP2_DATA_FLAG_EOF;
return 0; 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; remain = cbuf_len(cb) - sd->sd_body_offset;
clixon_debug(CLIXON_DBG_RESTCONF, "length:%zu totlen:%zu, offset:%zu remain:%zu", clixon_debug(CLIXON_DBG_RESTCONF, "length:%zu totlen:%zu, offset:%zu remain:%zu",
length, length,
@ -415,6 +431,18 @@ restconf_sd_read(nghttp2_session *session,
return len; 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 static int
restconf_submit_response(nghttp2_session *session, restconf_submit_response(nghttp2_session *session,
restconf_conn *rc, restconf_conn *rc,
@ -439,22 +467,34 @@ restconf_submit_response(nghttp2_session *session,
hdr = &hdrs[i++]; hdr = &hdrs[i++];
hdr->name = (uint8_t*)":status"; hdr->name = (uint8_t*)":status";
snprintf(valstr, 15, "%u", sd->sd_code); 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->value = (uint8_t*)valstr;
hdr->namelen = strlen(":status"); hdr->namelen = strlen(":status");
hdr->valuelen = strlen(valstr); hdr->valuelen = strlen(valstr);
hdr->flags = 0; hdr->flags = 0;
cv = NULL; cv = NULL;
while ((cv = cvec_each(sd->sd_outp_hdrs, cv)) != NULL){ while ((cv = cvec_each(sd->sd_outp_hdrs, cv)) != NULL){
hdr = &hdrs[i++]; hdr = &hdrs[i++];
hdr->name = (uint8_t*)cv_name_get(cv); 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->value = (uint8_t*)cv_string_get(cv);
hdr->namelen = strlen(cv_name_get(cv)); hdr->namelen = strlen(cv_name_get(cv));
hdr->valuelen = strlen(cv_string_get(cv)); hdr->valuelen = strlen(cv_string_get(cv));
clixon_debug(CLIXON_DBG_RESTCONF, "%s: %s", hdr->name, hdr->value);
hdr->flags = 0; hdr->flags = 0;
} }
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, if ((ngerr = nghttp2_submit_response(session,
stream_id, stream_id,
hdrs, i, hdrs, i,
@ -462,6 +502,7 @@ restconf_submit_response(nghttp2_session *session,
clixon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_response"); clixon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_response");
goto done; goto done;
} }
}
retval = 0; retval = 0;
done: done:
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval); clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
@ -471,6 +512,13 @@ restconf_submit_response(nghttp2_session *session,
} }
/*! Simulate a received request in an upgrade scenario by talking the http/1 parameters /*! 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 int
http2_exec(restconf_conn *rc, http2_exec(restconf_conn *rc,
@ -530,6 +578,12 @@ http2_exec(restconf_conn *rc,
} }
/*! A frame is received /*! 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 static int
on_frame_recv_callback(nghttp2_session *session, on_frame_recv_callback(nghttp2_session *session,
@ -575,6 +629,10 @@ on_frame_recv_callback(nghttp2_session *session,
} }
/*! An invalid non-DATA frame is received. /*! 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 static int
on_invalid_frame_recv_callback(nghttp2_session *session, on_invalid_frame_recv_callback(nghttp2_session *session,
@ -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. * 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 * You should use :type:`nghttp2_on_frame_recv_callback` to know all
* data frames are received. * data frames are received.
* @param[in] session Nghttp2 session struct
* @param[in] user_data User data, in effect Restconf connection
*/ */
static int static int
on_data_chunk_recv_callback(nghttp2_session *session, 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 /*! 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 static int
before_frame_send_callback(nghttp2_session *session, before_frame_send_callback(nghttp2_session *session,
@ -625,6 +689,10 @@ before_frame_send_callback(nghttp2_session *session,
} }
/*! After the frame |frame| is sent /*! 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 static int
on_frame_send_callback(nghttp2_session *session, on_frame_send_callback(nghttp2_session *session,
@ -637,6 +705,10 @@ on_frame_send_callback(nghttp2_session *session,
} }
/*! After the non-DATA frame |frame| is not sent because of error /*! 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 static int
on_frame_not_send_callback(nghttp2_session *session, on_frame_not_send_callback(nghttp2_session *session,
@ -650,6 +722,9 @@ on_frame_not_send_callback(nghttp2_session *session,
} }
/*! Stream |stream_id| is closed. /*! Stream |stream_id| is closed.
*
* @param[in] session Nghttp2 session struct
* @param[in] user_data User data, in effect Restconf connection
*/ */
static int static int
on_stream_close_callback(nghttp2_session *session, on_stream_close_callback(nghttp2_session *session,
@ -670,6 +745,10 @@ on_stream_close_callback(nghttp2_session *session,
} }
/*! Reception of header block in HEADERS or PUSH_PROMISE is started. /*! 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 static int
on_begin_headers_callback(nghttp2_session *session, on_begin_headers_callback(nghttp2_session *session,
@ -730,6 +809,9 @@ nghttp2_hdr2clixon(clixon_handle h,
* If the application uses `nghttp2_session_mem_recv()`, it can return * If the application uses `nghttp2_session_mem_recv()`, it can return
* :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()` * :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()`
* return without processing further input bytes. * 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 static int
on_header_callback(nghttp2_session *session, on_header_callback(nghttp2_session *session,
@ -760,41 +842,13 @@ on_header_callback(nghttp2_session *session,
return retval; 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. /*! Invoked when a frame header is received.
* *
* Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will * Unlike :type:`nghttp2_on_frame_recv_callback`, this callback will
* also be called when frame header of CONTINUATION frame is received. * 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 static int
on_begin_frame_callback(nghttp2_session *session, on_begin_frame_callback(nghttp2_session *session,
@ -813,11 +867,15 @@ on_begin_frame_callback(nghttp2_session *session,
* Callback function invoked when :enum:`NGHTTP2_DATA_FLAG_NO_COPY` is * Callback function invoked when :enum:`NGHTTP2_DATA_FLAG_NO_COPY` is
* used in :type:`nghttp2_data_source_read_callback` to send complete * used in :type:`nghttp2_data_source_read_callback` to send complete
* DATA frame. * 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 static int
send_data_callback(nghttp2_session *session, send_data_callback(nghttp2_session *session,
nghttp2_frame *frame, nghttp2_frame *frame,
const uint8_t *framehd, size_t length, const uint8_t *framehd,
size_t length,
nghttp2_data_source *source, nghttp2_data_source *source,
void *user_data) void *user_data)
{ {
@ -826,35 +884,11 @@ send_data_callback(nghttp2_session *session,
return 0; 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 /*! 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 static int
on_extension_chunk_recv_callback(nghttp2_session *session, on_extension_chunk_recv_callback(nghttp2_session *session,
@ -869,6 +903,9 @@ on_extension_chunk_recv_callback(nghttp2_session *session,
} }
/*! Library provides the error code, and message for debugging purpose. /*! 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 static int
error_callback(nghttp2_session *session, error_callback(nghttp2_session *session,
@ -883,6 +920,9 @@ error_callback(nghttp2_session *session,
#if (NGHTTP2_VERSION_NUM > 0x011201) /* Unsure of version number */ #if (NGHTTP2_VERSION_NUM > 0x011201) /* Unsure of version number */
/*! Library provides the error code, and message for debugging purpose. /*! 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 static int
error_callback2(nghttp2_session *session, error_callback2(nghttp2_session *session,
@ -988,6 +1028,7 @@ http2_send_server_connection(restconf_conn *rc)
} }
/*! Initialize callbacks /*! Initialize callbacks
*
*/ */
int int
http2_session_init(restconf_conn *rc) 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_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_begin_headers_callback(callbacks, on_begin_headers_callback);
nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_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_on_begin_frame_callback(callbacks, on_begin_frame_callback);
nghttp2_session_callbacks_set_send_data_callback(callbacks, send_data_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_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_callback(callbacks, error_callback);
#if (NGHTTP2_VERSION_NUM > 0x011201) /* Unsure of version number */ #if (NGHTTP2_VERSION_NUM > 0x011201) /* Unsure of version number */

View file

@ -33,6 +33,7 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
* *
* Virtual clixon restconf API functions. * Virtual clixon restconf API functions.
* @see RFC9113
*/ */
#ifndef _RESTCONF_NGHTTP2_H_ #ifndef _RESTCONF_NGHTTP2_H_
@ -42,6 +43,7 @@
* Prototypes * Prototypes
*/ */
int clixon_nghttp2_log_cb(void *handle, int suberr, cbuf *cb); 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_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_recv(restconf_conn *rc, const unsigned char *buf, size_t n);
int http2_send_server_connection(restconf_conn *rc); int http2_send_server_connection(restconf_conn *rc);

View file

@ -398,8 +398,8 @@ api_data(clixon_handle h,
goto done; goto done;
retval = api_return_err0(h, req, xerr, pretty, media_out, 0); retval = api_return_err0(h, req, xerr, pretty, media_out, 0);
} }
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
done: done:
clixon_debug(CLIXON_DBG_RESTCONF, "retval:%d", retval);
if (xerr) if (xerr)
xml_free(xerr); xml_free(xerr);
return retval; return retval;

View file

@ -125,7 +125,7 @@ api_path_is_stream(clixon_handle h)
* @param[in] qvec * @param[in] qvec
* @param[in] pretty Pretty-print json/xml reply * @param[in] pretty Pretty-print json/xml reply
* @param[in] media_out Restconf output media * @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 0 OK
* @retval -1 Error * @retval -1 Error
*/ */
@ -179,14 +179,15 @@ restconf_subscription(clixon_handle h,
goto ok; goto ok;
} }
/* Setting up stream */ /* Setting up stream */
if (restconf_reply_header(req, "Server", "clixon") < 0)
goto done;
if (restconf_reply_header(req, "Server", "clixon") < 0) if (restconf_reply_header(req, "Server", "clixon") < 0)
goto done; goto done;
if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0) if (restconf_reply_header(req, "Content-Type", "text/event-stream") < 0)
goto done; goto done;
if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0) if (restconf_reply_header(req, "Cache-Control", "no-cache") < 0)
goto done; 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) if (restconf_reply_header(req, "Connection", "keep-alive") < 0)
goto done; goto done;
/* Must be there for FCGI caching */ /* Must be there for FCGI caching */

View file

@ -55,7 +55,6 @@
#include <syslog.h> #include <syslog.h>
#include <pwd.h> #include <pwd.h>
#include <ctype.h> #include <ctype.h>
#include <assert.h>
#include <signal.h> #include <signal.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/socket.h> #include <sys/socket.h>
@ -86,6 +85,39 @@
#include "restconf_native.h" /* Restconf-openssl mode specific headers*/ #include "restconf_native.h" /* Restconf-openssl mode specific headers*/
#include "restconf_stream.h" #include "restconf_stream.h"
#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 /*! Callback when stream notifications arrive from backend
* *
* @param[in] s Socket * @param[in] s Socket
@ -110,6 +142,9 @@ stream_native_backend_cb(int s,
int ret; int ret;
restconf_conn *rc = sd->sd_conn; restconf_conn *rc = sd->sd_conn;
clixon_handle h = rc->rc_h; clixon_handle h = rc->rc_h;
#ifdef HAVE_LIBNGHTTP2
nghttp2_error ngerr;
#endif
clixon_debug(CLIXON_DBG_STREAM|CLIXON_DBG_DETAIL, ""); clixon_debug(CLIXON_DBG_STREAM|CLIXON_DBG_DETAIL, "");
pretty = restconf_pretty_get(h); pretty = restconf_pretty_get(h);
@ -139,30 +174,27 @@ stream_native_backend_cb(int s,
} }
if ((xn = xpath_first(xtop, NULL, "notification")) == NULL) if ((xn = xpath_first(xtop, NULL, "notification")) == NULL)
goto ok; 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: "); cprintf(cb, "data: ");
if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0) if (clixon_xml2cbuf(cb, xn, 0, pretty, NULL, -1, 0) < 0)
goto done; goto done;
cprintf(cb, "\r\n"); cprintf(cb, "\r\n");
cprintf(cb, "\r\n"); cprintf(cb, "\r\n");
#endif #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) if ((ret = native_buf_write(h, cbuf_get(cb), cbuf_len(cb), rc, "native stream")) < 0)
goto done; goto done;
ok: ok:
@ -181,15 +213,39 @@ stream_native_backend_cb(int s,
} }
/*! Timeout of notification stream, limit lifetime, for debug /*! Timeout of notification stream, limit lifetime, for debug
*
* XXX HTTP/2 not closed cleanly
*/ */
static int static int
stream_timeout_end(int s, stream_timeout_end(int s,
void *arg) void *arg)
{ {
int retval = -1;
restconf_conn *rc = (restconf_conn *)arg; restconf_conn *rc = (restconf_conn *)arg;
clixon_debug(CLIXON_DBG_STREAM, ""); 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 /*! Close notification stream
@ -203,6 +259,7 @@ stream_close(clixon_handle h,
{ {
restconf_conn *rc = (restconf_conn *)req; restconf_conn *rc = (restconf_conn *)req;
clixon_debug(CLIXON_DBG_STREAM, "");
clicon_rpc_close_session(h); clicon_rpc_close_session(h);
clixon_event_unreg_fd(rc->rc_event_stream, stream_native_backend_cb); clixon_event_unreg_fd(rc->rc_event_stream, stream_native_backend_cb);
clixon_event_unreg_timeout(stream_timeout_end, req); clixon_event_unreg_timeout(stream_timeout_end, req);

View file

@ -35,7 +35,6 @@ fi
: ${SLEEP2:=1} : ${SLEEP2:=1}
SLEEP5=.5 SLEEP5=.5
APPNAME=example APPNAME=example
: ${clixon_util_stream:=clixon_util_stream}
: ${TIMEOUT:=10} : ${TIMEOUT:=10}
: ${PERIOD:=2} : ${PERIOD:=2}
@ -231,13 +230,14 @@ if [ "${WITH_RESTCONF}" = "fcgi" ]; then
runtest "" runtest ""
fi fi
if [ "${WITH_RESTCONF}" = "native" ]; then 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 runtest --http2
fi fi
fi fi
if false; then # NYI if false; then # NYI
: ${clixon_util_stream:=clixon_util_stream}
# 2c # 2c
new "2c) start sub 8s - replay from start -8s - expect 3-4 notifications" new "2c) start sub 8s - replay from start -8s - expect 3-4 notifications"
ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 10 -s -8) ret=$($clixon_util_stream -u $RCPROTO://localhost/streams/EXAMPLE -t 10 -s -8)