Fix GOAWAY handling

On reception of GOAWAY, new stream creation is disallowed regardless
of last-stream-id in GOAWAY is larger than next stream ID.
This commit is contained in:
Tatsuhiro Tsujikawa 2015-01-07 22:53:43 +09:00
parent 2b14e4a617
commit a804117c83
3 changed files with 32 additions and 42 deletions

View File

@ -1248,11 +1248,10 @@ static int stream_predicate_for_send(nghttp2_stream *stream) {
*/
static int session_predicate_request_headers_send(nghttp2_session *session,
nghttp2_headers *frame) {
/* If we are terminating session (session->goaway_flag is nonzero),
we don't send new request. Also if received last_stream_id is
strictly less than new stream ID, cancel its transmission. */
if (session->goaway_flags ||
session->remote_last_stream_id < frame->hd.stream_id) {
/* If we are terminating session (NGHTTP2_GOAWAY_TERM_ON_SEND) or
GOAWAY was received from peer, new request is not allowed. */
if (session->goaway_flags &
(NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_RECV)) {
return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
}
return 0;
@ -1429,7 +1428,8 @@ static int session_predicate_push_promise_send(nghttp2_session *session,
if (stream->state == NGHTTP2_STREAM_CLOSING) {
return NGHTTP2_ERR_STREAM_CLOSING;
}
if (session->remote_last_stream_id < promised_stream_id) {
if (session->goaway_flags &
(NGHTTP2_GOAWAY_TERM_ON_SEND | NGHTTP2_GOAWAY_RECV)) {
return NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
}
return 0;
@ -2345,6 +2345,8 @@ static int session_after_frame_sent1(nghttp2_session *session) {
session->goaway_flags |= NGHTTP2_GOAWAY_TERM_SENT;
}
session->goaway_flags |= NGHTTP2_GOAWAY_SENT;
rv = session_close_stream_on_goaway(session, frame->goaway.last_stream_id,
1);
@ -3227,8 +3229,8 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session,
}
session->last_recv_stream_id = frame->hd.stream_id;
if (session->local_last_stream_id < frame->hd.stream_id) {
/* We just ignore stream rather than local_last_stream_id */
if (session->goaway_flags & NGHTTP2_GOAWAY_SENT) {
/* We just ignore stream after GOAWAY was queued */
return NGHTTP2_ERR_IGN_HEADER_BLOCK;
}
@ -4038,6 +4040,8 @@ int nghttp2_session_on_goaway_received(nghttp2_session *session,
"GOAWAY: invalid last_stream_id");
}
session->goaway_flags |= NGHTTP2_GOAWAY_RECV;
session->remote_last_stream_id = frame->goaway.last_stream_id;
rv = session_call_on_frame_received(session, frame);
@ -5615,10 +5619,9 @@ static size_t session_get_num_active_streams(nghttp2_session *session) {
int nghttp2_session_want_read(nghttp2_session *session) {
size_t num_active_streams;
/* If these flags are set, we don't want to read. The application
/* If this flag is set, we don't want to read. The application
should drop the connection. */
if ((session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) &&
(session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT)) {
if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) {
return 0;
}
@ -5631,26 +5634,18 @@ int nghttp2_session_want_read(nghttp2_session *session) {
return 1;
}
/* If there is no active streams, we check last_stream_id peer sent
to us. Here is the asymmetry between server and client. For
server, if client cannot make new request, current conneciton is
no use. Server push might be going, but it is idempotent and can
be safely retried with the new connection. For client side, the
theory is the same. */
if (session->server) {
return session->local_last_stream_id > session->last_recv_stream_id;
} else {
return (uint32_t)session->remote_last_stream_id >= session->next_stream_id;
}
/* If there is no active streams and GOAWAY has been sent or
received, we are done with this session. */
return (session->goaway_flags &
(NGHTTP2_GOAWAY_SENT | NGHTTP2_GOAWAY_RECV)) == 0;
}
int nghttp2_session_want_write(nghttp2_session *session) {
size_t num_active_streams;
/* If these flags are set, we don't want to write any data. The
/* If these flag is set, we don't want to write any data. The
application should drop the connection. */
if ((session->goaway_flags & NGHTTP2_GOAWAY_TERM_ON_SEND) &&
(session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT)) {
if (session->goaway_flags & NGHTTP2_GOAWAY_TERM_SENT) {
return 0;
}
@ -5675,17 +5670,10 @@ int nghttp2_session_want_write(nghttp2_session *session) {
return 1;
}
/* If there is no active streams, we check last_stream_id peer sent
to us. Here is the asymmetry between server and client. For
server, if client cannot make new request, current conneciton is
no use. Server push might be going, but it is idempotent and can
be safely retried with the new connection. For client side, the
theory is the same. */
if (session->server) {
return session->local_last_stream_id > session->last_recv_stream_id;
} else {
return (uint32_t)session->remote_last_stream_id >= session->next_stream_id;
}
/* If there is no active streams and GOAWAY has been sent or
received, we are done with this session. */
return (session->goaway_flags &
(NGHTTP2_GOAWAY_SENT | NGHTTP2_GOAWAY_RECV)) == 0;
}
int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags,

View File

@ -130,6 +130,10 @@ typedef enum {
NGHTTP2_GOAWAY_TERM_ON_SEND = 0x1,
/* Flag means GOAWAY to terminate session has been sent */
NGHTTP2_GOAWAY_TERM_SENT = 0x2,
/* Flag means GOAWAY was sent */
NGHTTP2_GOAWAY_SENT = 0x4,
/* Flag means GOAWAY was received */
NGHTTP2_GOAWAY_RECV = 0x8,
} nghttp2_goaway_flag;
struct nghttp2_session {

View File

@ -1756,11 +1756,11 @@ void test_nghttp2_session_on_request_headers_received(void) {
nghttp2_frame_headers_free(&frame.headers, mem);
/* Stream ID which is greater than local_last_stream_id is
ignored */
/* If GOAWAY has been sent, new stream is ignored */
nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, 5,
NGHTTP2_HCAT_REQUEST, NULL, NULL, 0);
session->goaway_flags |= NGHTTP2_GOAWAY_SENT;
user_data.invalid_frame_recv_cb_called = 0;
CU_ASSERT(NGHTTP2_ERR_IGN_HEADER_BLOCK ==
nghttp2_session_on_request_headers_received(session, &frame));
@ -4847,10 +4847,8 @@ void test_nghttp2_session_on_ctrl_not_send(void) {
NULL, 0, NULL));
user_data.frame_not_send_cb_called = 0;
/* Terminating session */
CU_ASSERT(0 == nghttp2_session_add_goaway(session, 0, NGHTTP2_NO_ERROR, NULL,
0, 1));
/* GOAWAY received */
session->goaway_flags |= NGHTTP2_GOAWAY_RECV;
session->next_stream_id = 9;
CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_END_STREAM, -1,