/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2019 Olof Hagsand Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 3 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL, and not to allow others to use your version of this file under the terms of Apache License version 2, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** * nghttp2 callback mechanism * * nghttp2_session_mem_recv() * on_begin_headers_callback() * create sd * on_header_callback() NGHTTP2_HEADERS * translate all headers * on_data_chunk_recv_callback * get indata * on_frame_recv_callback NGHTTP2_FLAG_END_STREAM * get method and call handler * create rr */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif /* The clixon evhtp code can be compiled with or without threading support * The choice is set at libevhtp compile time by cmake. Eg: * cmake -DEVHTP_DISABLE_EVTHR=ON # Disable threads. * Default in testing is disabled threads. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* cligen */ #include /* clicon */ #include #ifdef HAVE_LIBEVHTP /* To get restconf_native.h include files right */ /* evhtp */ #include /* evbuffer */ #define EVHTP_DISABLE_REGEX #define EVHTP_DISABLE_EVTHR #include #endif /* HAVE_LIBEVHTP */ #ifdef HAVE_LIBNGHTTP2 #include #endif /* restconf */ #include "restconf_lib.h" /* generic shared with plugins */ #include "restconf_handle.h" #include "restconf_api.h" /* generic not shared with plugins */ #include "restconf_err.h" #include "restconf_root.h" #include "restconf_native.h" /* Restconf-openssl mode specific headers*/ #ifdef HAVE_LIBNGHTTP2 #include "restconf_nghttp2.h" /* Restconf-openssl mode specific headers*/ #define ARRLEN(x) (sizeof(x) / sizeof(x[0])) /*! Map http2 frame types in nghttp2 * I had expected it in in libnghttp2 but havent found it */ static const map_str2int nghttp2_frame_type_map[] = { {"DATA", NGHTTP2_DATA}, {"HEADERS", NGHTTP2_HEADERS}, {"PRIORITY", NGHTTP2_PRIORITY}, {"RST_STREAM", NGHTTP2_RST_STREAM}, {"SETTINGS", NGHTTP2_SETTINGS}, {"PUSH_PROMISE", NGHTTP2_PUSH_PROMISE}, {"PING", NGHTTP2_PING}, {"GOAWAY", NGHTTP2_GOAWAY}, {"WINDOW_UPDATE", NGHTTP2_WINDOW_UPDATE}, {"CONTINUATION", NGHTTP2_CONTINUATION}, {"ALTSVC", NGHTTP2_ALTSVC}, {NULL, -1} }; /* Clixon error category specialized log callback for nghttp2 * @param[in] handle Application-specific handle * @param[in] suberr Application-specific handle * @param[out] cb Read log/error string into this buffer */ int clixon_nghttp2_log_cb(void *handle, int suberr, cbuf *cb) { clicon_debug(1, "%s", __FUNCTION__); cprintf(cb, "Fatal error: %s", nghttp2_strerror(suberr)); return 0; } #ifdef NOTUSED static void nghttp2_print_header(const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen) { clicon_debug(1, "%s %s", name, value); } /* Print HTTP headers to |f|. Please note that this function does not take into account that header name and value are sequence of octets, therefore they may contain non-printable characters. */ static void nghttp2_print_headers(nghttp2_nv *nva, size_t nvlen) { size_t i; for (i = 0; i < nvlen; ++i) nghttp2_print_header(nva[i].name, nva[i].namelen, nva[i].value, nva[i].valuelen); } #endif /* NOTUSED */ /*! Send data to remote peer, Send at most the |length| bytes of |data|. * This callback is required if the application uses * `nghttp2_session_send()` to send data to the remote endpoint. If * the application uses solely `nghttp2_session_mem_send()` instead, * this callback function is unnecessary. * XXX see buf_write */ static ssize_t session_send_callback(nghttp2_session *session, const uint8_t *buf, size_t buflen, int flags, void *user_data) { int retval = -1; restconf_conn *rc = (restconf_conn *)user_data; int er; ssize_t len; ssize_t totlen = 0; int s; SSL *ssl; int sslerr; clicon_debug(1, "%s buflen:%zu", __FUNCTION__, buflen); s = rc->rc_s; ssl = rc->rc_ssl; while (totlen < buflen){ if (ssl){ if ((len = SSL_write(ssl, buf+totlen, buflen-totlen)) <= 0){ er = errno; sslerr = SSL_get_error(ssl, len); clicon_debug(1, "%s errno:;%d sslerr:%d", __FUNCTION__, errno, sslerr); switch (sslerr){ case SSL_ERROR_WANT_WRITE: /* 3 */ clicon_debug(1, "%s write SSL_ERROR_WANT_WRITE", __FUNCTION__); usleep(1000); continue; break; case SSL_ERROR_SYSCALL: /* 5 */ if (er == ECONNRESET) {/* Connection reset by peer */ if (ssl) SSL_free(ssl); close(s); // XXX clixon_event_unreg_fd(s, restconf_connection); goto ok; /* Close socket and ssl */ } else if (er == EAGAIN){ /* same as want_write above, but different behaviour on different * platforms, linux here, freebsd want_write, or possibly differnt * ssl lib versions? */ clicon_debug(1, "%s write EAGAIN", __FUNCTION__); usleep(1000); continue; } else{ clicon_err(OE_RESTCONF, er, "SSL_write %d", er); goto done; } break; default: clicon_err(OE_SSL, 0, "SSL_write"); goto done; break; } goto done; } } else{ if ((len = write(s, buf+totlen, buflen-totlen)) < 0){ if (errno == EAGAIN){ clicon_debug(1, "%s write EAGAIN", __FUNCTION__); usleep(10000); continue; } #if 1 else if (errno == ECONNRESET) {/* Connection reset by peer */ close(s); // XXX clixon_event_unreg_fd(s, restconf_connection); goto ok; /* Close socket and ssl */ } #endif else{ clicon_err(OE_UNIX, errno, "write"); goto done; } } assert(len != 0); } totlen += len; } /* while */ ok: retval = 0; done: if (retval < 0){ clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; } clicon_debug(1, "%s retval:%zd", __FUNCTION__, totlen); return totlen; } /*! Invoked when |session| wants to receive data from the remote peer. */ static ssize_t recv_callback(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } /*! Callback for each incoming http request for path / * * This are all messages except /.well-known, Registered with evhtp_set_cb * * @param[in] req evhtp http request structure defining the incoming message * @param[in] arg cx_evhtp handle clixon specific fields * @retval void * Discussion: problematic if fatal error -1 is returneod from clixon routines * without actually terminating. Consider: * 1) sending some error? and/or * 2) terminating the process? */ static int restconf_nghttp2_path(restconf_stream_data *sd) { int retval = -1; clicon_handle h; restconf_conn *rc; char *oneline = NULL; cvec *cvv = NULL; char *cn; clicon_debug(1, "------------"); rc = sd->sd_conn; if ((h = rc->rc_h) == NULL){ clicon_err(OE_RESTCONF, EINVAL, "arg is NULL"); goto done; } if (rc->rc_ssl != NULL){ /* Slightly awkward way of taking SSL cert subject and CN and add it to restconf parameters * instead of accessing it directly * SSL subject fields, eg CN (Common Name) , can add more here? */ if (ssl_x509_name_oneline(rc->rc_ssl, &oneline) < 0) goto done; if (oneline != NULL) { if (uri_str2cvec(oneline, '/', '=', 1, &cvv) < 0) goto done; if ((cn = cvec_find_str(cvv, "CN")) != NULL){ if (restconf_param_set(h, "SSL_CN", cn) < 0) goto done; } } } /* Check sanity of session, eg ssl client cert validation, may set rc_exit */ if (restconf_connection_sanity(h, rc, sd) < 0) goto done; if (!rc->rc_exit){ /* call generic function */ if (strcmp(sd->sd_path, RESTCONF_WELL_KNOWN) == 0){ if (api_well_known(h, sd) < 0) goto done; } 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) goto done; retval = 0; done: clicon_debug(1, "%s %d", __FUNCTION__, retval); /* Catch all on fatal error. This does not terminate the process but closes request stream */ // if (retval < 0){ // evhtp_send_reply(req, EVHTP_RES_ERROR); // } if (cvv) cvec_free(cvv); if (oneline) free(oneline); return retval; /* void */ } /*! data callback, just pass pointer to cbuf * XXX handle several chunks with cbuf */ static 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) { restconf_stream_data *sd = (restconf_stream_data *)source->ptr; cbuf *cb; size_t len = 0; size_t remain; if ((cb = sd->sd_body) == NULL){ /* shouldnt happen */ *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; clicon_debug(1, "%s length:%zu totlen:%zu, offset:%zu remain:%zu", __FUNCTION__, length, cbuf_len(cb), sd->sd_body_offset, remain); if (remain <= length){ len = remain; *data_flags |= NGHTTP2_DATA_FLAG_EOF; } else{ len = length; } memcpy(buf, cbuf_get(cb) + sd->sd_body_offset, len); sd->sd_body_offset += len; clicon_debug(1, "%s retval:%zu", __FUNCTION__, len); return len; } static int restconf_submit_response(nghttp2_session *session, restconf_conn *rc, int stream_id, restconf_stream_data *sd) { int retval = -1; nghttp2_data_provider data_prd; nghttp2_error ngerr; cg_var *cv; nghttp2_nv *hdrs = NULL; nghttp2_nv *hdr; int i = 0; char valstr[16]; data_prd.source.ptr = sd; data_prd.read_callback = restconf_sd_read; if ((hdrs = (nghttp2_nv*)calloc(1+cvec_len(sd->sd_outp_hdrs), sizeof(nghttp2_nv))) == NULL){ clicon_err(OE_UNIX, errno, "calloc"); goto done; } hdr = &hdrs[i++]; hdr->name = (uint8_t*)":status"; snprintf(valstr, 15, "%u", sd->sd_code); clicon_debug(1, "%s status %d", __FUNCTION__, 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); clicon_debug(1, "%s hdr: %s", __FUNCTION__, hdr->name); hdr->value = (uint8_t*)cv_string_get(cv); hdr->namelen = strlen(cv_name_get(cv)); hdr->valuelen = strlen(cv_string_get(cv)); hdr->flags = 0; } if ((ngerr = nghttp2_submit_response(session, stream_id, hdrs, i, (data_prd.source.ptr != NULL)?&data_prd:NULL)) < 0){ clicon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_response"); goto done; } retval = 0; done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (hdrs) free(hdrs); 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; clicon_debug(1, "%s", __FUNCTION__); 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 && sd->sd_body_len) if (restconf_reply_header(sd, "Content-Length", "%zu", 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: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; } /*! A frame is received */ static int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { 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), frame->hd.stream_id); switch (frame->hd.type) { case NGHTTP2_DATA: case NGHTTP2_HEADERS: /* Check that the client request has finished */ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { /* For DATA and HEADERS frame, this callback may be called after * on_stream_close_callback. Check that stream still alive. */ if ((sd = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) == NULL) return 0; /* 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; } if (http2_exec(rc, sd, session, frame->hd.stream_id) < 0) goto done; } break; default: break; } retval = 0; done: return retval; } /*! An invalid non-DATA frame is received. */ static int on_invalid_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } /*! A chunk of data in DATA frame is received * * ``(flags & NGHTTP2_FLAG_END_STREAM) != 0`` does not * 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. */ static int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) { restconf_conn *rc = (restconf_conn *)user_data; restconf_stream_data *sd; clicon_debug(1, "%s %d", __FUNCTION__, stream_id); if ((sd = restconf_stream_find(rc, stream_id)) != NULL){ cbuf_append_buf(sd->sd_indata, (void*)data, len); } return 0; } /*! Just before the non-DATA frame |frame| is sent */ static int before_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } /*! After the frame |frame| is sent */ static int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } /*! After the non-DATA frame |frame| is not sent because of error */ static int on_frame_not_send_callback(nghttp2_session *session, const nghttp2_frame *frame, int lib_error_code, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } /*! Stream |stream_id| is closed. */ static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code, void *user_data) { // restconf_conn *rc = (restconf_conn *)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; } /*! Reception of header block in HEADERS or PUSH_PROMISE is started. */ static int on_begin_headers_callback(nghttp2_session *session, const nghttp2_frame *frame, void *user_data) { restconf_conn *rc = (restconf_conn *)user_data; restconf_stream_data *sd; clicon_debug(1, "%s %s", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, frame->hd.type)); if (frame->hd.type == NGHTTP2_HEADERS && frame->headers.cat == NGHTTP2_HCAT_REQUEST) { sd = restconf_stream_data_new(rc, frame->hd.stream_id); nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, sd); } return 0; } /*! Map from nghttp2 headers to "fcgi" type parameters used in clixon code * Both |name| and |value| are guaranteed to be NULL-terminated. */ static int nghttp2_hdr2clixon(clicon_handle h, char *name, char *value) { int retval = -1; if (strcmp(name, ":path") == 0){ /* Including ?args, call restconf_uripath() to get only path */ if (restconf_param_set(h, "REQUEST_URI", value) < 0) goto done; } else if (strcmp(name, ":method") == 0){ if (restconf_param_set(h, "REQUEST_METHOD", value) < 0) goto done; } else if (strcmp(name, ":scheme") == 0){ if (strcmp(value, "https") == 0 && restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */ goto done; } else if (strcmp(name, ":authority") == 0){ if (restconf_param_set(h, "HTTP_HOST", value) < 0) goto done; } else if (restconf_convert_hdr(h, name, value) < 0) goto done; retval = 0; done: return retval; } /*! Header name/value pair is received * Both |name| and |value| are guaranteed to be NULL-terminated. * If the application uses `nghttp2_session_mem_recv()`, it can return * :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()` * return without processing further input bytes. */ static int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, const uint8_t *name, size_t namelen, const uint8_t *value, size_t valuelen, uint8_t flags, void *user_data) { int retval = -1; restconf_conn *rc = (restconf_conn *)user_data; switch (frame->hd.type){ case NGHTTP2_HEADERS: assert (frame->headers.cat == NGHTTP2_HCAT_REQUEST); clicon_debug(1, "%s HEADERS %s %s", __FUNCTION__, name, value); if (nghttp2_hdr2clixon(rc->rc_h, (char*)name, (char*)value) < 0) goto done; break; default: clicon_debug(1, "%s %s %s", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, frame->hd.type), name); break; } retval = 0; done: 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; clicon_debug(1, "%s", __FUNCTION__); 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; clicon_debug(1, "%s", __FUNCTION__); 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. */ static int on_begin_frame_callback(nghttp2_session *session, const nghttp2_frame_hd *hd, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s %s", __FUNCTION__, clicon_int2str(nghttp2_frame_type_map, hd->type)); if (hd->type == NGHTTP2_CONTINUATION) assert(0); return 0; } /*! Send complete DATA frame for no-copy * Callback function invoked when :enum:`NGHTTP2_DATA_FLAG_NO_COPY` is * used in :type:`nghttp2_data_source_read_callback` to send complete * DATA frame. */ static int send_data_callback(nghttp2_session *session, nghttp2_frame *frame, const uint8_t *framehd, size_t length, nghttp2_data_source *source, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); 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; clicon_debug(1, "%s", __FUNCTION__); 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; clicon_debug(1, "%s", __FUNCTION__); return 0; } #endif /* NOTUSED */ /*! Chunk of extension frame payload is received */ static int on_extension_chunk_recv_callback(nghttp2_session *session, const nghttp2_frame_hd *hd, const uint8_t *data, size_t len, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } /*! Library provides the error code, and message for debugging purpose. */ static int error_callback(nghttp2_session *session, const char *msg, size_t len, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); return 0; } #if (NGHTTP2_VERSION_NUM > 0x011201) /* Unsure of version number */ /*! Library provides the error code, and message for debugging purpose. */ static int error_callback2(nghttp2_session *session, int lib_error_code, const char *msg, size_t len, void *user_data) { // restconf_conn *rc = (restconf_conn *)user_data; clicon_debug(1, "%s", __FUNCTION__); clicon_err(OE_NGHTTP2, lib_error_code, "%s", msg); return 0; } #endif /*! Process an HTTP/2 request received in buffer, process request and send reply * * @param[in] rc Restconf connection * @param[in] buf Character buffer * @param[in] n Lenght of buf * @retval 1 OK * @retval 0 Invald request * @retval -1 Fatal error */ int http2_recv(restconf_conn *rc, const unsigned char *buf, size_t n) { int retval = -1; nghttp2_error ngerr; clicon_debug(1, "%s", __FUNCTION__); if (rc->rc_ngsession == NULL){ /* http2_session_init not called */ clicon_err(OE_RESTCONF, EINVAL, "No nghttp2 session"); goto done; } /* may make additional pending frames */ if ((ngerr = nghttp2_session_mem_recv(rc->rc_ngsession, buf, n)) < 0){ if (ngerr == NGHTTP2_ERR_BAD_CLIENT_MAGIC){ /* :enum:`NGHTTP2_ERR_BAD_CLIENT_MAGIC` * Invalid client magic was detected. This error only returns * when |session| was configured as server and * `nghttp2_option_set_no_recv_client_magic()` is not used with * nonzero value. */ clicon_log(LOG_INFO, "%s Received bad client magic byte strin", __FUNCTION__); /* unsure if this does anything, byt does not seem to hurt */ if ((ngerr = nghttp2_session_terminate_session(rc->rc_ngsession, ngerr)) < 0) clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_terminate_session %d", ngerr); goto fail; } clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_mem_recv"); goto done; } /* sends highest prio frame from outbound queue to remote peer. It does this as * many as possible until user callback :type:`nghttp2_send_callback` returns * * :enum:`NGHTTP2_ERR_WOULDBLOCK` or the outbound queue becomes empty. */ if ((ngerr = nghttp2_session_send(rc->rc_ngsession)) != 0){ clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_send"); goto done; } retval = 1; /* OK */ done: clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval; fail: retval = 0; goto done; } /* Send HTTP/2 client connection header, which includes 24 bytes magic octets and SETTINGS frame */ int http2_send_server_connection(restconf_conn *rc) { int retval = -1; nghttp2_settings_entry iv[1] = {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; nghttp2_error ngerr; if ((ngerr = nghttp2_submit_settings(rc->rc_ngsession, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv))) != 0){ clicon_err(OE_NGHTTP2, ngerr, "nghttp2_submit_settings"); goto done; } if ((ngerr = nghttp2_session_send(rc->rc_ngsession)) != 0){ clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_send"); goto done; } retval = 0; done: return retval; } /*! Initialize callbacks */ int http2_session_init(restconf_conn *rc) { int retval = -1; nghttp2_session_callbacks *callbacks = NULL; nghttp2_session *session = NULL; nghttp2_error ngerr; nghttp2_session_callbacks_new(&callbacks); nghttp2_session_callbacks_set_send_callback(callbacks, session_send_callback); nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback); nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback); nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(callbacks, on_invalid_frame_recv_callback); nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback); nghttp2_session_callbacks_set_before_frame_send_callback(callbacks, before_frame_send_callback); nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, on_frame_send_callback); nghttp2_session_callbacks_set_on_frame_not_send_callback(callbacks, on_frame_not_send_callback); nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback); nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback); nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback); #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 */ nghttp2_session_callbacks_set_error_callback2(callbacks, error_callback2); #endif /* Create session for server use, register callbacks */ if ((ngerr = nghttp2_session_server_new3(&session, callbacks, rc, NULL, NULL)) < 0){ clicon_err(OE_NGHTTP2, ngerr, "nghttp2_session_server_new"); goto done; } nghttp2_session_callbacks_del(callbacks); rc->rc_ngsession = session; retval = 0; done: return retval; } #endif /* HAVE_LIBNGHTTP2 */