Add a server option to fallback to RFC 7540 priorities

Add nghttp2_option_set_server_fallback_rfc7540_priorities.  If it is
set to nonzero, and server submits SETTINGS_NO_RFC7540_PRIORITIES = 1,
but it does not receive SETTINGS_NO_RFC7540_PRIORITIES from client,
server falls back to RFC 7540 priorities.  Only minimal set of
features are enabled in this fallback case.
This commit is contained in:
Tatsuhiro Tsujikawa 2022-06-14 23:30:24 +09:00
parent d1e07ab6b7
commit 8c2386c221
9 changed files with 254 additions and 9 deletions

View File

@ -69,6 +69,7 @@ APIDOCS= \
nghttp2_option_set_no_http_messaging.rst \
nghttp2_option_set_no_recv_client_magic.rst \
nghttp2_option_set_peer_max_concurrent_streams.rst \
nghttp2_option_set_server_fallback_rfc7540_priorities.rst \
nghttp2_option_set_user_recv_extension_type.rst \
nghttp2_option_set_max_outbound_ack.rst \
nghttp2_option_set_max_settings.rst \

View File

@ -2732,6 +2732,24 @@ NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option,
size_t val);
/**
* @function
*
* This option, if set to nonzero, allows server to fallback to
* :rfc:`7540` priorities if SETTINGS_NO_RFC7540_PRIORITIES was not
* received from client, and server submitted
* :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
* = 1 via `nghttp2_submit_settings()`. Most of the advanced
* functionality for RFC 7540 priorities are still disabled. This
* fallback only enables the minimal feature set of RFC 7540
* priorities to deal with priority signaling from client.
*
* Client session ignores this option.
*/
NGHTTP2_EXTERN void
nghttp2_option_set_server_fallback_rfc7540_priorities(nghttp2_option *option,
int val);
/**
* @function
*

View File

@ -130,3 +130,9 @@ void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val) {
option->opt_set_mask |= NGHTTP2_OPT_MAX_SETTINGS;
option->max_settings = val;
}
void nghttp2_option_set_server_fallback_rfc7540_priorities(
nghttp2_option *option, int val) {
option->opt_set_mask |= NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES;
option->server_fallback_rfc7540_priorities = val;
}

View File

@ -68,6 +68,7 @@ typedef enum {
NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13,
} nghttp2_option_flag;
/**
@ -127,6 +128,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_NO_CLOSED_STREAMS
*/
int no_closed_streams;
/**
* NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES
*/
int server_fallback_rfc7540_priorities;
/**
* NGHTTP2_OPT_USER_RECV_EXT_TYPES
*/

View File

@ -144,6 +144,11 @@ static int session_detect_idle_stream(nghttp2_session *session,
return 0;
}
static int session_no_rfc7540_pri_no_fallback(nghttp2_session *session) {
return session->pending_no_rfc7540_priorities == 1 &&
!session->fallback_rfc7540_priorities;
}
static int check_ext_type_set(const uint8_t *ext_types, uint8_t type) {
return (ext_types[type / 8] & (1 << (type & 0x7))) > 0;
}
@ -554,6 +559,13 @@ static int session_new(nghttp2_session **session_ptr,
option->max_settings) {
(*session_ptr)->max_settings = option->max_settings;
}
if ((option->opt_set_mask &
NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES) &&
option->server_fallback_rfc7540_priorities) {
(*session_ptr)->opt_flags |=
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES;
}
}
rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
@ -810,7 +822,8 @@ int nghttp2_session_reprioritize_stream(
nghttp2_priority_spec pri_spec_default;
const nghttp2_priority_spec *pri_spec = pri_spec_in;
assert(session->pending_no_rfc7540_priorities != 1);
assert((!session->server && session->pending_no_rfc7540_priorities != 1) ||
(session->server && !session_no_rfc7540_pri_no_fallback(session)));
assert(pri_spec->stream_id != stream->stream_id);
if (!nghttp2_stream_in_dep_tree(stream)) {
@ -1296,7 +1309,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
return NULL;
}
if (session->pending_no_rfc7540_priorities == 1) {
if (session_no_rfc7540_pri_no_fallback(session)) {
stream->flags |= NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES;
}
}
@ -1309,7 +1322,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
stream_alloc = 1;
}
if (session->pending_no_rfc7540_priorities == 1 ||
if (session_no_rfc7540_pri_no_fallback(session) ||
session->remote_settings.no_rfc7540_priorities == 1) {
/* For client which has not received server
SETTINGS_NO_RFC7540_PRIORITIES = 1, send a priority signal
@ -1369,7 +1382,7 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
(int32_t)session->local_settings.initial_window_size,
stream_user_data, mem);
if (session->pending_no_rfc7540_priorities == 1) {
if (session_no_rfc7540_pri_no_fallback(session)) {
stream->seq = session->stream_seq++;
}
@ -4440,7 +4453,7 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
int rv;
nghttp2_stream *stream;
assert(session->pending_no_rfc7540_priorities != 1);
assert(!session_no_rfc7540_pri_no_fallback(session));
if (frame->hd.stream_id == 0) {
return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
@ -4499,7 +4512,7 @@ static int session_process_priority_frame(nghttp2_session *session) {
nghttp2_inbound_frame *iframe = &session->iframe;
nghttp2_frame *frame = &iframe->frame;
assert(session->pending_no_rfc7540_priorities != 1);
assert(!session_no_rfc7540_pri_no_fallback(session));
nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos);
@ -4927,6 +4940,12 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
if (session->remote_settings.no_rfc7540_priorities == UINT32_MAX) {
session->remote_settings.no_rfc7540_priorities = 0;
if (session->server && session->pending_no_rfc7540_priorities &&
(session->opt_flags &
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES)) {
session->fallback_rfc7540_priorities = 1;
}
}
if (!noack && !session_is_closing(session)) {
@ -6380,7 +6399,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
break;
}
if (session->pending_no_rfc7540_priorities != 1 ||
if (!session_no_rfc7540_pri_no_fallback(session) ||
iframe->payloadleft > sizeof(iframe->raw_sbuf)) {
busy = 1;
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
@ -6498,7 +6517,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
break;
case NGHTTP2_PRIORITY:
if (session->pending_no_rfc7540_priorities != 1 &&
if (!session_no_rfc7540_pri_no_fallback(session) &&
session->remote_settings.no_rfc7540_priorities != 1) {
rv = session_process_priority_frame(session);
if (nghttp2_is_fatal(rv)) {

View File

@ -52,7 +52,8 @@ typedef enum {
NGHTTP2_OPTMASK_NO_RECV_CLIENT_MAGIC = 1 << 1,
NGHTTP2_OPTMASK_NO_HTTP_MESSAGING = 1 << 2,
NGHTTP2_OPTMASK_NO_AUTO_PING_ACK = 1 << 3,
NGHTTP2_OPTMASK_NO_CLOSED_STREAMS = 1 << 4
NGHTTP2_OPTMASK_NO_CLOSED_STREAMS = 1 << 4,
NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 5
} nghttp2_optmask;
/*
@ -340,6 +341,8 @@ struct nghttp2_session {
/* Unacked local SETTINGS_NO_RFC7540_PRIORITIES value, which is
effective before it is acknowledged. */
uint8_t pending_no_rfc7540_priorities;
/* Turn on fallback to RFC 7540 priorities; for server use only. */
uint8_t fallback_rfc7540_priorities;
/* Nonzero if the session is server side. */
uint8_t server;
/* Flags indicating GOAWAY is sent and/or received. The flags are

View File

@ -339,6 +339,8 @@ int main(void) {
test_nghttp2_session_set_stream_user_data) ||
!CU_add_test(pSuite, "session_no_rfc7540_priorities",
test_nghttp2_session_no_rfc7540_priorities) ||
!CU_add_test(pSuite, "session_server_fallback_rfc7540_priorities",
test_nghttp2_session_server_fallback_rfc7540_priorities) ||
!CU_add_test(pSuite, "http_mandatory_headers",
test_nghttp2_http_mandatory_headers) ||
!CU_add_test(pSuite, "http_content_length",

View File

@ -11637,6 +11637,196 @@ void test_nghttp2_session_no_rfc7540_priorities(void) {
nghttp2_session_del(session);
}
void test_nghttp2_session_server_fallback_rfc7540_priorities(void) {
nghttp2_session *session;
nghttp2_option *option;
nghttp2_session_callbacks callbacks;
nghttp2_frame frame;
nghttp2_bufs bufs;
nghttp2_buf *buf;
ssize_t rv;
nghttp2_settings_entry iv;
nghttp2_mem *mem;
nghttp2_hd_deflater deflater;
nghttp2_nv *nva;
size_t nvlen;
nghttp2_priority_spec pri_spec;
nghttp2_stream *anchor_stream, *stream;
my_user_data ud;
nghttp2_ext_priority_update priority_update;
static const uint8_t field_value[] = "u=0";
mem = nghttp2_mem_default();
frame_pack_bufs_init(&bufs);
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
callbacks.on_frame_recv_callback = on_frame_recv_callback;
nghttp2_option_new(&option);
nghttp2_option_set_server_fallback_rfc7540_priorities(option, 1);
iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv.value = 1;
/* Server falls back to RFC 7540 priorities. */
nghttp2_session_server_new2(&session, &callbacks, &ud, option);
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
CU_ASSERT(0 == rv);
rv = nghttp2_session_send(session);
CU_ASSERT(0 == rv);
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, NULL, 0);
rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
CU_ASSERT(0 == rv);
nghttp2_frame_settings_free(&frame.settings, mem);
buf = &bufs.head->buf;
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
CU_ASSERT(1 == session->fallback_rfc7540_priorities);
nghttp2_hd_deflate_init(&deflater, mem);
nvlen = ARRLEN(reqnv);
nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
nghttp2_priority_spec_init(&pri_spec, 3, 111, 1);
nghttp2_frame_headers_init(&frame.headers,
NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen);
nghttp2_bufs_reset(&bufs);
rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
CU_ASSERT(0 == rv);
nghttp2_frame_headers_free(&frame.headers, mem);
buf = &bufs.head->buf;
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
anchor_stream = nghttp2_session_get_stream_raw(session, 3);
CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state);
CU_ASSERT(
!(anchor_stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES));
CU_ASSERT(&session->root == anchor_stream->dep_prev);
stream = nghttp2_session_get_stream(session, 1);
CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
CU_ASSERT(!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES));
CU_ASSERT(anchor_stream == stream->dep_prev);
/* Make sure that PRIORITY frame updates stream priority. */
nghttp2_priority_spec_init(&pri_spec, 5, 1, 0);
nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec);
nghttp2_bufs_reset(&bufs);
rv = nghttp2_frame_pack_priority(&bufs, &frame.priority);
CU_ASSERT(0 == rv);
nghttp2_frame_priority_free(&frame.priority);
buf = &bufs.head->buf;
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
anchor_stream = nghttp2_session_get_stream_raw(session, 5);
CU_ASSERT(NGHTTP2_STREAM_IDLE == anchor_stream->state);
CU_ASSERT(&session->root == anchor_stream->dep_prev);
CU_ASSERT(anchor_stream == stream->dep_prev);
/* Make sure that PRIORITY_UPDATE frame is ignored. */
frame.ext.payload = &priority_update;
nghttp2_frame_priority_update_init(&frame.ext, 1, (uint8_t *)field_value,
sizeof(field_value) - 1);
nghttp2_bufs_reset(&bufs);
nghttp2_frame_pack_priority_update(&bufs, &frame.ext);
ud.frame_recv_cb_called = 0;
buf = &bufs.head->buf;
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
CU_ASSERT(0 == ud.frame_recv_cb_called);
CU_ASSERT(NGHTTP2_EXTPRI_DEFAULT_URGENCY == stream->extpri);
nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session);
/* Server does not fallback to RFC 7540 priorities. */
nghttp2_session_server_new2(&session, &callbacks, &ud, option);
rv = nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1);
CU_ASSERT(0 == rv);
rv = nghttp2_session_send(session);
CU_ASSERT(0 == rv);
iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv.value = 0;
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE,
dup_iv(&iv, 1), 1);
nghttp2_bufs_reset(&bufs);
rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
CU_ASSERT(0 == rv);
nghttp2_frame_settings_free(&frame.settings, mem);
buf = &bufs.head->buf;
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
CU_ASSERT(0 == session->fallback_rfc7540_priorities);
nghttp2_hd_deflate_init(&deflater, mem);
nvlen = ARRLEN(reqnv);
nghttp2_nv_array_copy(&nva, reqnv, nvlen, mem);
nghttp2_priority_spec_init(&pri_spec, 3, 111, 1);
nghttp2_frame_headers_init(&frame.headers,
NGHTTP2_FLAG_END_HEADERS | NGHTTP2_FLAG_PRIORITY,
1, NGHTTP2_HCAT_HEADERS, &pri_spec, nva, nvlen);
nghttp2_bufs_reset(&bufs);
rv = nghttp2_frame_pack_headers(&bufs, &frame.headers, &deflater);
CU_ASSERT(0 == rv);
nghttp2_frame_headers_free(&frame.headers, mem);
buf = &bufs.head->buf;
rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
CU_ASSERT(NULL == nghttp2_session_get_stream_raw(session, 3));
stream = nghttp2_session_get_stream(session, 1);
CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state);
CU_ASSERT(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES);
nghttp2_hd_deflate_free(&deflater);
nghttp2_session_del(session);
nghttp2_option_del(option);
nghttp2_bufs_free(&bufs);
}
static void check_nghttp2_http_recv_headers_fail(
nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id,
int stream_state, const nghttp2_nv *nva, size_t nvlen) {

View File

@ -166,6 +166,7 @@ void test_nghttp2_session_pause_data(void);
void test_nghttp2_session_no_closed_streams(void);
void test_nghttp2_session_set_stream_user_data(void);
void test_nghttp2_session_no_rfc7540_priorities(void);
void test_nghttp2_session_server_fallback_rfc7540_priorities(void);
void test_nghttp2_http_mandatory_headers(void);
void test_nghttp2_http_content_length(void);
void test_nghttp2_http_content_length_mismatch(void);