| /** @file | |
| Test TLS handshakes that pause inside an OpenSSL async job. | |
| @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 <ts/ts.h> | |
| #include <openssl/async.h> | |
| #include <openssl/crypto.h> | |
| #include <cerrno> | |
| #include <cstdint> | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <pthread.h> | |
| #include <unistd.h> | |
| #define PLUGIN_NAME "async_handshake" | |
| #define PCP "[" PLUGIN_NAME "] " | |
| namespace | |
| { | |
| DbgCtl dbg_ctl{PLUGIN_NAME}; | |
| char async_hook_key; | |
| int delay_ms = 100; | |
| void | |
| wait_cleanup(ASYNC_WAIT_CTX * /* ctx ATS_UNUSED */, const void * /* key ATS_UNUSED */, OSSL_ASYNC_FD read_fd, void *write_fd_ptr) | |
| { | |
| auto *write_fd = static_cast<OSSL_ASYNC_FD *>(write_fd_ptr); | |
| close(read_fd); | |
| close(*write_fd); | |
| OPENSSL_free(write_fd); | |
| } | |
| void * | |
| wake_async_job(void *arg) | |
| { | |
| auto signal_fd = static_cast<OSSL_ASYNC_FD>(reinterpret_cast<intptr_t>(arg)); | |
| char buf = 'X'; | |
| usleep(delay_ms * 1000); | |
| if (write(signal_fd, &buf, sizeof(buf)) < 0) { | |
| fprintf(stderr, PCP "failed to send async wake signal to %d, errno=%d\n", signal_fd, errno); | |
| } else { | |
| fprintf(stderr, PCP "sent async wake signal to %d\n", signal_fd); | |
| } | |
| return nullptr; | |
| } | |
| bool | |
| install_wait_fd(ASYNC_WAIT_CTX *wait_ctx, OSSL_ASYNC_FD &read_fd) | |
| { | |
| OSSL_ASYNC_FD *write_fd = nullptr; | |
| if (ASYNC_WAIT_CTX_get_fd(wait_ctx, &async_hook_key, &read_fd, reinterpret_cast<void **>(&write_fd))) { | |
| return true; | |
| } | |
| OSSL_ASYNC_FD pipe_fds[2] = {-1, -1}; | |
| write_fd = static_cast<OSSL_ASYNC_FD *>(OPENSSL_malloc(sizeof(*write_fd))); | |
| if (write_fd == nullptr) { | |
| TSError(PCP "failed to allocate async wait fd storage"); | |
| return false; | |
| } | |
| if (pipe(pipe_fds) < 0) { | |
| TSError(PCP "failed to create async wait pipe: errno=%d", errno); | |
| OPENSSL_free(write_fd); | |
| return false; | |
| } | |
| *write_fd = pipe_fds[1]; | |
| if (!ASYNC_WAIT_CTX_set_wait_fd(wait_ctx, &async_hook_key, pipe_fds[0], write_fd, wait_cleanup)) { | |
| TSError(PCP "failed to install async wait fd"); | |
| close(pipe_fds[0]); | |
| close(pipe_fds[1]); | |
| OPENSSL_free(write_fd); | |
| return false; | |
| } | |
| pthread_t thread_id; | |
| if (pthread_create(&thread_id, nullptr, wake_async_job, reinterpret_cast<void *>(static_cast<intptr_t>(*write_fd))) == 0) { | |
| pthread_detach(thread_id); | |
| } else if (write(*write_fd, "X", 1) < 0) { | |
| TSError(PCP "failed to start async wake thread and immediate wake failed: errno=%d", errno); | |
| return false; | |
| } | |
| read_fd = pipe_fds[0]; | |
| return true; | |
| } | |
| void | |
| pause_current_async_job(TSVConn ssl_vc) | |
| { | |
| ASYNC_JOB *job = ASYNC_get_current_job(); | |
| if (job == nullptr) { | |
| TSError(PCP "SSL cert hook is not running in an OpenSSL async job"); | |
| TSVConnReenable(ssl_vc); | |
| return; | |
| } | |
| ASYNC_WAIT_CTX *wait_ctx = ASYNC_get_wait_ctx(job); | |
| if (wait_ctx == nullptr) { | |
| TSError(PCP "OpenSSL async job has no wait context"); | |
| TSVConnReenable(ssl_vc); | |
| return; | |
| } | |
| OSSL_ASYNC_FD read_fd = -1; | |
| if (!install_wait_fd(wait_ctx, read_fd)) { | |
| TSVConnReenable(ssl_vc); | |
| return; | |
| } | |
| TSVConnReenable(ssl_vc); | |
| fprintf(stderr, PCP "pausing OpenSSL async job\n"); | |
| if (!ASYNC_pause_job()) { | |
| TSError(PCP "ASYNC_pause_job failed"); | |
| return; | |
| } | |
| fprintf(stderr, PCP "resumed OpenSSL async job\n"); | |
| } | |
| int | |
| handle_ssl_cert(TSCont /* cont ATS_UNUSED */, TSEvent event, void *edata) | |
| { | |
| TSVConn ssl_vc = static_cast<TSVConn>(edata); | |
| if (event == TS_EVENT_SSL_CERT) { | |
| pause_current_async_job(ssl_vc); | |
| } else { | |
| TSVConnReenable(ssl_vc); | |
| } | |
| return TS_SUCCESS; | |
| } | |
| } // namespace | |
| void | |
| TSPluginInit(int argc, const char **argv) | |
| { | |
| TSPluginRegistrationInfo info; | |
| info.plugin_name = PLUGIN_NAME; | |
| info.vendor_name = "Apache Software Foundation"; | |
| info.support_email = "dev@trafficserver.apache.org"; | |
| if (TS_SUCCESS != TSPluginRegister(&info)) { | |
| TSError(PCP "registration failed"); | |
| return; | |
| } | |
| for (int i = 1; i < argc; ++i) { | |
| if (strncmp(argv[i], "-delay-ms=", 10) == 0) { | |
| const char *delay_arg = argv[i] + 10; | |
| char *end = nullptr; | |
| long parsed = strtol(delay_arg, &end, 10); | |
| if (end == delay_arg || *end != '\0' || parsed < 0) { | |
| TSError(PCP "invalid -delay-ms value '%s', keeping default %d", delay_arg, delay_ms); | |
| } else { | |
| delay_ms = static_cast<int>(parsed); | |
| } | |
| } | |
| } | |
| TSCont cb_cert = TSContCreate(handle_ssl_cert, TSMutexCreate()); | |
| if (cb_cert == nullptr) { | |
| TSError(PCP "failed to create SSL cert hook"); | |
| return; | |
| } | |
| TSHttpHookAdd(TS_SSL_CERT_HOOK, cb_cert); | |
| Dbg(dbg_ctl, "plugin online"); | |
| } |