| /** @file |
| |
| OpenSSL native QUIC listener support. |
| |
| @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 "P_QUICPacketHandler.h" |
| #include "P_QUICNetProcessor.h" |
| #include "P_QUICNetVConnection.h" |
| #include "P_SSLCertLookup.h" |
| #include "P_UnixNet.h" |
| #include "iocore/net/QUICMultiCertConfigLoader.h" |
| #include "iocore/net/quic/QUICConfig.h" |
| #include "tscore/ink_atomic.h" |
| #include "tscore/ink_sock.h" |
| |
| #include <openssl/bio.h> |
| #include <openssl/crypto.h> |
| #include <openssl/err.h> |
| #include <openssl/ssl.h> |
| |
| #include <cerrno> |
| #include <algorithm> |
| |
| namespace |
| { |
| constexpr ink_hrtime OPENSSL_QUIC_EVENT_INTERVAL = HRTIME_MSECONDS(2); |
| |
| DbgCtl dbg_ctl_openssl_quic{"openssl_quic"}; |
| |
| struct PeerCaptureBioState { |
| BIO *inner{nullptr}; |
| IpEndpoint last_peer; |
| bool have_last_peer{false}; |
| }; |
| |
| thread_local PeerCaptureBioState *active_peer_capture = nullptr; |
| |
| void |
| free_quic_peer_ex_data(void *, void *ptr, CRYPTO_EX_DATA *, int, long, void *) |
| { |
| delete static_cast<IpEndpoint *>(ptr); |
| } |
| |
| int |
| quic_peer_ex_data_index() |
| { |
| static int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, free_quic_peer_ex_data); |
| |
| return index; |
| } |
| |
| bool |
| bio_addr_to_ip_endpoint(IpEndpoint &endpoint, BIO_ADDR const *bio_addr) |
| { |
| if (bio_addr == nullptr) { |
| return false; |
| } |
| |
| in_port_t const port = BIO_ADDR_rawport(bio_addr); |
| if (BIO_ADDR_family(bio_addr) == AF_INET) { |
| in_addr_t addr = 0; |
| size_t len = sizeof(addr); |
| |
| if (BIO_ADDR_rawaddress(bio_addr, &addr, &len) != 1 || len != sizeof(addr)) { |
| return false; |
| } |
| |
| ats_ip4_set(&endpoint, addr, port); |
| return true; |
| } |
| |
| if (BIO_ADDR_family(bio_addr) == AF_INET6) { |
| in6_addr addr; |
| size_t len = sizeof(addr); |
| |
| if (BIO_ADDR_rawaddress(bio_addr, &addr, &len) != 1 || len != sizeof(addr)) { |
| return false; |
| } |
| |
| ats_ip6_set(&endpoint, addr, port); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| PeerCaptureBioState * |
| peer_capture_state(BIO *bio) |
| { |
| return static_cast<PeerCaptureBioState *>(BIO_get_data(bio)); |
| } |
| |
| BIO_MSG * |
| bio_msg_at(BIO_MSG *msg, size_t stride, size_t index) |
| { |
| return reinterpret_cast<BIO_MSG *>(reinterpret_cast<char *>(msg) + (stride * index)); |
| } |
| |
| void |
| update_last_peer(PeerCaptureBioState &state, BIO_MSG const &msg) |
| { |
| IpEndpoint peer; |
| if (msg.peer != nullptr && bio_addr_to_ip_endpoint(peer, msg.peer)) { |
| state.last_peer = peer; |
| state.have_last_peer = true; |
| active_peer_capture = &state; |
| } |
| } |
| |
| void |
| copy_retry_flags(BIO *bio, BIO *inner) |
| { |
| BIO_clear_retry_flags(bio); |
| BIO_set_flags(bio, BIO_get_retry_flags(inner)); |
| BIO_set_retry_reason(bio, BIO_get_retry_reason(inner)); |
| } |
| |
| int |
| peer_capture_bio_create(BIO *bio) |
| { |
| BIO_set_init(bio, 0); |
| BIO_set_data(bio, nullptr); |
| BIO_set_shutdown(bio, 1); |
| |
| return 1; |
| } |
| |
| int |
| peer_capture_bio_destroy(BIO *bio) |
| { |
| if (bio == nullptr) { |
| return 0; |
| } |
| |
| auto *state = peer_capture_state(bio); |
| if (state != nullptr) { |
| if (active_peer_capture == state) { |
| active_peer_capture = nullptr; |
| } |
| BIO_free(state->inner); |
| delete state; |
| BIO_set_data(bio, nullptr); |
| } |
| BIO_set_init(bio, 0); |
| |
| return 1; |
| } |
| |
| int |
| peer_capture_bio_write(BIO *bio, char const *data, int len) |
| { |
| auto *state = peer_capture_state(bio); |
| if (state == nullptr || state->inner == nullptr) { |
| return 0; |
| } |
| |
| int const result = BIO_write(state->inner, data, len); |
| copy_retry_flags(bio, state->inner); |
| |
| return result; |
| } |
| |
| int |
| peer_capture_bio_write_ex(BIO *bio, char const *data, size_t len, size_t *written) |
| { |
| auto *state = peer_capture_state(bio); |
| if (state == nullptr || state->inner == nullptr) { |
| return 0; |
| } |
| |
| int const result = BIO_write_ex(state->inner, data, len, written); |
| copy_retry_flags(bio, state->inner); |
| |
| return result; |
| } |
| |
| int |
| peer_capture_bio_sendmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t num_msg, uint64_t flags, size_t *num_processed) |
| { |
| auto *state = peer_capture_state(bio); |
| if (state == nullptr || state->inner == nullptr) { |
| return 0; |
| } |
| |
| int const result = BIO_sendmmsg(state->inner, msg, stride, num_msg, flags, num_processed); |
| copy_retry_flags(bio, state->inner); |
| |
| return result; |
| } |
| |
| int |
| peer_capture_bio_read(BIO *bio, char *data, int len) |
| { |
| auto *state = peer_capture_state(bio); |
| if (state == nullptr || state->inner == nullptr) { |
| return 0; |
| } |
| |
| int const result = BIO_read(state->inner, data, len); |
| copy_retry_flags(bio, state->inner); |
| |
| return result; |
| } |
| |
| int |
| peer_capture_bio_read_ex(BIO *bio, char *data, size_t len, size_t *read_bytes) |
| { |
| auto *state = peer_capture_state(bio); |
| if (state == nullptr || state->inner == nullptr) { |
| return 0; |
| } |
| |
| int const result = BIO_read_ex(state->inner, data, len, read_bytes); |
| copy_retry_flags(bio, state->inner); |
| |
| return result; |
| } |
| |
| int |
| peer_capture_bio_recvmmsg(BIO *bio, BIO_MSG *msg, size_t stride, size_t num_msg, uint64_t flags, size_t *num_processed) |
| { |
| auto *state = peer_capture_state(bio); |
| if (state == nullptr || state->inner == nullptr) { |
| return 0; |
| } |
| |
| size_t const read_limit = std::min<size_t>(num_msg, 1); |
| |
| int const result = BIO_recvmmsg(state->inner, msg, stride, read_limit, flags, num_processed); |
| copy_retry_flags(bio, state->inner); |
| |
| if (result == 1 && num_processed != nullptr && *num_processed > 0) { |
| update_last_peer(*state, *bio_msg_at(msg, stride, 0)); |
| } |
| |
| return result; |
| } |
| |
| long |
| peer_capture_bio_ctrl(BIO *bio, int cmd, long larg, void *parg) |
| { |
| auto *state = peer_capture_state(bio); |
| if (state == nullptr || state->inner == nullptr) { |
| return 0; |
| } |
| |
| return BIO_ctrl(state->inner, cmd, larg, parg); |
| } |
| |
| long |
| peer_capture_bio_callback_ctrl(BIO *bio, int cmd, BIO_info_cb *cb) |
| { |
| auto *state = peer_capture_state(bio); |
| if (state == nullptr || state->inner == nullptr) { |
| return 0; |
| } |
| |
| return BIO_callback_ctrl(state->inner, cmd, cb); |
| } |
| |
| BIO_METHOD * |
| peer_capture_bio_method() |
| { |
| static BIO_METHOD *method = []() -> BIO_METHOD * { |
| BIO_METHOD *m = BIO_meth_new(BIO_TYPE_SOURCE_SINK | BIO_get_new_index(), "ATS OpenSSL QUIC peer capture"); |
| if (m == nullptr) { |
| return nullptr; |
| } |
| |
| BIO_meth_set_create(m, peer_capture_bio_create); |
| BIO_meth_set_destroy(m, peer_capture_bio_destroy); |
| BIO_meth_set_write(m, peer_capture_bio_write); |
| BIO_meth_set_write_ex(m, peer_capture_bio_write_ex); |
| BIO_meth_set_sendmmsg(m, peer_capture_bio_sendmmsg); |
| BIO_meth_set_read(m, peer_capture_bio_read); |
| BIO_meth_set_read_ex(m, peer_capture_bio_read_ex); |
| BIO_meth_set_recvmmsg(m, peer_capture_bio_recvmmsg); |
| BIO_meth_set_ctrl(m, peer_capture_bio_ctrl); |
| BIO_meth_set_callback_ctrl(m, peer_capture_bio_callback_ctrl); |
| |
| return m; |
| }(); |
| |
| return method; |
| } |
| |
| BIO * |
| new_peer_capture_bio(int fd) |
| { |
| BIO *inner = BIO_new_dgram(fd, BIO_NOCLOSE); |
| if (inner == nullptr) { |
| return nullptr; |
| } |
| |
| BIO *bio = BIO_new(peer_capture_bio_method()); |
| if (bio == nullptr) { |
| BIO_free(inner); |
| return nullptr; |
| } |
| |
| auto *state = new PeerCaptureBioState; |
| state->inner = inner; |
| BIO_set_data(bio, state); |
| BIO_set_init(bio, 1); |
| |
| return bio; |
| } |
| |
| int |
| new_pending_quic_connection_cb(SSL_CTX *, SSL *new_ssl, void *) |
| { |
| auto *capture = active_peer_capture; |
| if (capture == nullptr || !capture->have_last_peer) { |
| return 1; |
| } |
| |
| int const index = quic_peer_ex_data_index(); |
| if (index < 0) { |
| return 0; |
| } |
| |
| delete static_cast<IpEndpoint *>(SSL_get_ex_data(new_ssl, index)); |
| auto *peer = new IpEndpoint(capture->last_peer); |
| if (SSL_set_ex_data(new_ssl, index, peer) != 1) { |
| delete peer; |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| bool |
| get_bio_peer_addr(BIO *bio, IpEndpoint &remote_addr) |
| { |
| if (bio == nullptr) { |
| return false; |
| } |
| |
| BIO_ADDR *peer_addr = BIO_ADDR_new(); |
| if (peer_addr == nullptr) { |
| return false; |
| } |
| |
| bool const success = BIO_dgram_get_peer(bio, peer_addr) == 1 && bio_addr_to_ip_endpoint(remote_addr, peer_addr); |
| BIO_ADDR_free(peer_addr); |
| |
| return success; |
| } |
| |
| bool |
| get_quic_peer_addr(SSL *ssl, IpEndpoint &remote_addr) |
| { |
| int const index = quic_peer_ex_data_index(); |
| if (index >= 0) { |
| auto *peer = static_cast<IpEndpoint *>(SSL_get_ex_data(ssl, index)); |
| if (peer != nullptr) { |
| remote_addr = *peer; |
| return true; |
| } |
| } |
| |
| return get_bio_peer_addr(SSL_get_rbio(ssl), remote_addr) || get_bio_peer_addr(SSL_get_wbio(ssl), remote_addr); |
| } |
| |
| } // end anonymous namespace |
| |
| QUICPacketHandler::QUICPacketHandler() |
| { |
| this->_closed_con_collector = std::make_unique<QUICClosedConCollector>(); |
| this->_closed_con_collector->mutex = new_ProxyMutex(); |
| } |
| |
| QUICPacketHandler::~QUICPacketHandler() |
| { |
| if (this->_collector_event != nullptr) { |
| this->_collector_event->cancel(); |
| this->_collector_event = nullptr; |
| } |
| } |
| |
| void |
| QUICPacketHandler::close_connection(QUICNetVConnection *conn) |
| { |
| int isin = ink_atomic_swap(&conn->in_closed_queue, 1); |
| if (!isin) { |
| this->_closed_con_collector->closedQueue.push(conn); |
| } |
| } |
| |
| void |
| QUICPacketHandler::send_packet(UDPConnection * /* udp_con ATS_UNUSED */, IpEndpoint & /* addr ATS_UNUSED */, |
| Ptr<IOBufferBlock> /* udp_payload ATS_UNUSED */, uint16_t /* segment_size ATS_UNUSED */, |
| struct timespec * /* send_at_hint ATS_UNUSED */) |
| { |
| } |
| |
| QUICPacketHandlerIn::QUICPacketHandlerIn(NetProcessor::AcceptOptions const &opt) : NetAccept(opt), QUICPacketHandler() |
| { |
| this->mutex = new_ProxyMutex(); |
| } |
| |
| QUICPacketHandlerIn::~QUICPacketHandlerIn() |
| { |
| if (this->_event != nullptr) { |
| this->_event->cancel(); |
| this->_event = nullptr; |
| } |
| if (this->_listener != nullptr) { |
| SSL_free(this->_listener); |
| this->_listener = nullptr; |
| } |
| } |
| |
| NetProcessor * |
| QUICPacketHandlerIn::getNetProcessor() const |
| { |
| return &quic_NetProcessor; |
| } |
| |
| NetAccept * |
| QUICPacketHandlerIn::clone() const |
| { |
| return new QUICPacketHandlerIn(this->opt); |
| } |
| |
| int |
| QUICPacketHandlerIn::acceptEvent(int /* event ATS_UNUSED */, void * /* data ATS_UNUSED */) |
| { |
| this->setThreadAffinity(this_ethread()); |
| |
| if (this->_collector_event == nullptr) { |
| this->_collector_event = this_ethread()->schedule_every(this->_closed_con_collector.get(), HRTIME_MSECONDS(100)); |
| } |
| |
| if (this->_listener == nullptr && !this->_start_listener()) { |
| return EVENT_DONE; |
| } |
| |
| this->_event = nullptr; |
| this->_service_listener(); |
| this->_schedule_event(); |
| |
| return EVENT_CONT; |
| } |
| |
| void |
| QUICPacketHandlerIn::init_accept(EThread *t) |
| { |
| SET_HANDLER(&QUICPacketHandlerIn::acceptEvent); |
| |
| if (t == nullptr) { |
| t = eventProcessor.assign_thread(ET_NET); |
| } |
| |
| if (!this->action_->continuation->mutex) { |
| this->action_->continuation->mutex = t->mutex; |
| this->action_->mutex = t->mutex; |
| } |
| |
| this->mutex = get_NetHandler(t)->mutex; |
| t->schedule_imm(this); |
| } |
| |
| Continuation * |
| QUICPacketHandlerIn::_get_continuation() |
| { |
| return this; |
| } |
| |
| void |
| QUICPacketHandlerIn::_recv_packet(int /* event ATS_UNUSED */, UDPPacket * /* udpPacket ATS_UNUSED */) |
| { |
| } |
| |
| bool |
| QUICPacketHandlerIn::_start_listener() |
| { |
| if (!this->server.sock.is_ok()) { |
| this->server.sock = UnixSocket{this->server.accept_addr.sa.sa_family, SOCK_DGRAM, 0}; |
| if (!this->server.sock.is_ok()) { |
| Error("failed to create OpenSSL QUIC UDP socket: %s", strerror(errno)); |
| return false; |
| } |
| |
| if (this->server.accept_addr.sa.sa_family == AF_INET6 && this->server.sock.enable_option(IPPROTO_IPV6, IPV6_V6ONLY) < 0) { |
| Error("failed to set IPV6_V6ONLY on OpenSSL QUIC socket: %s", strerror(errno)); |
| return false; |
| } |
| if (this->server.sock.enable_option(SOL_SOCKET, SO_REUSEADDR) < 0) { |
| Error("failed to set SO_REUSEADDR on OpenSSL QUIC socket: %s", strerror(errno)); |
| return false; |
| } |
| if (this->server.sock.bind(&this->server.accept_addr.sa, ats_ip_size(&this->server.accept_addr.sa)) < 0) { |
| Error("failed to bind OpenSSL QUIC socket: %s", strerror(errno)); |
| return false; |
| } |
| } |
| |
| if (this->server.sock.set_nonblocking() < 0) { |
| Error("failed to make OpenSSL QUIC socket nonblocking: %s", strerror(errno)); |
| return false; |
| } |
| if (this->opt.recv_bufsize > 0) { |
| this->server.sock.set_rcvbuf_size(this->opt.recv_bufsize); |
| } |
| if (this->opt.send_bufsize > 0) { |
| this->server.sock.set_sndbuf_size(this->opt.send_bufsize); |
| } |
| |
| QUICCertConfig::scoped_config server_cert; |
| QUICConfig::scoped_config params; |
| auto default_ctx = server_cert->defaultContext(); |
| uint64_t listener_flags = 0; |
| if (!params->stateless_retry()) { |
| listener_flags |= SSL_LISTENER_FLAG_NO_VALIDATE; |
| } |
| |
| SSL_CTX_set_new_pending_conn_cb(default_ctx.get(), new_pending_quic_connection_cb, nullptr); |
| |
| this->_listener = SSL_new_listener(default_ctx.get(), listener_flags); |
| if (this->_listener == nullptr) { |
| Error("failed to create OpenSSL QUIC listener"); |
| return false; |
| } |
| |
| BIO *bio = new_peer_capture_bio(this->server.sock.get_fd()); |
| if (bio == nullptr) { |
| Error("failed to create OpenSSL QUIC peer capture BIO"); |
| return false; |
| } |
| SSL_set_bio(this->_listener, bio, bio); |
| if (SSL_get_rbio(this->_listener) == nullptr || SSL_get_wbio(this->_listener) == nullptr) { |
| Error("failed to configure OpenSSL QUIC listener socket"); |
| return false; |
| } |
| SSL_set_blocking_mode(this->_listener, 0); |
| SSL_set_event_handling_mode(this->_listener, SSL_VALUE_EVENT_HANDLING_MODE_EXPLICIT); |
| |
| SSL_set_feature_request_uint(this->_listener, SSL_VALUE_QUIC_IDLE_TIMEOUT, params->no_activity_timeout_in()); |
| SSL_set_feature_request_uint(this->_listener, SSL_VALUE_QUIC_STREAM_BIDI_LOCAL_AVAIL, params->initial_max_streams_bidi_in()); |
| SSL_set_feature_request_uint(this->_listener, SSL_VALUE_QUIC_STREAM_UNI_LOCAL_AVAIL, params->initial_max_streams_uni_in()); |
| SSL_set_incoming_stream_policy(this->_listener, SSL_INCOMING_STREAM_POLICY_ACCEPT, 0); |
| |
| if (SSL_listen(this->_listener) != 1) { |
| Error("failed to start OpenSSL QUIC listener"); |
| return false; |
| } |
| |
| Dbg(dbg_ctl_openssl_quic, "OpenSSL QUIC listener started on fd %d", this->server.sock.get_fd()); |
| return true; |
| } |
| |
| void |
| QUICPacketHandlerIn::_service_listener() |
| { |
| SSL_handle_events(this->_listener); |
| |
| while (SSL *ssl = SSL_accept_connection(this->_listener, SSL_ACCEPT_CONNECTION_NO_BLOCK)) { |
| SSL_set_blocking_mode(ssl, 0); |
| SSL_set_event_handling_mode(ssl, SSL_VALUE_EVENT_HANDLING_MODE_IMPLICIT); |
| SSL_set_incoming_stream_policy(ssl, SSL_INCOMING_STREAM_POLICY_ACCEPT, 0); |
| SSL_set_default_stream_mode(ssl, SSL_DEFAULT_STREAM_MODE_NONE); |
| |
| auto *vc = static_cast<QUICNetVConnection *>(getNetProcessor()->allocate_vc(this_ethread())); |
| if (vc == nullptr) { |
| SSL_free(ssl); |
| continue; |
| } |
| |
| IpEndpoint remote_addr; |
| if (!get_quic_peer_addr(ssl, remote_addr)) { |
| remote_addr.setToAnyAddr(this->server.accept_addr.sa.sa_family); |
| } |
| |
| vc->init(ssl, this); |
| vc->id = net_next_connection_number(); |
| vc->set_quic_endpoints(this->server.accept_addr, remote_addr); |
| vc->submit_time = ink_get_hrtime(); |
| vc->thread = this_ethread(); |
| vc->mutex = get_NetHandler(this_ethread())->mutex; |
| vc->action_ = *this->action_; |
| vc->set_is_transparent(this->opt.f_inbound_transparent); |
| vc->set_context(NET_VCONNECTION_IN); |
| vc->options.ip_proto = NetVCOptions::USE_UDP; |
| vc->options.ip_family = this->server.accept_addr.sa.sa_family; |
| this_ethread()->schedule_imm(vc, EVENT_NONE, nullptr); |
| } |
| } |
| |
| void |
| QUICPacketHandlerIn::_schedule_event() |
| { |
| if (this->_event == nullptr) { |
| this->_event = this_ethread()->schedule_in(this, OPENSSL_QUIC_EVENT_INTERVAL); |
| } |
| } |
| |
| void |
| QUICPacketHandlerOut::init(QUICNetVConnection * /* vc ATS_UNUSED */) |
| { |
| } |
| |
| Continuation * |
| QUICPacketHandlerOut::_get_continuation() |
| { |
| return this; |
| } |
| |
| void |
| QUICPacketHandlerOut::_recv_packet(int /* event ATS_UNUSED */, UDPPacket * /* udp_packet ATS_UNUSED */) |
| { |
| } |