| // Copyright 2014 The BoringSSL Authors | |
| // | |
| // Licensed 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 | |
| // | |
| // https://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 "packeted_bio.h" | |
| #include <assert.h> | |
| #include <inttypes.h> | |
| #include <limits.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <algorithm> | |
| #include <functional> | |
| #include <utility> | |
| #include <vector> | |
| #include <openssl/bio.h> | |
| #include <openssl/mem.h> | |
| #include <openssl/span.h> | |
| #include <openssl/ssl.h> | |
| #include "../../crypto/internal.h" | |
| using namespace bssl; | |
| namespace { | |
| constexpr uint8_t kOpcodePacket = 'P'; | |
| constexpr uint8_t kOpcodeTimeout = 'T'; | |
| constexpr uint8_t kOpcodeTimeoutAck = 't'; | |
| constexpr uint8_t kOpcodeMTU = 'M'; | |
| constexpr uint8_t kOpcodeExpectNextTimeout = 'E'; | |
| constexpr uint8_t kOpcodeUpdateTimeout = 'U'; | |
| struct PacketedBio { | |
| PacketedBio(timeval *clock_arg, SSL *ssl_arg) | |
| : clock(clock_arg), ssl(ssl_arg) {} | |
| timeval *clock = nullptr; | |
| SSL *ssl = nullptr; | |
| std::function<bool(void)> interrupt; | |
| }; | |
| static int PacketedBioMethodType() { | |
| static int type = [] { | |
| int idx = BIO_get_new_index(); | |
| BSSL_CHECK(idx > 0); | |
| return idx | BIO_TYPE_FILTER; | |
| }(); | |
| return type; | |
| } | |
| PacketedBio *GetData(BIO *bio) { | |
| if (BIO_method_type(bio) != PacketedBioMethodType()) { | |
| return nullptr; | |
| } | |
| return static_cast<PacketedBio *>(BIO_get_data(bio)); | |
| } | |
| // ReadAll reads `len` bytes from `bio` into `out`. It returns 1 on success and | |
| // 0 or -1 on error. | |
| static int ReadAll(BIO *bio, bssl::Span<uint8_t> out) { | |
| while (!out.empty()) { | |
| int ret = BIO_read(bio, out.data(), | |
| static_cast<int>(std::min(out.size(), size_t{INT_MAX}))); | |
| if (ret <= 0) { | |
| return ret; | |
| } | |
| out = out.subspan(ret); | |
| } | |
| return 1; | |
| } | |
| static int PacketedWriteEx(BIO *bio, const char *in, size_t inl, | |
| size_t *out_written) { | |
| BIO *next = BIO_next(bio); | |
| if (next == nullptr) { | |
| return 0; | |
| } | |
| BIO_clear_retry_flags(bio); | |
| // Write the header. | |
| uint8_t header[5]; | |
| header[0] = kOpcodePacket; | |
| BSSL_CHECK(inl <= UINT32_MAX - 1); | |
| CRYPTO_store_u32_be(header + 1, inl); | |
| if (!BIO_write_all(next, header, sizeof(header))) { | |
| BIO_copy_next_retry(bio); | |
| return 0; | |
| } | |
| // Write the buffer. | |
| if (!BIO_write_all(next, in, inl)) { | |
| BIO_copy_next_retry(bio); | |
| return 0; | |
| } | |
| *out_written = inl; | |
| return 1; | |
| } | |
| static int PacketedRead(BIO *bio, char *out, int outl) { | |
| PacketedBio *data = GetData(bio); | |
| BIO *next = BIO_next(bio); | |
| if (next == nullptr) { | |
| return -1; | |
| } | |
| BIO_clear_retry_flags(bio); | |
| if (data->interrupt) { | |
| BIO_set_retry_read(bio); | |
| return -1; | |
| } | |
| // Read the opcode. | |
| uint8_t opcode; | |
| int ret = ReadAll(next, bssl::Span(&opcode, 1)); | |
| if (ret <= 0) { | |
| BIO_copy_next_retry(bio); | |
| return ret; | |
| } | |
| switch (opcode) { | |
| case kOpcodeTimeout: { | |
| // Process the timeout. | |
| uint8_t buf[8]; | |
| ret = ReadAll(next, buf); | |
| if (ret <= 0) { | |
| BIO_copy_next_retry(bio); | |
| return ret; | |
| } | |
| uint64_t timeout = CRYPTO_load_u64_be(buf); | |
| timeout /= 1000; // Convert nanoseconds to microseconds. | |
| data->interrupt = [=] { return PacketedBioAdvanceClock(bio, timeout); }; | |
| // Send an ACK to the peer. | |
| ret = BIO_write(next, &kOpcodeTimeoutAck, 1); | |
| if (ret <= 0) { | |
| return ret; | |
| } | |
| assert(ret == 1); | |
| BIO_set_retry_read(bio); | |
| return -1; | |
| } | |
| case kOpcodeMTU: { | |
| uint8_t buf[4]; | |
| ret = ReadAll(next, buf); | |
| if (ret <= 0) { | |
| BIO_copy_next_retry(bio); | |
| return ret; | |
| } | |
| uint32_t mtu = CRYPTO_load_u32_be(buf); | |
| data->interrupt = [=]() -> bool { return SSL_set_mtu(data->ssl, mtu); }; | |
| BIO_set_retry_read(bio); | |
| return -1; | |
| } | |
| case kOpcodeExpectNextTimeout: { | |
| uint8_t buf[8]; | |
| ret = ReadAll(next, buf); | |
| if (ret <= 0) { | |
| BIO_copy_next_retry(bio); | |
| return ret; | |
| } | |
| uint64_t expected_ns = CRYPTO_load_u64_be(buf); | |
| data->interrupt = [=]() -> bool { | |
| timeval timeout; | |
| bool has_timeout = DTLSv1_get_timeout(data->ssl, &timeout); | |
| if (expected_ns == UINT64_MAX) { | |
| if (has_timeout) { | |
| fprintf(stderr, | |
| "Expected no timeout, but got %" PRIu64 ".%06" PRIu64 | |
| "s.\n", | |
| static_cast<uint64_t>(timeout.tv_sec), | |
| static_cast<uint64_t>(timeout.tv_usec)); | |
| return false; | |
| } | |
| } else { | |
| // Convert nanoseconds to microseconds. | |
| uint64_t expected_usec = expected_ns / 1000; | |
| // Split into seconds and fractional part. | |
| uint64_t expected_sec = expected_usec / 1000000; | |
| expected_usec %= 1000000; | |
| if (!has_timeout) { | |
| fprintf(stderr, | |
| "Expected timeout of %" PRIu64 ".%06" PRIu64 | |
| "s, but got none.\n", | |
| expected_sec, expected_usec); | |
| return false; | |
| } | |
| if (static_cast<uint64_t>(timeout.tv_sec) != expected_sec || | |
| static_cast<uint64_t>(timeout.tv_usec) != expected_usec) { | |
| fprintf(stderr, | |
| "Expected timeout of %" PRIu64 ".%06" PRIu64 | |
| "s, but got %" PRIu64 ".%06" PRIu64 "s.\n", | |
| expected_sec, expected_usec, | |
| static_cast<uint64_t>(timeout.tv_sec), | |
| static_cast<uint64_t>(timeout.tv_usec)); | |
| return false; | |
| } | |
| } | |
| return true; | |
| }; | |
| BIO_set_retry_read(bio); | |
| return -1; | |
| } | |
| case kOpcodeUpdateTimeout: { | |
| uint8_t buf[sizeof(uint32_t)]; | |
| ret = ReadAll(next, buf); | |
| if (ret <= 0) { | |
| BIO_copy_next_retry(bio); | |
| return ret; | |
| } | |
| uint32_t duration_ms = CRYPTO_load_u32_be(buf); | |
| data->interrupt = [=] { | |
| DTLSv1_set_initial_timeout_duration(data->ssl, duration_ms); | |
| // A real caller is expected to immediately check the new timeout and | |
| // call `DTLSv1_handle_timeout` if the timeout is now expired. We do not | |
| // this automatically, so that the test can send ExpectNextTimeout(0) | |
| // first. | |
| return true; | |
| }; | |
| BIO_set_retry_read(bio); | |
| return -1; | |
| } | |
| case kOpcodePacket: { | |
| // Read the length prefix. | |
| uint8_t len_bytes[4]; | |
| ret = ReadAll(next, len_bytes); | |
| if (ret <= 0) { | |
| BIO_copy_next_retry(bio); | |
| return ret; | |
| } | |
| std::vector<uint8_t> buf(CRYPTO_load_u32_be(len_bytes), 0); | |
| ret = ReadAll(next, bssl::Span(buf)); | |
| if (ret <= 0) { | |
| fprintf(stderr, "Packeted BIO was truncated\n"); | |
| return -1; | |
| } | |
| if (static_cast<size_t>(outl) > buf.size()) { | |
| outl = static_cast<int>(buf.size()); | |
| } | |
| OPENSSL_memcpy(out, buf.data(), outl); | |
| return outl; | |
| } | |
| default: | |
| fprintf(stderr, "Unknown opcode, %u\n", opcode); | |
| return -1; | |
| } | |
| } | |
| static long PacketedCtrl(BIO *bio, int cmd, long num, void *ptr) { | |
| BIO *next = BIO_next(bio); | |
| if (next == nullptr) { | |
| return 0; | |
| } | |
| BIO_clear_retry_flags(bio); | |
| long ret = BIO_ctrl(next, cmd, num, ptr); | |
| BIO_copy_next_retry(bio); | |
| return ret; | |
| } | |
| static int PacketedFree(BIO *bio) { | |
| if (bio == nullptr) { | |
| return 0; | |
| } | |
| delete GetData(bio); | |
| return 1; | |
| } | |
| static long PacketedCallbackCtrl(BIO *bio, int cmd, BIO_info_cb *fp) { | |
| BIO *next = BIO_next(bio); | |
| if (next == nullptr) { | |
| return 0; | |
| } | |
| return BIO_callback_ctrl(next, cmd, fp); | |
| } | |
| static const BIO_METHOD *PacketedBioMethod() { | |
| static const BIO_METHOD *method = [] { | |
| BIO_METHOD *ret = BIO_meth_new(PacketedBioMethodType(), "packeted bio"); | |
| BSSL_CHECK(ret); | |
| BSSL_CHECK(BIO_meth_set_write_ex(ret, PacketedWriteEx)); | |
| BSSL_CHECK(BIO_meth_set_read(ret, PacketedRead)); | |
| BSSL_CHECK(BIO_meth_set_ctrl(ret, PacketedCtrl)); | |
| BSSL_CHECK(BIO_meth_set_destroy(ret, PacketedFree)); | |
| BSSL_CHECK(BIO_meth_set_callback_ctrl(ret, PacketedCallbackCtrl)); | |
| return ret; | |
| }(); | |
| return method; | |
| } | |
| } // namespace | |
| bssl::UniquePtr<BIO> PacketedBioCreate(timeval *clock, SSL *ssl) { | |
| bssl::UniquePtr<BIO> bio(BIO_new(PacketedBioMethod())); | |
| if (!bio) { | |
| return nullptr; | |
| } | |
| BIO_set_data(bio.get(), new PacketedBio(clock, ssl)); | |
| BIO_set_init(bio.get(), 1); | |
| return bio; | |
| } | |
| bool PacketedBioHasInterrupt(BIO *bio) { | |
| PacketedBio *data = GetData(bio); | |
| return data != nullptr && data->interrupt; | |
| } | |
| bool PacketedBioHandleInterrupt(BIO *bio) { | |
| PacketedBio *data = GetData(bio); | |
| if (data == nullptr || !data->interrupt) { | |
| return false; | |
| } | |
| return std::exchange(data->interrupt, {})(); | |
| } | |
| bool PacketedBioAdvanceClock(BIO *bio, uint64_t microseconds) { | |
| PacketedBio *data = GetData(bio); | |
| if (data == nullptr) { | |
| return false; | |
| } | |
| data->clock->tv_sec += microseconds / 1000000; | |
| data->clock->tv_usec += microseconds % 1000000; | |
| if (data->clock->tv_usec >= 1000000) { | |
| data->clock->tv_usec -= 1000000; | |
| data->clock->tv_sec++; | |
| } | |
| // If DTLSv1_handle_timeout returns 1 (successfully handled timer), 0 (no | |
| // timers), or SSL_ERROR_WANT_WRITE (handled timeout but transport wasn't | |
| // ready), retry the operation. | |
| int ret = DTLSv1_handle_timeout(data->ssl); | |
| return ret >= 0 || SSL_get_error(data->ssl, ret) == SSL_ERROR_WANT_WRITE; | |
| } |