blob: 914b890e29c280ed778a07073e3ffd5b6061d963 [file] [log] [blame]
// Copyright (c) 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/quic/load_balancer/load_balancer_encoder.h"
#include "absl/numeric/int128.h"
#include "quiche/quic/core/quic_connection_id.h"
#include "quiche/quic/core/quic_data_reader.h"
#include "quiche/quic/core/quic_data_writer.h"
#include "quiche/quic/core/quic_packet_number.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/quic_utils.h"
#include "quiche/quic/load_balancer/load_balancer_config.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
namespace quic {
namespace {
// Returns the number of nonces given a certain |nonce_len|.
absl::uint128 NumberOfNonces(uint8_t nonce_len) {
return (static_cast<absl::uint128>(1) << (nonce_len * 8));
}
// Writes the |size| least significant bytes from |in| to |out| in host byte
// order. Returns false if |out| does not have enough space.
bool WriteUint128(const absl::uint128 in, uint8_t size, QuicDataWriter &out) {
if (out.remaining() < size) {
QUIC_BUG(quic_bug_435375038_05)
<< "Call to WriteUint128() does not have enough space in |out|";
return false;
}
uint64_t num64 = absl::Uint128Low64(in);
if (size <= sizeof(num64)) {
out.WriteBytes(&num64, size);
} else {
out.WriteBytes(&num64, sizeof(num64));
num64 = absl::Uint128High64(in);
out.WriteBytes(&num64, size - sizeof(num64));
}
return true;
}
} // namespace
constexpr uint8_t kLoadBalancerLengthMask = 0x3f;
constexpr uint8_t kLoadBalancerUnroutableConfigId = 0xc0;
absl::optional<LoadBalancerEncoder> LoadBalancerEncoder::Create(
QuicRandom &random, LoadBalancerEncoderVisitorInterface *const visitor,
const bool len_self_encoded, const uint8_t unroutable_connection_id_len) {
if (unroutable_connection_id_len == 0 ||
unroutable_connection_id_len >
kQuicMaxConnectionIdWithLengthPrefixLength) {
QUIC_BUG(quic_bug_435375038_01)
<< "Invalid unroutable_connection_id_len = "
<< static_cast<int>(unroutable_connection_id_len);
return absl::optional<LoadBalancerEncoder>();
}
return LoadBalancerEncoder(random, visitor, len_self_encoded,
unroutable_connection_id_len);
}
bool LoadBalancerEncoder::UpdateConfig(const LoadBalancerConfig &config,
const LoadBalancerServerId server_id) {
if (config_.has_value() && config_->config_id() == config.config_id()) {
QUIC_BUG(quic_bug_435375038_02)
<< "Attempting to change config with same ID";
return false;
}
if (server_id.length() != config.server_id_len()) {
QUIC_BUG(quic_bug_435375038_03)
<< "Server ID length " << static_cast<int>(server_id.length())
<< " does not match configured value of "
<< static_cast<int>(config.server_id_len());
return false;
}
if (visitor_ != nullptr) {
if (config_.has_value()) {
visitor_->OnConfigChanged(config_->config_id(), config.config_id());
} else {
visitor_->OnConfigAdded(config.config_id());
}
}
config_ = config;
server_id_ = server_id;
seed_ = absl::MakeUint128(random_.RandUint64(), random_.RandUint64()) %
NumberOfNonces(config.nonce_len());
num_nonces_left_ = NumberOfNonces(config.nonce_len());
return true;
}
void LoadBalancerEncoder::DeleteConfig() {
if (visitor_ != nullptr && config_.has_value()) {
visitor_->OnConfigDeleted(config_->config_id());
}
config_.reset();
server_id_.reset();
num_nonces_left_ = 0;
}
QuicConnectionId LoadBalancerEncoder::GenerateConnectionId() {
uint8_t length = (config_.has_value()) ? config_->total_len()
: unroutable_connection_id_len_;
uint8_t config_id = config_.has_value() ? (config_->config_id() << 6)
: kLoadBalancerUnroutableConfigId;
if (config_.has_value() != server_id_.has_value()) {
QUIC_BUG(quic_bug_435375038_04)
<< "Existence of config and server_id are out of sync";
return QuicConnectionId();
}
uint8_t first_byte;
// first byte
if (len_self_encoded_) {
first_byte = config_id | (length - 1);
} else {
random_.RandBytes(static_cast<void *>(&first_byte), 1);
first_byte = config_id | (first_byte & kLoadBalancerLengthMask);
}
if (config_id == kLoadBalancerUnroutableConfigId) {
return MakeUnroutableConnectionId(first_byte);
}
QuicConnectionId id;
id.set_length(length);
QuicDataWriter writer(length, id.mutable_data(), quiche::HOST_BYTE_ORDER);
writer.WriteUInt8(first_byte);
absl::uint128 next_nonce =
(seed_ + num_nonces_left_--) % NumberOfNonces(config_->nonce_len());
writer.WriteBytes(server_id_->data().data(), server_id_->length());
if (!WriteUint128(next_nonce, config_->nonce_len(), writer)) {
return QuicConnectionId();
}
uint8_t *block_start = reinterpret_cast<uint8_t *>(writer.data() + 1);
if (!config_->IsEncrypted()) {
// Fill the nonce field with a hash of the Connection ID to avoid the nonce
// visibly increasing by one. This would allow observers to correlate
// connection IDs as being sequential and likely from the same connection,
// not just the same server.
absl::uint128 nonce_hash =
QuicUtils::FNV1a_128_Hash(absl::string_view(writer.data(), length));
QuicDataWriter rewriter(config_->nonce_len(),
id.mutable_data() + config_->server_id_len() + 1,
quiche::HOST_BYTE_ORDER);
if (!WriteUint128(nonce_hash, config_->nonce_len(), rewriter)) {
return QuicConnectionId();
}
} else if (config_->plaintext_len() == kLoadBalancerBlockSize) {
// Use one encryption pass.
if (!config_->BlockEncrypt(block_start, block_start)) {
QUIC_LOG(ERROR) << "Block encryption failed";
return QuicConnectionId();
}
} else {
for (uint8_t i = 1; i <= kNumLoadBalancerCryptoPasses; i++) {
if (!config_->EncryptionPass(absl::Span<uint8_t>(block_start, length - 1),
i)) {
QUIC_LOG(ERROR) << "Block encryption failed";
return QuicConnectionId();
}
}
}
if (num_nonces_left_ == 0) {
DeleteConfig();
}
return id;
}
absl::optional<QuicConnectionId> LoadBalancerEncoder::GenerateNextConnectionId(
[[maybe_unused]] const QuicConnectionId &original) {
// Do not allow new connection IDs if linkable.
return (IsEncoding() && !IsEncrypted()) ? absl::optional<QuicConnectionId>()
: GenerateConnectionId();
}
absl::optional<QuicConnectionId> LoadBalancerEncoder::MaybeReplaceConnectionId(
const QuicConnectionId &original, const ParsedQuicVersion &version) {
// Pre-IETF versions of QUIC can respond poorly to new connection IDs issued
// during the handshake.
uint8_t needed_length = config_.has_value() ? config_->total_len()
: unroutable_connection_id_len_;
return (!version.HasIetfQuicFrames() && original.length() == needed_length)
? absl::optional<QuicConnectionId>()
: GenerateConnectionId();
}
QuicConnectionId LoadBalancerEncoder::MakeUnroutableConnectionId(
uint8_t first_byte) {
QuicConnectionId id;
id.set_length(unroutable_connection_id_len_);
id.mutable_data()[0] = first_byte;
random_.RandBytes(&id.mutable_data()[1], unroutable_connection_id_len_ - 1);
return id;
}
} // namespace quic