diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc index 0a97482e5a8..e23c44d985f 100644 --- a/proxy/http2/HTTP2.cc +++ b/proxy/http2/HTTP2.cc @@ -243,24 +243,6 @@ http2_write_frame_header(const Http2FrameHeader &hdr, IOVec iov) return true; } -bool -http2_write_data(const uint8_t *src, size_t length, const IOVec &iov) -{ - byte_pointer ptr(iov.iov_base); - write_and_advance(ptr, src, length); - - return true; -} - -bool -http2_write_headers(const uint8_t *src, size_t length, const IOVec &iov) -{ - byte_pointer ptr(iov.iov_base); - write_and_advance(ptr, src, length); - - return true; -} - bool http2_write_rst_stream(uint32_t error_code, IOVec iov) { diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h index 6d2fad5019a..41518871c0b 100644 --- a/proxy/http2/HTTP2.h +++ b/proxy/http2/HTTP2.h @@ -33,6 +33,9 @@ class HTTPHdr; typedef unsigned Http2StreamId; +constexpr Http2StreamId HTTP2_CONNECTION_CONTROL_STRTEAM = 0; +constexpr uint8_t HTTP2_FRAME_NO_FLAG = 0; + // [RFC 7540] 6.9.2. Initial Flow Control Window Size // the flow control window can be come negative so we need to track it with a signed type. typedef int32_t Http2WindowSize; @@ -320,10 +323,6 @@ bool http2_parse_frame_header(IOVec, Http2FrameHeader &); bool http2_write_frame_header(const Http2FrameHeader &, IOVec); -bool http2_write_data(const uint8_t *, size_t, const IOVec &); - -bool http2_write_headers(const uint8_t *, size_t, const IOVec &); - bool http2_write_rst_stream(uint32_t, IOVec); bool http2_write_settings(const Http2SettingsParameter &, const IOVec &); diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc index 10378c7ac75..127b9388cf9 100644 --- a/proxy/http2/Http2ClientSession.cc +++ b/proxy/http2/Http2ClientSession.cc @@ -213,7 +213,8 @@ Http2ClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOB this->read_buffer->water_mark = connection_state.server_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE); this->_reader = reader ? reader : this->read_buffer->alloc_reader(); - this->write_buffer = new_MIOBuffer(HTTP2_HEADER_BUFFER_SIZE_INDEX); + // Set write buffer size to max size of TLS record (16KB) + this->write_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_16K); this->sm_writer = this->write_buffer->alloc_reader(); this->_handle_if_ssl(new_vc); @@ -332,6 +333,19 @@ Http2ClientSession::set_half_close_local_flag(bool flag) half_close_local = flag; } +int64_t +Http2ClientSession::xmit(const Http2TxFrame &frame) +{ + int64_t len = frame.write_to(this->write_buffer); + + if (len > 0) { + total_write_len += len; + write_reenable(); + } + + return len; +} + int Http2ClientSession::main_event_handler(int event, void *edata) { @@ -356,16 +370,6 @@ Http2ClientSession::main_event_handler(int event, void *edata) break; } - case HTTP2_SESSION_EVENT_XMIT: { - Http2Frame *frame = static_cast(edata); - total_write_len += frame->size(); - write_vio->nbytes = total_write_len; - frame->xmit(this->write_buffer); - write_reenable(); - retval = 0; - break; - } - case HTTP2_SESSION_EVENT_REENABLE: // VIO will be reenableed in this handler retval = (this->*session_handler)(VC_EVENT_READ_READY, static_cast(e->cookie)); @@ -391,6 +395,7 @@ Http2ClientSession::main_event_handler(int event, void *edata) retval = 0; break; + case HTTP2_SESSION_EVENT_XMIT: default: Http2SsnDebug("unexpected event=%d edata=%p", event, edata); ink_release_assert(0); diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h index c3a7209e077..c03a0533626 100644 --- a/proxy/http2/Http2ClientSession.h +++ b/proxy/http2/Http2ClientSession.h @@ -27,6 +27,7 @@ #include "Plugin.h" #include "ProxySession.h" #include "Http2ConnectionState.h" +#include "Http2Frame.h" #include #include "tscore/ink_inet.h" #include "tscore/History.h" @@ -77,96 +78,6 @@ struct Http2UpgradeContext { Http2ConnectionSettings client_settings; }; -class Http2Frame -{ -public: - Http2Frame(const Http2FrameHeader &h, IOBufferReader *r, bool e = false) : hdr(h), ioreader(r), from_early_data(e) {} - Http2Frame(Http2FrameType type, Http2StreamId streamid, uint8_t flags, bool e = false) - : hdr({0, (uint8_t)type, flags, streamid}), from_early_data(e) - { - } - - IOBufferReader * - reader() const - { - return ioreader; - } - - const Http2FrameHeader & - header() const - { - return this->hdr; - } - - // Allocate an IOBufferBlock for payload of this frame. - void - alloc(int index) - { - this->ioblock = new_IOBufferBlock(); - this->ioblock->alloc(index); - } - - // Return the writeable buffer space for frame payload - IOVec - write() - { - return make_iovec(this->ioblock->end(), this->ioblock->write_avail()); - } - - // Once the frame has been serialized, update the payload length of frame header. - void - finalize(size_t nbytes) - { - if (this->ioblock) { - ink_assert((int64_t)nbytes <= this->ioblock->write_avail()); - this->ioblock->fill(nbytes); - - this->hdr.length = this->ioblock->size(); - } - } - - void - xmit(MIOBuffer *iobuffer) - { - // Write frame header - uint8_t buf[HTTP2_FRAME_HEADER_LEN]; - http2_write_frame_header(hdr, make_iovec(buf)); - iobuffer->write(buf, sizeof(buf)); - - // Write frame payload - // It could be empty (e.g. SETTINGS frame with ACK flag) - if (ioblock && ioblock->read_avail() > 0) { - iobuffer->append_block(this->ioblock.get()); - } - } - - int64_t - size() - { - if (ioblock) { - return HTTP2_FRAME_HEADER_LEN + ioblock->size(); - } else { - return HTTP2_FRAME_HEADER_LEN; - } - } - - bool - is_from_early_data() const - { - return this->from_early_data; - } - - // noncopyable - Http2Frame(Http2Frame &) = delete; - Http2Frame &operator=(const Http2Frame &) = delete; - -private: - Http2FrameHeader hdr; // frame header - Ptr ioblock; // frame payload - IOBufferReader *ioreader = nullptr; - bool from_early_data = false; -}; - class Http2ClientSession : public ProxySession { public: @@ -194,6 +105,7 @@ class Http2ClientSession : public ProxySession // more methods void write_reenable(); + int64_t xmit(const Http2TxFrame &frame); //////////////////// // Accessors diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc index fed9cd211ec..57a40a683e0 100644 --- a/proxy/http2/Http2ConnectionState.cc +++ b/proxy/http2/Http2ConnectionState.cc @@ -25,6 +25,7 @@ #include "Http2ConnectionState.h" #include "Http2ClientSession.h" #include "Http2Stream.h" +#include "Http2Frame.h" #include "Http2DebugNames.h" #include "HttpDebugNames.h" #include @@ -49,12 +50,12 @@ static const int buffer_size_index[HTTP2_FRAME_TYPE_MAX] = { BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_DATA BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_HEADERS -1, // HTTP2_FRAME_TYPE_PRIORITY - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_RST_STREAM - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_SETTINGS + -1, // HTTP2_FRAME_TYPE_RST_STREAM + -1, // HTTP2_FRAME_TYPE_SETTINGS BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_PUSH_PROMISE - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_PING - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_GOAWAY - BUFFER_SIZE_INDEX_128, // HTTP2_FRAME_TYPE_WINDOW_UPDATE + -1, // HTTP2_FRAME_TYPE_PING + -1, // HTTP2_FRAME_TYPE_GOAWAY + -1, // HTTP2_FRAME_TYPE_WINDOW_UPDATE BUFFER_SIZE_INDEX_16K, // HTTP2_FRAME_TYPE_CONTINUATION }; @@ -639,8 +640,8 @@ rcv_settings_frame(Http2ConnectionState &cstate, const Http2Frame &frame) // [RFC 7540] 6.5. Once all values have been applied, the recipient MUST // immediately emit a SETTINGS frame with the ACK flag set. - Http2Frame ackFrame(HTTP2_FRAME_TYPE_SETTINGS, 0, HTTP2_FLAGS_SETTINGS_ACK); - cstate.ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &ackFrame); + Http2SettingsFrame ack_frame(0, HTTP2_FLAGS_SETTINGS_ACK); + cstate.ua_session->xmit(ack_frame); return Http2Error(Http2ErrorClass::HTTP2_ERROR_CLASS_NONE); } @@ -1481,27 +1482,29 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len const size_t write_available_size = std::min(buf_len, static_cast(window_size)); payload_length = 0; - uint8_t flags = 0x00; - uint8_t payload_buffer[buf_len]; - IOBufferReader *_sm = stream->response_get_data_reader(); + uint8_t flags = 0x00; + IOBufferReader *resp_reader = stream->response_get_data_reader(); SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread()); - if (!_sm) { + if (!resp_reader) { Http2StreamDebug(this->ua_session, stream->get_id(), "couldn't get data reader"); return Http2SendDataFrameResult::ERROR; } // Select appropriate payload length - if (_sm->is_read_avail_more_than(0)) { + if (resp_reader->is_read_avail_more_than(0)) { // We only need to check for window size when there is a payload if (window_size <= 0) { Http2StreamDebug(this->ua_session, stream->get_id(), "No window"); return Http2SendDataFrameResult::NO_WINDOW; } - // Copy into the payload buffer. Seems like we should be able to skip this copy step - payload_length = write_available_size; - payload_length = _sm->read(payload_buffer, static_cast(write_available_size)); + + if (resp_reader->is_read_avail_more_than(write_available_size)) { + payload_length = write_available_size; + } else { + payload_length = resp_reader->read_avail(); + } } else { payload_length = 0; } @@ -1514,7 +1517,7 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len return Http2SendDataFrameResult::NO_PAYLOAD; } - if (stream->is_write_vio_done() && !_sm->is_read_avail_more_than(0)) { + if (stream->is_write_vio_done() && !resp_reader->is_read_avail_more_than(0)) { flags |= HTTP2_FLAGS_DATA_END_STREAM; } @@ -1526,22 +1529,16 @@ Http2ConnectionState::send_a_data_frame(Http2Stream *stream, size_t &payload_len Http2StreamDebug(ua_session, stream->get_id(), "Send a DATA frame - client window con: %5zd stream: %5zd payload: %5zd", _client_rwnd, stream->client_rwnd(), payload_length); - Http2Frame data(HTTP2_FRAME_TYPE_DATA, stream->get_id(), flags); - data.alloc(buffer_size_index[HTTP2_FRAME_TYPE_DATA]); - http2_write_data(payload_buffer, payload_length, data.write()); - data.finalize(payload_length); + Http2DataFrame data(stream->get_id(), flags, resp_reader, payload_length); + this->ua_session->xmit(data); stream->update_sent_count(payload_length); - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &data); - if (flags & HTTP2_FLAGS_DATA_END_STREAM) { Http2StreamDebug(ua_session, stream->get_id(), "END_STREAM"); stream->send_end_stream = true; // Setting to the same state shouldn't be erroneous - stream->change_state(data.header().type, data.header().flags); + stream->change_state(HTTP2_FRAME_TYPE_DATA, flags); return Http2SendDataFrameResult::DONE; } @@ -1586,10 +1583,8 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) uint32_t buf_len = 0; uint32_t header_blocks_size = 0; int payload_length = 0; - uint64_t sent = 0; uint8_t flags = 0x00; - - HTTPHdr *resp_header = &stream->response_header; + HTTPHdr *resp_header = &stream->response_header; Http2StreamDebug(ua_session, stream->get_id(), "Send HEADERS frame"); @@ -1626,10 +1621,6 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) } else { payload_length = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]); } - Http2Frame headers(HTTP2_FRAME_TYPE_HEADERS, stream->get_id(), flags); - headers.alloc(buffer_size_index[HTTP2_FRAME_TYPE_HEADERS]); - http2_write_headers(buf, payload_length, headers.write()); - headers.finalize(payload_length); // Change stream state if (!stream->change_state(HTTP2_FRAME_TYPE_HEADERS, flags)) { @@ -1644,10 +1635,9 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) return; } - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &headers); - sent += payload_length; + Http2HeadersFrame headers(stream->get_id(), flags, buf, payload_length); + this->ua_session->xmit(headers); + uint64_t sent = payload_length; // Send CONTINUATION frames flags = 0; @@ -1658,14 +1648,10 @@ Http2ConnectionState::send_headers_frame(Http2Stream *stream) if (sent + payload_length == header_blocks_size) { flags |= HTTP2_FLAGS_CONTINUATION_END_HEADERS; } - Http2Frame continuation_frame(HTTP2_FRAME_TYPE_CONTINUATION, stream->get_id(), flags); - continuation_frame.alloc(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION]); - http2_write_headers(buf + sent, payload_length, continuation_frame.write()); - continuation_frame.finalize(payload_length); - stream->change_state(continuation_frame.header().type, continuation_frame.header().flags); - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &continuation_frame); + stream->change_state(HTTP2_FRAME_TYPE_CONTINUATION, flags); + + Http2ContinuationFrame continuation_frame(stream->get_id(), flags, buf + sent, payload_length); + this->ua_session->xmit(continuation_frame); sent += payload_length; } @@ -1681,7 +1667,6 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con uint32_t buf_len = 0; uint32_t header_blocks_size = 0; int payload_length = 0; - uint64_t sent = 0; uint8_t flags = 0x00; if (client_settings.get(HTTP2_SETTINGS_ENABLE_PUSH) == 0) { @@ -1735,16 +1720,13 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con payload_length = BUFFER_SIZE_FOR_INDEX(buffer_size_index[HTTP2_FRAME_TYPE_PUSH_PROMISE]) - sizeof(push_promise.promised_streamid); } - Http2Frame push_promise_frame(HTTP2_FRAME_TYPE_PUSH_PROMISE, stream->get_id(), flags); - push_promise_frame.alloc(buffer_size_index[HTTP2_FRAME_TYPE_PUSH_PROMISE]); + Http2StreamId id = this->get_latest_stream_id_out() + 2; push_promise.promised_streamid = id; - http2_write_push_promise(push_promise, buf, payload_length, push_promise_frame.write()); - push_promise_frame.finalize(sizeof(push_promise.promised_streamid) + payload_length); - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &push_promise_frame); - sent += payload_length; + + Http2PushPromiseFrame push_promise_frame(stream->get_id(), flags, push_promise, buf, payload_length); + this->ua_session->xmit(push_promise_frame); + uint64_t sent = payload_length; // Send CONTINUATION frames flags = 0; @@ -1755,13 +1737,9 @@ Http2ConnectionState::send_push_promise_frame(Http2Stream *stream, URL &url, con if (sent + payload_length == header_blocks_size) { flags |= HTTP2_FLAGS_CONTINUATION_END_HEADERS; } - Http2Frame continuation_frame(HTTP2_FRAME_TYPE_CONTINUATION, stream->get_id(), flags); - continuation_frame.alloc(buffer_size_index[HTTP2_FRAME_TYPE_CONTINUATION]); - http2_write_headers(buf + sent, payload_length, continuation_frame.write()); - continuation_frame.finalize(payload_length); - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &continuation_frame); + + Http2ContinuationFrame continuation(stream->get_id(), flags, buf + sent, payload_length); + this->ua_session->xmit(continuation); sent += payload_length; } ats_free(buf); @@ -1806,12 +1784,6 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec) ++stream_error_count; } - Http2Frame rst_stream(HTTP2_FRAME_TYPE_RST_STREAM, id, 0); - - rst_stream.alloc(buffer_size_index[HTTP2_FRAME_TYPE_RST_STREAM]); - http2_write_rst_stream(static_cast(ec), rst_stream.write()); - rst_stream.finalize(HTTP2_RST_STREAM_LEN); - // change state to closed Http2Stream *stream = find_stream(id); if (stream != nullptr) { @@ -1827,9 +1799,8 @@ Http2ConnectionState::send_rst_stream_frame(Http2StreamId id, Http2ErrorCode ec) } } - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &rst_stream); + Http2RstStreamFrame rst_stream(id, static_cast(ec)); + this->ua_session->xmit(rst_stream); } void @@ -1839,11 +1810,8 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set Http2StreamDebug(ua_session, stream_id, "Send SETTINGS frame"); - Http2Frame settings(HTTP2_FRAME_TYPE_SETTINGS, stream_id, 0); - settings.alloc(buffer_size_index[HTTP2_FRAME_TYPE_SETTINGS]); - - IOVec iov = settings.write(); - uint32_t settings_length = 0; + Http2SettingsParameter params[HTTP2_SETTINGS_MAX]; + size_t params_size = 0; for (int i = HTTP2_SETTINGS_HEADER_TABLE_SIZE; i < HTTP2_SETTINGS_MAX; ++i) { Http2SettingsIdentifier id = static_cast(i); @@ -1851,32 +1819,17 @@ Http2ConnectionState::send_settings_frame(const Http2ConnectionSettings &new_set // Send only difference if (settings_value != server_settings.get(id)) { - const Http2SettingsParameter param = {static_cast(id), settings_value}; + Http2StreamDebug(ua_session, stream_id, " %s : %u", Http2DebugNames::get_settings_param_name(id), settings_value); - // Write settings to send buffer - if (!http2_write_settings(param, iov)) { - this->send_goaway_frame(this->latest_streamid_in, Http2ErrorCode::HTTP2_ERROR_INTERNAL_ERROR); - this->ua_session->set_half_close_local_flag(true); - if (fini_event == nullptr) { - fini_event = this_ethread()->schedule_imm_local((Continuation *)this, HTTP2_SESSION_EVENT_FINI); - } - - return; - } - iov.iov_base = reinterpret_cast(iov.iov_base) + HTTP2_SETTINGS_PARAMETER_LEN; - iov.iov_len -= HTTP2_SETTINGS_PARAMETER_LEN; - settings_length += HTTP2_SETTINGS_PARAMETER_LEN; + params[params_size++] = {static_cast(id), settings_value}; // Update current settings server_settings.set(id, new_settings.get(id)); - - Http2StreamDebug(ua_session, stream_id, " %s : %u", Http2DebugNames::get_settings_param_name(param.id), param.value); } } - settings.finalize(settings_length); - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &settings); + Http2SettingsFrame settings(stream_id, HTTP2_FRAME_NO_FLAG, params, params_size); + this->ua_session->xmit(settings); } void @@ -1884,15 +1837,8 @@ Http2ConnectionState::send_ping_frame(Http2StreamId id, uint8_t flag, const uint { Http2StreamDebug(ua_session, id, "Send PING frame"); - Http2Frame ping(HTTP2_FRAME_TYPE_PING, id, flag); - - ping.alloc(buffer_size_index[HTTP2_FRAME_TYPE_PING]); - http2_write_ping(opaque_data, ping.write()); - ping.finalize(HTTP2_PING_LEN); - - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &ping); + Http2PingFrame ping(id, flag, opaque_data); + this->ua_session->xmit(ping); } // As for gracefull shutdown, TS should process outstanding stream as long as possible. @@ -1908,21 +1854,14 @@ Http2ConnectionState::send_goaway_frame(Http2StreamId id, Http2ErrorCode ec) HTTP2_INCREMENT_THREAD_DYN_STAT(HTTP2_STAT_CONNECTION_ERRORS_COUNT, this_ethread()); } - Http2Frame frame(HTTP2_FRAME_TYPE_GOAWAY, 0, 0); - Http2Goaway goaway; + this->tx_error_code = {ProxyErrorClass::SSN, static_cast(ec)}; + Http2Goaway goaway; goaway.last_streamid = id; goaway.error_code = ec; - frame.alloc(buffer_size_index[HTTP2_FRAME_TYPE_GOAWAY]); - http2_write_goaway(goaway, frame.write()); - frame.finalize(HTTP2_GOAWAY_LEN); - - this->tx_error_code = {ProxyErrorClass::SSN, static_cast(ec)}; - - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &frame); + Http2GoawayFrame frame(goaway); + this->ua_session->xmit(frame); } void @@ -1931,14 +1870,8 @@ Http2ConnectionState::send_window_update_frame(Http2StreamId id, uint32_t size) Http2StreamDebug(ua_session, id, "Send WINDOW_UPDATE frame"); // Create WINDOW_UPDATE frame - Http2Frame window_update(HTTP2_FRAME_TYPE_WINDOW_UPDATE, id, 0x0); - window_update.alloc(buffer_size_index[HTTP2_FRAME_TYPE_WINDOW_UPDATE]); - http2_write_window_update(static_cast(size), window_update.write()); - window_update.finalize(sizeof(uint32_t)); - - // xmit event - SCOPED_MUTEX_LOCK(lock, this->ua_session->mutex, this_ethread()); - this->ua_session->handleEvent(HTTP2_SESSION_EVENT_XMIT, &window_update); + Http2WindowUpdateFrame window_update(id, size); + this->ua_session->xmit(window_update); } void diff --git a/proxy/http2/Http2Frame.cc b/proxy/http2/Http2Frame.cc new file mode 100644 index 00000000000..a731f09e2a8 --- /dev/null +++ b/proxy/http2/Http2Frame.cc @@ -0,0 +1,253 @@ +/** @file + + Http2Frame + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + */ + +#include "Http2Frame.h" + +// +// Http2Frame +// +IOBufferReader * +Http2Frame::reader() const +{ + return this->_ioreader; +} + +const Http2FrameHeader & +Http2Frame::header() const +{ + return this->_hdr; +} + +bool +Http2Frame::is_from_early_data() const +{ + return this->_from_early_data; +} + +// +// DATA Frame +// +int64_t +Http2DataFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + if (this->_reader && this->_payload_len > 0) { + int64_t written = 0; + // Fill current IOBufferBlock as much as possible to reduce SSL_write() calls + while (written < this->_payload_len) { + int64_t read_len = std::min(this->_payload_len - written, this->_reader->block_read_avail()); + written += iobuffer->write(this->_reader->start(), read_len); + this->_reader->consume(read_len); + } + len += written; + } + + return len; +} + +// +// HEADERS Frame +// +int64_t +Http2HeadersFrame::write_to(MIOBuffer *iobuffer) const +{ + // Validation + if (this->_hdr_block_len > Http2::max_frame_size) { + return -1; + } + + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + if (this->_hdr_block && this->_hdr_block_len > 0) { + len += iobuffer->write(this->_hdr_block, this->_hdr_block_len); + } + + return len; +} + +// +// PRIORITY Frame +// +int64_t +Http2PriorityFrame::write_to(MIOBuffer *iobuffer) const +{ + ink_abort("not supported yet"); + + return 0; +} + +// +// RST_STREM Frame +// +int64_t +Http2RstStreamFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t payload[HTTP2_RST_STREAM_LEN]; + http2_write_rst_stream(this->_error_code, make_iovec(payload)); + len += iobuffer->write(payload, sizeof(payload)); + + return len; +} + +// +// SETTINGS Frame +// +int64_t +Http2SettingsFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + for (uint32_t i = 0; i < this->_psize; ++i) { + Http2SettingsParameter *p = this->_params + i; + + uint8_t p_buf[HTTP2_SETTINGS_PARAMETER_LEN]; + http2_write_settings(*p, make_iovec(p_buf)); + len += iobuffer->write(p_buf, sizeof(p_buf)); + } + + return len; +} + +// +// PUSH_PROMISE Frame +// +int64_t +Http2PushPromiseFrame::write_to(MIOBuffer *iobuffer) const +{ + // Validation + if (this->_hdr_block_len > Http2::max_frame_size) { + return -1; + } + + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t p_buf[HTTP2_MAX_FRAME_SIZE]; + http2_write_push_promise(this->_params, this->_hdr_block, this->_hdr_block_len, make_iovec(p_buf)); + len += iobuffer->write(p_buf, sizeof(Http2StreamId) + this->_hdr_block_len); + + return len; +} + +// +// PING Frame +// +int64_t +Http2PingFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t payload[HTTP2_PING_LEN] = {0}; + http2_write_ping(this->_opaque_data, make_iovec(payload)); + len += iobuffer->write(payload, sizeof(payload)); + + return len; +} + +// +// GOAWAY Frame +// +int64_t +Http2GoawayFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t payload[HTTP2_GOAWAY_LEN]; + http2_write_goaway(this->_params, make_iovec(payload)); + len += iobuffer->write(payload, sizeof(payload)); + + return len; +} + +// +// WINDOW_UPDATE Frame +// +int64_t +Http2WindowUpdateFrame::write_to(MIOBuffer *iobuffer) const +{ + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + uint8_t payload[HTTP2_WINDOW_UPDATE_LEN]; + http2_write_window_update(this->_window, make_iovec(payload)); + len += iobuffer->write(payload, sizeof(payload)); + + return len; +} + +// +// CONTINUATION Frame +// +int64_t +Http2ContinuationFrame::write_to(MIOBuffer *iobuffer) const +{ + // Validation + if (this->_hdr_block_len > Http2::max_frame_size) { + return -1; + } + + // Write frame header + uint8_t buf[HTTP2_FRAME_HEADER_LEN]; + http2_write_frame_header(this->_hdr, make_iovec(buf)); + int64_t len = iobuffer->write(buf, sizeof(buf)); + + // Write frame payload + if (this->_hdr_block && this->_hdr_block_len > 0) { + len += iobuffer->write(this->_hdr_block, this->_hdr_block_len); + } + + return len; +} diff --git a/proxy/http2/Http2Frame.h b/proxy/http2/Http2Frame.h new file mode 100644 index 00000000000..1715dd59368 --- /dev/null +++ b/proxy/http2/Http2Frame.h @@ -0,0 +1,252 @@ +/** @file + + Http2Frame + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + */ + +#pragma once + +#include "P_Net.h" + +#include "HTTP2.h" + +/** + Incoming HTTP/2 Frame + */ +class Http2Frame +{ +public: + Http2Frame(const Http2FrameHeader &h, IOBufferReader *r, bool e = false) : _hdr(h), _ioreader(r), _from_early_data(e) {} + + // Accessor + IOBufferReader *reader() const; + const Http2FrameHeader &header() const; + bool is_from_early_data() const; + +private: + Http2FrameHeader _hdr; + IOBufferReader *_ioreader = nullptr; + bool _from_early_data = false; +}; + +/** + Outgoing HTTP/2 Frame + */ +class Http2TxFrame +{ +public: + Http2TxFrame(const Http2FrameHeader &h) : _hdr(h) {} + + // Don't allocate on heap + void *operator new(std::size_t) = delete; + void *operator new[](std::size_t) = delete; + + virtual int64_t write_to(MIOBuffer *iobuffer) const = 0; + +protected: + Http2FrameHeader _hdr; +}; + +/** + DATA Frame + */ +class Http2DataFrame : public Http2TxFrame +{ +public: + Http2DataFrame(Http2StreamId stream_id, uint8_t flags, IOBufferReader *r, uint32_t l) + : Http2TxFrame({l, HTTP2_FRAME_TYPE_DATA, flags, stream_id}), _reader(r), _payload_len(l) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + IOBufferReader *_reader = nullptr; + uint32_t _payload_len = 0; +}; + +/** + HEADERS Frame + + TODO: support priority info & padding using Http2HeadersParameter + */ +class Http2HeadersFrame : public Http2TxFrame +{ +public: + Http2HeadersFrame(Http2StreamId stream_id, uint8_t flags, uint8_t *h, uint32_t l) + : Http2TxFrame({l, HTTP2_FRAME_TYPE_HEADERS, flags, stream_id}), _hdr_block(h), _hdr_block_len(l) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + uint8_t *_hdr_block = nullptr; + uint32_t _hdr_block_len = 0; +}; + +/** + PRIORITY Frame + + TODO: implement xmit function + */ +class Http2PriorityFrame : public Http2TxFrame +{ +public: + Http2PriorityFrame(Http2StreamId stream_id, uint8_t flags, Http2Priority p) + : Http2TxFrame({HTTP2_PRIORITY_LEN, HTTP2_FRAME_TYPE_PRIORITY, flags, stream_id}), _params(p) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + Http2Priority _params; +}; + +/** + RST_STREAM Frame + */ +class Http2RstStreamFrame : public Http2TxFrame +{ +public: + Http2RstStreamFrame(Http2StreamId stream_id, uint32_t e) + : Http2TxFrame({HTTP2_RST_STREAM_LEN, HTTP2_FRAME_TYPE_RST_STREAM, HTTP2_FRAME_NO_FLAG, stream_id}), _error_code(e) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + uint32_t _error_code; +}; + +/** + SETTINGS Frame + */ +class Http2SettingsFrame : public Http2TxFrame +{ +public: + Http2SettingsFrame(Http2StreamId stream_id, uint8_t flags) : Http2TxFrame({0, HTTP2_FRAME_TYPE_SETTINGS, flags, stream_id}) {} + Http2SettingsFrame(Http2StreamId stream_id, uint8_t flags, Http2SettingsParameter *p, uint32_t s) + : Http2TxFrame({static_cast(HTTP2_SETTINGS_PARAMETER_LEN) * s, HTTP2_FRAME_TYPE_SETTINGS, flags, stream_id}), + _params(p), + _psize(s) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + Http2SettingsParameter *_params = nullptr; + uint32_t _psize = 0; +}; + +/** + PUSH_PROMISE Frame + + TODO: support padding + */ +class Http2PushPromiseFrame : public Http2TxFrame +{ +public: + Http2PushPromiseFrame(Http2StreamId stream_id, uint8_t flags, Http2PushPromise p, uint8_t *h, uint32_t l) + : Http2TxFrame({l, HTTP2_FRAME_TYPE_PUSH_PROMISE, flags, stream_id}), _params(p), _hdr_block(h), _hdr_block_len(l) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + Http2PushPromise _params; + uint8_t *_hdr_block = nullptr; + uint32_t _hdr_block_len = 0; +}; + +/** + PING Frame + */ +class Http2PingFrame : public Http2TxFrame +{ +public: + Http2PingFrame(Http2StreamId stream_id, uint8_t flags, const uint8_t *data) + : Http2TxFrame({HTTP2_PING_LEN, HTTP2_FRAME_TYPE_PING, flags, stream_id}), _opaque_data(data) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + const uint8_t *_opaque_data; +}; + +/** + GOAWAY Frame + + TODO: support Additional Debug Data + */ +class Http2GoawayFrame : public Http2TxFrame +{ +public: + Http2GoawayFrame(Http2Goaway p) + : Http2TxFrame({HTTP2_GOAWAY_LEN, HTTP2_FRAME_TYPE_GOAWAY, HTTP2_FRAME_NO_FLAG, HTTP2_CONNECTION_CONTROL_STRTEAM}), _params(p) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + Http2Goaway _params; +}; + +/** + WINDOW_UPDATE Frame + */ +class Http2WindowUpdateFrame : public Http2TxFrame +{ +public: + Http2WindowUpdateFrame(Http2StreamId stream_id, uint32_t w) + : Http2TxFrame({HTTP2_WINDOW_UPDATE_LEN, HTTP2_FRAME_TYPE_WINDOW_UPDATE, HTTP2_FRAME_NO_FLAG, stream_id}), _window(w) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + uint32_t _window = 0; +}; + +/** + CONTINUATION Frame + */ +class Http2ContinuationFrame : public Http2TxFrame +{ +public: + Http2ContinuationFrame(Http2StreamId stream_id, uint8_t flags, uint8_t *h, uint32_t l) + : Http2TxFrame({l, HTTP2_FRAME_TYPE_CONTINUATION, flags, stream_id}), _hdr_block(h), _hdr_block_len(l) + { + } + + int64_t write_to(MIOBuffer *iobuffer) const override; + +private: + uint8_t *_hdr_block = nullptr; + uint32_t _hdr_block_len = 0; +}; diff --git a/proxy/http2/Http2Stream.cc b/proxy/http2/Http2Stream.cc index ecbec35a37d..77de1d12c69 100644 --- a/proxy/http2/Http2Stream.cc +++ b/proxy/http2/Http2Stream.cc @@ -384,7 +384,9 @@ Http2Stream::do_io_close(int /* flags */) if (_proxy_ssn && this->is_client_state_writeable()) { // Make sure any trailing end of stream frames are sent // Wee will be removed at send_data_frames or closing connection phase - static_cast(_proxy_ssn)->connection_state.send_data_frames(this); + Http2ClientSession *h2_proxy_ssn = static_cast(this->_proxy_ssn); + SCOPED_MUTEX_LOCK(lock, h2_proxy_ssn->connection_state.mutex, this_ethread()); + h2_proxy_ssn->connection_state.send_data_frames(this); } clear_timers(); diff --git a/proxy/http2/Makefile.am b/proxy/http2/Makefile.am index 3b33d361227..6ca54fe9bf9 100644 --- a/proxy/http2/Makefile.am +++ b/proxy/http2/Makefile.am @@ -38,6 +38,8 @@ libhttp2_a_SOURCES = \ HPACK.h \ HTTP2.cc \ HTTP2.h \ + Http2Frame.cc \ + Http2Frame.h \ Http2ClientSession.cc \ Http2ClientSession.h \ Http2ConnectionState.cc \