Victor Boivie | b9bdf64 | 2021-04-06 17:55:51 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2021 The WebRTC project authors. All Rights Reserved. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license |
| 5 | * that can be found in the LICENSE file in the root of the source |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | #include "net/dcsctp/socket/heartbeat_handler.h" |
| 11 | |
| 12 | #include <stddef.h> |
| 13 | |
| 14 | #include <cstdint> |
| 15 | #include <memory> |
| 16 | #include <string> |
| 17 | #include <utility> |
| 18 | #include <vector> |
| 19 | |
Victor Boivie | 600bb8c | 2021-08-12 13:43:13 | [diff] [blame] | 20 | #include "absl/functional/bind_front.h" |
Victor Boivie | b9bdf64 | 2021-04-06 17:55:51 | [diff] [blame] | 21 | #include "absl/strings/string_view.h" |
| 22 | #include "absl/types/optional.h" |
| 23 | #include "api/array_view.h" |
| 24 | #include "net/dcsctp/packet/bounded_byte_reader.h" |
| 25 | #include "net/dcsctp/packet/bounded_byte_writer.h" |
| 26 | #include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h" |
| 27 | #include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h" |
| 28 | #include "net/dcsctp/packet/parameter/heartbeat_info_parameter.h" |
| 29 | #include "net/dcsctp/packet/parameter/parameter.h" |
| 30 | #include "net/dcsctp/packet/sctp_packet.h" |
| 31 | #include "net/dcsctp/public/dcsctp_options.h" |
| 32 | #include "net/dcsctp/public/dcsctp_socket.h" |
| 33 | #include "net/dcsctp/socket/context.h" |
| 34 | #include "net/dcsctp/timer/timer.h" |
| 35 | #include "rtc_base/logging.h" |
| 36 | |
| 37 | namespace dcsctp { |
| 38 | |
| 39 | // This is stored (in serialized form) as HeartbeatInfoParameter sent in |
| 40 | // HeartbeatRequestChunk and received back in HeartbeatAckChunk. It should be |
| 41 | // well understood that this data may be modified by the peer, so it can't |
| 42 | // be trusted. |
| 43 | // |
| 44 | // It currently only stores a timestamp, in millisecond precision, to allow for |
| 45 | // RTT measurements. If that would be manipulated by the peer, it would just |
| 46 | // result in incorrect RTT measurements, which isn't an issue. |
| 47 | class HeartbeatInfo { |
| 48 | public: |
| 49 | static constexpr size_t kBufferSize = sizeof(uint64_t); |
| 50 | static_assert(kBufferSize == 8, "Unexpected buffer size"); |
| 51 | |
| 52 | explicit HeartbeatInfo(TimeMs created_at) : created_at_(created_at) {} |
| 53 | |
| 54 | std::vector<uint8_t> Serialize() { |
| 55 | uint32_t high_bits = static_cast<uint32_t>(*created_at_ >> 32); |
| 56 | uint32_t low_bits = static_cast<uint32_t>(*created_at_); |
| 57 | |
| 58 | std::vector<uint8_t> data(kBufferSize); |
| 59 | BoundedByteWriter<kBufferSize> writer(data); |
| 60 | writer.Store32<0>(high_bits); |
| 61 | writer.Store32<4>(low_bits); |
| 62 | return data; |
| 63 | } |
| 64 | |
| 65 | static absl::optional<HeartbeatInfo> Deserialize( |
| 66 | rtc::ArrayView<const uint8_t> data) { |
| 67 | if (data.size() != kBufferSize) { |
| 68 | RTC_LOG(LS_WARNING) << "Invalid heartbeat info: " << data.size() |
| 69 | << " bytes"; |
| 70 | return absl::nullopt; |
| 71 | } |
| 72 | |
| 73 | BoundedByteReader<kBufferSize> reader(data); |
| 74 | uint32_t high_bits = reader.Load32<0>(); |
| 75 | uint32_t low_bits = reader.Load32<4>(); |
| 76 | |
| 77 | uint64_t created_at = static_cast<uint64_t>(high_bits) << 32 | low_bits; |
| 78 | return HeartbeatInfo(TimeMs(created_at)); |
| 79 | } |
| 80 | |
| 81 | TimeMs created_at() const { return created_at_; } |
| 82 | |
| 83 | private: |
| 84 | const TimeMs created_at_; |
| 85 | }; |
| 86 | |
| 87 | HeartbeatHandler::HeartbeatHandler(absl::string_view log_prefix, |
| 88 | const DcSctpOptions& options, |
| 89 | Context* context, |
| 90 | TimerManager* timer_manager) |
| 91 | : log_prefix_(std::string(log_prefix) + "heartbeat: "), |
| 92 | ctx_(context), |
| 93 | timer_manager_(timer_manager), |
| 94 | interval_duration_(options.heartbeat_interval), |
| 95 | interval_duration_should_include_rtt_( |
| 96 | options.heartbeat_interval_include_rtt), |
| 97 | interval_timer_(timer_manager_->CreateTimer( |
| 98 | "heartbeat-interval", |
Victor Boivie | 600bb8c | 2021-08-12 13:43:13 | [diff] [blame] | 99 | absl::bind_front(&HeartbeatHandler::OnIntervalTimerExpiry, this), |
Victor Boivie | b9bdf64 | 2021-04-06 17:55:51 | [diff] [blame] | 100 | TimerOptions(interval_duration_, TimerBackoffAlgorithm::kFixed))), |
| 101 | timeout_timer_(timer_manager_->CreateTimer( |
| 102 | "heartbeat-timeout", |
Victor Boivie | 600bb8c | 2021-08-12 13:43:13 | [diff] [blame] | 103 | absl::bind_front(&HeartbeatHandler::OnTimeoutTimerExpiry, this), |
Victor Boivie | b9bdf64 | 2021-04-06 17:55:51 | [diff] [blame] | 104 | TimerOptions(options.rto_initial, |
| 105 | TimerBackoffAlgorithm::kExponential, |
| 106 | /*max_restarts=*/0))) { |
| 107 | // The interval timer must always be running as long as the association is up. |
Victor Boivie | 5429d71 | 2021-05-19 20:49:28 | [diff] [blame] | 108 | RestartTimer(); |
Victor Boivie | b9bdf64 | 2021-04-06 17:55:51 | [diff] [blame] | 109 | } |
| 110 | |
| 111 | void HeartbeatHandler::RestartTimer() { |
Victor Boivie | 5429d71 | 2021-05-19 20:49:28 | [diff] [blame] | 112 | if (interval_duration_ == DurationMs(0)) { |
| 113 | // Heartbeating has been disabled. |
| 114 | return; |
| 115 | } |
| 116 | |
Victor Boivie | b9bdf64 | 2021-04-06 17:55:51 | [diff] [blame] | 117 | if (interval_duration_should_include_rtt_) { |
| 118 | // The RTT should be used, but it's not easy accessible. The RTO will |
| 119 | // suffice. |
| 120 | interval_timer_->set_duration(interval_duration_ + ctx_->current_rto()); |
| 121 | } else { |
| 122 | interval_timer_->set_duration(interval_duration_); |
| 123 | } |
| 124 | |
| 125 | interval_timer_->Start(); |
| 126 | } |
| 127 | |
| 128 | void HeartbeatHandler::HandleHeartbeatRequest(HeartbeatRequestChunk chunk) { |
| 129 | // https://tools.ietf.org/html/rfc4960#section-8.3 |
| 130 | // "The receiver of the HEARTBEAT should immediately respond with a |
| 131 | // HEARTBEAT ACK that contains the Heartbeat Information TLV, together with |
| 132 | // any other received TLVs, copied unchanged from the received HEARTBEAT |
| 133 | // chunk." |
| 134 | ctx_->Send(ctx_->PacketBuilder().Add( |
| 135 | HeartbeatAckChunk(std::move(chunk).extract_parameters()))); |
| 136 | } |
| 137 | |
| 138 | void HeartbeatHandler::HandleHeartbeatAck(HeartbeatAckChunk chunk) { |
| 139 | timeout_timer_->Stop(); |
| 140 | absl::optional<HeartbeatInfoParameter> info_param = chunk.info(); |
| 141 | if (!info_param.has_value()) { |
| 142 | ctx_->callbacks().OnError( |
| 143 | ErrorKind::kParseFailed, |
| 144 | "Failed to parse HEARTBEAT-ACK; No Heartbeat Info parameter"); |
| 145 | return; |
| 146 | } |
| 147 | absl::optional<HeartbeatInfo> info = |
| 148 | HeartbeatInfo::Deserialize(info_param->info()); |
| 149 | if (!info.has_value()) { |
| 150 | ctx_->callbacks().OnError(ErrorKind::kParseFailed, |
| 151 | "Failed to parse HEARTBEAT-ACK; Failed to " |
| 152 | "deserialized Heartbeat info parameter"); |
| 153 | return; |
| 154 | } |
| 155 | |
Victor Boivie | f75f9c2 | 2021-09-24 09:50:29 | [diff] [blame] | 156 | TimeMs now = ctx_->callbacks().TimeMillis(); |
Victor Boivie | 3179fb2 | 2021-10-08 09:53:32 | [diff] [blame] | 157 | if (info->created_at() > TimeMs(0) && info->created_at() <= now) { |
Victor Boivie | f75f9c2 | 2021-09-24 09:50:29 | [diff] [blame] | 158 | ctx_->ObserveRTT(now - info->created_at()); |
| 159 | } |
Victor Boivie | b9bdf64 | 2021-04-06 17:55:51 | [diff] [blame] | 160 | |
| 161 | // https://tools.ietf.org/html/rfc4960#section-8.1 |
| 162 | // "The counter shall be reset each time ... a HEARTBEAT ACK is received from |
| 163 | // the peer endpoint." |
| 164 | ctx_->ClearTxErrorCounter(); |
| 165 | } |
| 166 | |
| 167 | absl::optional<DurationMs> HeartbeatHandler::OnIntervalTimerExpiry() { |
| 168 | if (ctx_->is_connection_established()) { |
| 169 | HeartbeatInfo info(ctx_->callbacks().TimeMillis()); |
| 170 | timeout_timer_->set_duration(ctx_->current_rto()); |
| 171 | timeout_timer_->Start(); |
| 172 | RTC_DLOG(LS_INFO) << log_prefix_ << "Sending HEARTBEAT with timeout " |
| 173 | << *timeout_timer_->duration(); |
| 174 | |
| 175 | Parameters parameters = Parameters::Builder() |
| 176 | .Add(HeartbeatInfoParameter(info.Serialize())) |
| 177 | .Build(); |
| 178 | |
| 179 | ctx_->Send(ctx_->PacketBuilder().Add( |
| 180 | HeartbeatRequestChunk(std::move(parameters)))); |
| 181 | } else { |
| 182 | RTC_DLOG(LS_VERBOSE) |
| 183 | << log_prefix_ |
| 184 | << "Will not send HEARTBEAT when connection not established"; |
| 185 | } |
| 186 | return absl::nullopt; |
| 187 | } |
| 188 | |
| 189 | absl::optional<DurationMs> HeartbeatHandler::OnTimeoutTimerExpiry() { |
| 190 | // Note that the timeout timer is not restarted. It will be started again when |
| 191 | // the interval timer expires. |
| 192 | RTC_DCHECK(!timeout_timer_->is_running()); |
| 193 | ctx_->IncrementTxErrorCounter("HEARTBEAT timeout"); |
| 194 | return absl::nullopt; |
| 195 | } |
| 196 | } // namespace dcsctp |