blob: 8a65c288c0a17cb679f867ca6cd12078abe202bc [file] [log] [blame]
/** @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-27"sv);
static constexpr std::string_view H3_ALPN_PROTO_LIST("\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;
}