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