| /** @file |
| * |
| * A brief file description |
| * |
| * @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 "quic_client.h" |
| |
| #include <iostream> |
| #include <fstream> |
| #include <string_view> |
| |
| #include "Http3Transaction.h" |
| #include "P_QUICNetVConnection.h" |
| |
| // OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings) |
| // https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html |
| // Should be integrate with IP_PROTO_TAG_HTTP_QUIC in ts/ink_inet.h ? |
| using namespace std::literals; |
| static constexpr std::string_view HQ_ALPN_PROTO_LIST("\5hq-29\5hq-27"sv); |
| static constexpr std::string_view H3_ALPN_PROTO_LIST("\5h3-29\5h3-27"sv); |
| |
| QUICClient::QUICClient(const QUICClientConfig *config) : Continuation(new_ProxyMutex()), _config(config) |
| { |
| SET_HANDLER(&QUICClient::start); |
| } |
| |
| QUICClient::~QUICClient() |
| { |
| freeaddrinfo(this->_remote_addr_info); |
| } |
| |
| int |
| QUICClient::start(int, void *) |
| { |
| SET_HANDLER(&QUICClient::state_http_server_open); |
| |
| struct addrinfo hints; |
| |
| memset(&hints, 0, sizeof(struct addrinfo)); |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_socktype = SOCK_DGRAM; |
| hints.ai_flags = 0; |
| hints.ai_protocol = 0; |
| |
| int res = getaddrinfo(this->_config->addr, this->_config->port, &hints, &this->_remote_addr_info); |
| if (res < 0) { |
| Debug("quic_client", "Error: %s (%d)", strerror(errno), errno); |
| return EVENT_DONE; |
| } |
| |
| std::string_view alpn_protos; |
| if (this->_config->http3) { |
| alpn_protos = H3_ALPN_PROTO_LIST; |
| } else { |
| alpn_protos = HQ_ALPN_PROTO_LIST; |
| } |
| |
| for (struct addrinfo *info = this->_remote_addr_info; info != nullptr; info = info->ai_next) { |
| NetVCOptions opt; |
| opt.ip_proto = NetVCOptions::USE_UDP; |
| opt.ip_family = info->ai_family; |
| opt.etype = ET_NET; |
| opt.socket_recv_bufsize = 1048576; |
| opt.socket_send_bufsize = 1048576; |
| opt.alpn_protos = alpn_protos; |
| if (strlen(this->_config->server_name) == 0) { |
| opt.set_sni_servername(this->_config->addr, strnlen(this->_config->addr, 1023)); |
| } else { |
| opt.set_sni_servername(this->_config->server_name, strnlen(this->_config->server_name, 1023)); |
| } |
| |
| SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread()); |
| |
| Action *action = quic_NetProcessor.connect_re(this, info->ai_addr, &opt); |
| if (action == ACTION_RESULT_DONE) { |
| break; |
| } |
| } |
| return EVENT_CONT; |
| } |
| |
| // Similar to HttpSM::state_http_server_open(int event, void *data) |
| int |
| QUICClient::state_http_server_open(int event, void *data) |
| { |
| switch (event) { |
| case NET_EVENT_OPEN: { |
| // TODO: create ProxyServerSession / ProxyServerTransaction |
| Debug("quic_client", "start proxy server ssn/txn"); |
| |
| QUICNetVConnection *conn = static_cast<QUICNetVConnection *>(data); |
| |
| if (this->_config->http0_9) { |
| Http09ClientApp *app = new Http09ClientApp(conn, this->_config); |
| app->start(); |
| } else if (this->_config->http3) { |
| // TODO: see what server session is doing with IpAllow::ACL |
| IpAllow::ACL session_acl; |
| Http3ClientApp *app = new Http3ClientApp(conn, std::move(session_acl), options, this->_config); |
| SCOPED_MUTEX_LOCK(lock, app->mutex, this_ethread()); |
| app->start(); |
| } else { |
| ink_abort("invalid config"); |
| } |
| |
| break; |
| } |
| case NET_EVENT_OPEN_FAILED: { |
| ink_assert(false); |
| break; |
| } |
| case NET_EVENT_ACCEPT: { |
| // do nothing |
| break; |
| } |
| default: |
| ink_assert(false); |
| } |
| |
| return 0; |
| } |
| |
| // |
| // Http09ClientApp |
| // |
| #define Http09ClientAppDebug(fmt, ...) Debug("quic_client_app", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__) |
| #define Http09ClientAppVDebug(fmt, ...) Debug("v_quic_client_app", "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__) |
| |
| Http09ClientApp::Http09ClientApp(QUICNetVConnection *qvc, const QUICClientConfig *config) : QUICApplication(qvc), _config(config) |
| { |
| this->_qc->stream_manager()->set_default_application(this); |
| |
| SET_HANDLER(&Http09ClientApp::main_event_handler); |
| } |
| |
| void |
| Http09ClientApp::start() |
| { |
| if (this->_config->output[0] != 0x0) { |
| this->_filename = this->_config->output; |
| } |
| |
| if (this->_filename) { |
| // Destroy contents if file already exists |
| std::ofstream f_stream(this->_filename, std::ios::binary | std::ios::trunc); |
| } |
| |
| this->_do_http_request(); |
| } |
| |
| void |
| Http09ClientApp::_do_http_request() |
| { |
| QUICStreamId stream_id; |
| QUICConnectionErrorUPtr error = this->_qc->stream_manager()->create_bidi_stream(stream_id); |
| |
| if (error != nullptr) { |
| Error("%s", error->msg); |
| ink_abort("Could not create bidi stream : %s", error->msg); |
| } |
| |
| // TODO: move to transaction |
| char request[1024] = {0}; |
| int request_len = snprintf(request, sizeof(request), "GET %s\r\n", this->_config->path); |
| |
| Http09ClientAppDebug("\n%s", request); |
| |
| QUICStreamIO *stream_io = this->_find_stream_io(stream_id); |
| |
| stream_io->write(reinterpret_cast<uint8_t *>(request), request_len); |
| stream_io->write_done(); |
| stream_io->write_reenable(); |
| } |
| |
| int |
| Http09ClientApp::main_event_handler(int event, Event *data) |
| { |
| Http09ClientAppVDebug("%s (%d)", get_vc_event_name(event), event); |
| |
| VIO *vio = reinterpret_cast<VIO *>(data); |
| QUICStreamIO *stream_io = this->_find_stream_io(vio); |
| |
| if (stream_io == nullptr) { |
| Http09ClientAppDebug("Unknown Stream"); |
| return -1; |
| } |
| |
| switch (event) { |
| case VC_EVENT_READ_READY: |
| case VC_EVENT_READ_COMPLETE: { |
| std::streambuf *default_stream = nullptr; |
| std::ofstream f_stream; |
| |
| if (this->_filename) { |
| default_stream = std::cout.rdbuf(); |
| f_stream = std::ofstream(this->_filename, std::ios::binary | std::ios::app); |
| std::cout.rdbuf(f_stream.rdbuf()); |
| } |
| |
| uint8_t buf[8192] = {0}; |
| int64_t nread; |
| while ((nread = stream_io->read(buf, sizeof(buf))) > 0) { |
| std::cout.write(reinterpret_cast<char *>(buf), nread); |
| } |
| std::cout.flush(); |
| |
| if (this->_filename) { |
| f_stream.close(); |
| std::cout.rdbuf(default_stream); |
| } |
| |
| if (stream_io->is_read_done() && this->_config->close) { |
| // Connection Close Exercise |
| this->_qc->close_quic_connection( |
| QUICConnectionErrorUPtr(new QUICConnectionError(QUICTransErrorCode::NO_ERROR, "Close Exercise"))); |
| } |
| |
| break; |
| } |
| case VC_EVENT_WRITE_READY: |
| case VC_EVENT_WRITE_COMPLETE: |
| break; |
| case VC_EVENT_EOS: |
| case VC_EVENT_ERROR: |
| case VC_EVENT_INACTIVITY_TIMEOUT: |
| case VC_EVENT_ACTIVE_TIMEOUT: |
| ink_assert(false); |
| break; |
| default: |
| break; |
| } |
| |
| return EVENT_CONT; |
| } |
| |
| // |
| // Http3ClientApp |
| // |
| Http3ClientApp::Http3ClientApp(QUICNetVConnection *qvc, IpAllow::ACL &&session_acl, const HttpSessionAccept::Options &options, |
| const QUICClientConfig *config) |
| : super(qvc, std::move(session_acl), options), _config(config) |
| { |
| } |
| |
| Http3ClientApp::~Http3ClientApp() |
| { |
| free_MIOBuffer(this->_req_buf); |
| this->_req_buf = nullptr; |
| |
| free_MIOBuffer(this->_resp_buf); |
| this->_resp_buf = nullptr; |
| |
| delete this->_resp_handler; |
| } |
| |
| void |
| Http3ClientApp::start() |
| { |
| this->_req_buf = new_MIOBuffer(BUFFER_SIZE_INDEX_32K); |
| this->_resp_buf = new_MIOBuffer(BUFFER_SIZE_INDEX_32K); |
| IOBufferReader *resp_buf_reader = _resp_buf->alloc_reader(); |
| |
| this->_resp_handler = new RespHandler(this->_config, resp_buf_reader, [&](void) { |
| if (this->_config->close) { |
| // Connection Close Exercise |
| this->_qc->close_quic_connection( |
| QUICConnectionErrorUPtr(new QUICConnectionError(QUICTransErrorCode::NO_ERROR, "Close Exercise"))); |
| } else if (this->_config->reset) { |
| // Stateless Reset Exercise |
| this->_qc->reset_quic_connection(); |
| } |
| }); |
| |
| super::start(); |
| this->_do_http_request(); |
| } |
| |
| void |
| Http3ClientApp::_do_http_request() |
| { |
| QUICConnectionErrorUPtr error; |
| QUICStreamId stream_id; |
| error = this->_qc->stream_manager()->create_bidi_stream(stream_id); |
| if (error != nullptr) { |
| Error("%s", error->msg); |
| ink_abort("Could not create bidi stream : %s", error->msg); |
| } |
| |
| QUICStreamIO *stream_io = this->_find_stream_io(stream_id); |
| |
| // TODO: create Http3ServerTransaction |
| Http3Transaction *txn = new Http3Transaction(this->_ssn, stream_io); |
| SCOPED_MUTEX_LOCK(lock, txn->mutex, this_ethread()); |
| |
| // TODO: fix below issue with H2 origin conn stuff |
| // Do not call ProxyClientTransaction::new_transaction(), but need to setup txn - e.g. do_io_write / do_io_read |
| VIO *read_vio = txn->do_io_read(this->_resp_handler, INT64_MAX, this->_resp_buf); |
| this->_resp_handler->set_read_vio(read_vio); |
| |
| // Write HTTP Request to write_vio |
| char request[1024] = {0}; |
| std::string format; |
| if (this->_config->path[0] == '/') { |
| format = "GET https://%s%s HTTP/1.1\r\n\r\n"; |
| } else { |
| format = "GET https://%s/%s HTTP/1.1\r\n\r\n"; |
| } |
| |
| const char *authority; |
| if (strlen(this->_config->server_name) == 0) { |
| authority = this->_config->addr; |
| } else { |
| authority = this->_config->server_name; |
| } |
| int request_len = snprintf(request, sizeof(request), format.c_str(), authority, this->_config->path); |
| |
| Http09ClientAppDebug("\n%s", request); |
| |
| // TODO: check write avail size |
| int64_t nbytes = this->_req_buf->write(request, request_len); |
| IOBufferReader *buf_start = this->_req_buf->alloc_reader(); |
| txn->do_io_write(this, nbytes, buf_start); |
| } |
| |
| // |
| // Response Handler |
| // |
| RespHandler::RespHandler(const QUICClientConfig *config, IOBufferReader *reader, std::function<void()> on_complete) |
| : Continuation(new_ProxyMutex()), _config(config), _reader(reader), _on_complete(on_complete) |
| { |
| if (this->_config->output[0] != 0x0) { |
| this->_filename = this->_config->output; |
| } |
| |
| if (this->_filename) { |
| // Destroy contents if file already exists |
| std::ofstream f_stream(this->_filename, std::ios::binary | std::ios::trunc); |
| } |
| |
| SET_HANDLER(&RespHandler::main_event_handler); |
| } |
| |
| void |
| RespHandler::set_read_vio(VIO *vio) |
| { |
| this->_read_vio = vio; |
| } |
| |
| int |
| RespHandler::main_event_handler(int event, Event *data) |
| { |
| Debug("v_http3", "%s", get_vc_event_name(event)); |
| switch (event) { |
| case VC_EVENT_READ_READY: |
| case VC_EVENT_READ_COMPLETE: { |
| std::streambuf *default_stream = nullptr; |
| std::ofstream f_stream; |
| |
| if (this->_filename) { |
| default_stream = std::cout.rdbuf(); |
| f_stream = std::ofstream(this->_filename, std::ios::binary | std::ios::app); |
| std::cout.rdbuf(f_stream.rdbuf()); |
| } |
| |
| uint8_t buf[8192] = {0}; |
| int64_t nread; |
| while ((nread = this->_reader->read(buf, sizeof(buf))) > 0) { |
| std::cout.write(reinterpret_cast<char *>(buf), nread); |
| this->_read_vio->ndone += nread; |
| } |
| std::cout.flush(); |
| |
| if (this->_filename) { |
| f_stream.close(); |
| std::cout.rdbuf(default_stream); |
| } |
| |
| if (event == VC_EVENT_READ_COMPLETE) { |
| this->_on_complete(); |
| } |
| |
| break; |
| } |
| case VC_EVENT_WRITE_READY: |
| case VC_EVENT_WRITE_COMPLETE: |
| default: |
| break; |
| } |
| |
| return EVENT_CONT; |
| } |